Compare commits

...

1403 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 4c6ae316d8 Moodle release 3.5.4 2019-01-12 13:25:56 +01:00
Sara Arjona 609b020512 weekly release 3.5.3+ 2019-01-10 17:51:28 +01:00
Sara Arjona da5d00f2b2 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2019-01-10 17:51:26 +01:00
Jake Dallimore 765c99d4d9 Merge branch 'MDL-64004-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2019-01-10 11:00:43 +08:00
AMOS bot 4b04cbdbab Automatically generated installer lang files 2019-01-10 00:08:50 +00:00
Eloy Lafuente (stronk7) 4412db99fc Merge branch 'MDL-63686-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2019-01-09 12:36:47 +01:00
Andrew Nicols ef227b403e MDL-63686 core: recordset => records when fetching contexts 2019-01-09 19:08:08 +08:00
Jake Dallimore 96457d1739 Merge branch 'MDL-64375-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2019-01-09 15:34:06 +08:00
Andrew Nicols 26f70e8f28 Merge branch 'MDL-62550-35-2' of https://github.com/snake/moodle into MOODLE_35_STABLE 2019-01-09 15:13:27 +08:00
Jake Dallimore 3fdd140150 Merge branch 'MDL-64345-35' of https://github.com/s-cenni/MDL-64345 into MOODLE_35_STABLE 2019-01-09 14:15:05 +08:00
Jake Dallimore 29cb950320 MDL-62550 core_theme: remove unused #fgroup_ and #fitem_ rules
These ids didn't exists in form elements in Boost until now, so we'll
remove the rules to prevent them having any effect now.
2019-01-09 11:27:40 +08:00
Jake Dallimore ac25e4ace6 MDL-62550 core_form: bring back the form item unique ids to fix js fails
This id matches the id seen on the clean theme elements and is needed by
the forms js.
2019-01-09 11:27:38 +08:00
Jake Dallimore c00f5586f3 Merge branch 'MDL-63761-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2019-01-09 09:07:56 +08:00
Eloy Lafuente (stronk7) 821a753844 Merge branch 'MDL-64285-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2019-01-09 00:03:47 +01:00
Andrew Nicols 3cd69fd335 Merge branch 'MDL-24951_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2019-01-09 06:45:00 +08:00
Eloy Lafuente (stronk7) 67d77a2456 MDL-24951 mod_forum: Apply new string to related scenarios
Plus amend an scenario name that was clearly incorrect.
2019-01-08 16:48:25 +01:00
Michael Hawkins 9ca8ccbefc MDL-64395 groups: Added XSS Risk to the manage groups capability 2019-01-08 16:21:48 +01:00
Michael Hawkins 4166d28e1f MDL-64372 userpix: Escape fullname string on userpix index page 2019-01-08 16:21:48 +01:00
Sara Arjona 57e3305b41 MDL-64222 badge: Removed backpackurl from the edit_form 2019-01-08 16:21:48 +01:00
Eloy Lafuente (stronk7) 80f46ee1c0 Merge branch 'MDL-64509-35-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2019-01-08 16:19:56 +01:00
Andrew Nicols 7d837832e5 Merge branch 'MDL-58596-35' of git://github.com/vmdef/moodle into MOODLE_35_STABLE 2019-01-08 16:16:15 +08:00
Leon Stringer 43246bd859 MDL-58596 core_stats: fixed infinite loop cause by DST change 2019-01-08 08:09:38 +00:00
Jake Dallimore 29e7974006 Merge branch 'MDL-63686-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2019-01-08 14:38:31 +08:00
Andrew Nicols 7c512ad12f Merge branch 'MDL-24951-35' of git://github.com/Chocolate-lightning/moodle into MOODLE_35_STABLE 2019-01-08 14:20:25 +08:00
Andrew Nicols 5b01d6249b MDL-63686 core: Preload parent contexts 2019-01-08 14:12:27 +08:00
Andrew Nicols a36f9d20ed Merge branch 'MDL-64497-m35' of https://github.com/sammarshallou/moodle into MOODLE_35_STABLE 2019-01-08 12:29:16 +08:00
Andrew Nicols 68da8600b7 Merge branch 'MDL-64400-35' of git://github.com/aanabit/moodle into MOODLE_35_STABLE 2019-01-08 08:32:16 +08:00
Andrew Nicols b366722a14 Merge branch 'MDL-63276_35' of https://github.com/stronk7/moodle into MOODLE_35_STABLE 2019-01-08 06:56:29 +08:00
Eloy Lafuente (stronk7) 712fc8341c Merge branch 'MDL-63809-35-5' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2019-01-07 22:13:40 +01:00
Helen Foster ed72175e45 MDL-64509 lang: Import fixed English strings (en_fix) 2019-01-07 20:21:09 +01:00
Andrew Nicols 042748a342 Merge branch 'MDL-57636_m35' of https://github.com/dasistwas/moodle into MOODLE_35_STABLE 2019-01-07 13:48:32 +08:00
David Bogner 98125d376a MDL-57636 frontpage: Fix display enrolled courses 2019-01-04 10:56:24 +01:00
Damyon Wiese aa3c3bd353 MDL-64375 gradingform_rubric: Styles for overflow
Allow people to define lots of levels, and make the scroll region just the rubric table,
not the entire content region.
2019-01-04 08:44:41 +08:00
sam marshall 02fab5539c MDL-64497 GDPR: moodle_content_writer can cause endless loop
Fixes a buggy function by replacing it with a call to file_get_contents,
and adds error detection on a couple of file accesses.
2019-01-03 17:04:13 +00:00
Jake Dallimore a32a22f07f Merge branch 'MDL-49651-35' of git://github.com/Chocolate-lightning/moodle into MOODLE_35_STABLE 2019-01-03 10:50:01 +08:00
Eloy Lafuente (stronk7) 2e8f29095f MDL-63276 environment: PHP 7.1 and Moodle 3.2 required 2019-01-03 01:18:54 +01:00
Eloy Lafuente (stronk7) 56ce8d00da MDL-63276 environment: 3.7 base information
This is a direct copy of the 3.6 branch to
have changes under control easier in next commits.
2019-01-03 01:18:54 +01:00
Damyon Wiese fdfa0d497f MDL-63761 grade: Performance fix for regrading
Don't use a separate variable to count the number of items in finalids.
2019-01-02 10:25:08 +08:00
Amaia Anabitarte ecb4b0260e MDL-64400 core_messages: Mark notifications sent via email as read 2018-12-27 09:32:25 +01:00
Amaia Anabitarte da73f1364d MDL-64400 core_messages: Allow cron to delete unread notifications too 2018-12-27 09:32:25 +01:00
Mathew 3430f5252a MDL-49651 mod_forum: Respect course visibility in cron
This checks the courses' current visibility and the users permissions.
If they have the permission to view the course when it is hidden they'll get messages.

We already have checks for the users enrolled & subscription status.
2018-12-24 09:34:47 +08:00
Mathew 2f56e8a287 MDL-24951 mod_forum: Check user capability to know what message to show.
If the user has the following capabilty: mod/forum:canposttomygroups then show them
the more technical of the strings. Otherwise we use something a bit easier for them to ask their
tutor or teacher about.
2018-12-24 09:13:36 +08:00
Víctor Déniz Falcón 566978d358 MDL-58596 core_stats: unit test for stats_get_base_weekly 2018-12-20 13:54:46 +00:00
Adrian Greeve 79d87ad683 weekly release 3.5.3+ 2018-12-20 11:09:14 +01:00
Adrian Greeve f4b78030ae Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-12-20 11:09:13 +01:00
Andrew Nicols 97eac5487e Merge branch 'MDL-63512-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-12-20 13:04:22 +08:00
Peter f5065aad46 MDL-63512 mod_lesson: Update the lesson css to exclude mediaplugin div 2018-12-20 12:50:53 +08:00
Peter b5cc9d4e96 MDL-63512 mod_lesson: Fix video links provided in feedback 2018-12-20 12:50:53 +08:00
Adrian Greeve 5f2edc4df7 Merge branch 'MDL-64392-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-12-19 12:06:05 +01:00
Adrian Greeve 5900a79aca MDL-59954 course: Remove unused global. 2018-12-19 10:40:06 +01:00
Peter 18d086a8a4 MDL-59954 mod_course: Remove file size restriction on D&D on course page
Remove file size restrictions for teachers on course page.
Use user_max_upload_file_size instead of the max_file_size
2018-12-19 10:37:08 +01:00
Jake Dallimore cb14d331fe Merge branch 'MDL-64216-m35' of https://github.com/micaherne/moodle into MOODLE_35_STABLE 2018-12-19 11:08:35 +08:00
Andrew Nicols 56ec2b0949 Merge branch 'MDL-63512-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-12-19 10:01:00 +08:00
AMOS bot d8aba89429 Automatically generated installer lang files 2018-12-19 00:09:13 +00:00
Adrian Greeve 4632b4a151 Merge branch 'MDL-64025-35' of https://github.com/christianwolters/moodle into MOODLE_35_STABLE 2018-12-18 16:01:48 +01:00
Shamim Rezaie e4eb5170a1 MDL-63809 question: handling bad questions in question_has_capability_on 2018-12-18 15:44:36 +11:00
Tim Hunt 1340c03ba6 MDL-63809 question bank: ensure question_has_capability_on can be called 2018-12-18 15:44:36 +11:00
Damyon Wiese a912d44652 MDL-64004 webservice: Do not update accesstime
Do not update the automatically update the course access time when a
course is accessed via a webservice or ajax.
2018-12-18 09:08:20 +08:00
Sara Cenni 9ffc3296cc MDL-64345 core: Make FB logo upgrade cross-DB compatible 2018-12-17 11:33:50 +01:00
Eloy Lafuente (stronk7) 7650dbcaae MDL-63891 assignfeedback_editpdf: add missing UK to install.xml
Backport of MDL-57610.
2018-12-17 11:22:18 +01:00
Adam Olley 4015cb2d05 MDL-63891 assignfeedback_editpdf: integration cleanup
* Add a note to upgrade.php to notify devs/admins of the new setting.
* Replace update_record with set_field.
* Add missing phpdoc.

Backport of MDL-57610.
2018-12-17 11:22:17 +01:00
Adam Olley b1aac17d18 MDL-63891 assignfeedback_editpdf: Add test coverage for conversion task
Without this, we have no coverage of the convert_submissions task. This test
validates it both works normally, and that it also skips queued items that have
exceeded the conversion attempt limit introduced in this improvement.

Backport of MDL-57610.
2018-12-17 11:21:38 +01:00
Adam Olley de341c20d4 MDL-63891 assignfeedback_editpdf: remove queued jobs that keep failing
Without this, its possible to get a document in the editpdf conversion queue
that keeps failing - and in some cases completely crashing the PHP script
despite exception handling - which ends up blocking the processing of the queue
entirely.

This change allows for a configurable number of attempts the conversion
task will perform before removing the item from the queue.

Backport of MDL-57610.
2018-12-17 11:21:38 +01:00
Jun Pataleta 5156ced934 Merge branch 'MDL-59629-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-12-17 15:56:04 +08:00
Tomasz Muras 9d8470e4b8 MDL-52966 core: static cache fix for file types. 2018-12-17 15:40:54 +08:00
Andrew Nicols 613d4f9e6d Merge branch 'MDL-61633-35' of https://github.com/lucisgit/moodle into MOODLE_35_STABLE 2018-12-17 15:29:40 +08:00
Andrew Nicols fc28257e09 Merge branch 'MDL-60518-35' of git://github.com/aanabit/moodle into MOODLE_35_STABLE 2018-12-17 14:59:59 +08:00
Jun Pataleta 672905cb4f Merge branch 'MDL-63260_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-12-17 14:10:02 +08:00
Jake Dallimore 084dadaa86 Merge branch 'MDL-62756' of https://github.com/miotto/moodle into MOODLE_35_STABLE 2018-12-17 11:25:20 +08:00
Christian Wolters 790c306bf7 MDL-64025 admin_bookmarks: Enable bookmarking of admin categories 2018-12-16 15:49:56 +01:00
Bas Brands 16db1b8083 MDL-64392 core_question: flexbox compatible separator for question types
* the br tag does not work well in a flexbox context, a 100% with div
works in both clean and boost.
2018-12-14 16:31:18 +01:00
Amaia Anabitarte f91e884d68 MDL-60518 core_competency: Set $USER global variable for tests 2018-12-14 10:27:41 +01:00
Frédéric Massart 7dcc1e721c MDL-60518 core_user: A user with ID 0 is never a real user 2018-12-14 10:27:41 +01:00
Mirko Otto f32c2f7c47 MDL-62756 form: Remove any click handler first.
The handler has the old state variable. Remove any click handler first.
2018-12-14 10:22:11 +01:00
Jun Pataleta 808e7c80dc MDL-64285 core: Fix environment check test
* Looping the environment results and skipping some items mean that
if a test gets skipped, the rest of the environment results won't be
tested. It's better to use a data provider for this in order to
individually test each environment result.
2018-12-14 11:22:07 +08:00
Peter bb13181338 MDL-59629 mod_block: Set the default region in add_region
Check and update default region in add_region if none set.
2018-12-14 10:29:55 +08:00
Peter f13544412e MDL-63512 mod_lesson: Fix video links provided in feedback 2018-12-14 10:27:12 +08:00
Jake Dallimore c8b4ee45a4 weekly release 3.5.3+ 2018-12-14 07:51:44 +08:00
Tony Butler 773e24cad4 MDL-61633 assignfeedback_editpdf: Scroll back to top when changing pages 2018-12-13 12:21:07 +00:00
Jake Dallimore 86eb5bb7e2 Merge branch 'MDL-62624-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-12-12 16:14:04 +08:00
Jun Pataleta 76570e405a Merge branch 'MDL-41507-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-12-12 11:17:05 +08:00
Shamim Rezaie eeb374e2e6 MDL-41507 core_group: Consistent default value for FEATURE_GROUPS 2018-12-12 14:05:01 +11:00
Jake Dallimore b4999d92d9 MDL-63887 auth_ldap: fix unclean exit from sync when users not found
We should return, not exit, and we should clean up before we do.
2018-12-12 09:43:51 +08:00
Jake Dallimore 113c94cf9a Merge branch 'MDL-63887_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-12-12 09:43:45 +08:00
Jake Dallimore 583799d5b9 Merge branch 'MDL-63150-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-12-12 08:32:55 +08:00
Jake Dallimore 7f0903200f Merge branch 'MDL-63145-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-12-12 08:30:20 +08:00
Jake Dallimore 061db30ef7 Merge branch 'MDL-63147-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-12-12 08:25:43 +08:00
Peter 91ef3d2e40 MDL-63145 mod_page: Accept userid param as override
Accept a userid as an override in the lib function instead of
relying on $USER
2018-12-12 07:31:59 +08:00
Peter 07baf8e940 MDL-63150 mod_url: Accept a userid override in event action
Accept a userid in the event action instead of depending on $USER
2018-12-12 07:18:28 +08:00
Peter 5362c6f185 MDL-63147 mod_resource: Accept userid in resource event_action 2018-12-12 07:08:13 +08:00
Tim Hunt 2a0ee3647f MDL-63260 qtype_random: orphaned random questions should be deleted
This fix is based on some original code written by Bo Pierce.
2018-12-11 14:09:22 +00:00
Jake Dallimore 2496dc283c Merge branch 'MDL-64357_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-12-11 17:45:16 +08:00
Jake Dallimore 5b10127f11 Merge branch 'MDL-63485_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-12-11 17:30:10 +08:00
Mark Nelson 551975b2ac MDL-64357 ltiservice_gradebookservices: updated unit tests 2018-12-11 17:03:26 +08:00
Claude Vervoort 111998407c MDL-64357 ltiservice_gradebookservices: fix cleanup deleting lineitems 2018-12-11 17:03:26 +08:00
Jake Dallimore 7fc84f65de Merge branch 'MDL-62922-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-12-11 12:50:58 +08:00
Jun Pataleta 6cc427e025 Merge branch 'MDL-64280-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-12-11 09:53:20 +08:00
Luca Bösch 1188a6e41b MDL-64280 core_outcome: Add/remove outcome buttons style. 2018-12-10 17:56:03 +01:00
Sara Arjona 9cfb533e2d MDL-63485 core_user: Allow filtering by No groups on participants page
A new optional parameter $context has been added to the
core_group::groups_get_members_join() function.
Besides, some core_group methods now accept -1 (USERSWITHOUTHGROUP) for
the groupid field.

Backport of MDL-61967.
2018-12-10 20:29:04 +08:00
Jun Pataleta 2203bf4ad4 Merge branch 'MDL-62061-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-12-10 14:46:30 +08:00
Mihail Geshoski 82f74c8e2b MDL-62624 lesson: Fix alignment in matching questions in boost 2018-12-10 13:47:48 +08:00
Jake Dallimore 2fbae51df0 weekly release 3.5.3+ 2018-12-10 08:10:18 +08:00
Jake Dallimore f087b00ea6 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-12-10 08:10:17 +08:00
AMOS bot 1bfa3f5697 Automatically generated installer lang files 2018-12-07 00:09:32 +00:00
Adrian Greeve 3692fe595c MDL-62061 core_privacy: Stop directories being created.
This stops directories being created in the root directory
while running unit tests.
2018-12-06 11:39:01 +01:00
papillon326 10097b1b61 MDL-63887 auth_ldap: avoid infinite loop when search limit is reached 2018-12-06 15:40:47 +08:00
papillon326 4c7af8f17b MDL-63887 auth_ldap: changed variable names to be conform to core 2018-12-06 15:40:47 +08:00
Jun Pataleta 864374faf2 Merge branch 'MDL-64283-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-12-06 14:57:25 +08:00
Jake Dallimore f6b4c923ec Merge branch 'MDL-63710-MOODLE_35' of https://github.com/ilya-catalyst/moodle into MOODLE_35_STABLE 2018-12-06 11:45:04 +08:00
Jake Dallimore a702f2b935 Merge branch 'MDL-46783_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-12-06 10:23:50 +08:00
Ilya Tregubov daa7d65f34 MDL-63710 repository: Delete orphaned file records. 2018-12-06 12:38:25 +11:00
Jun Pataleta b7e4fbb246 Merge branch 'MOODLE_35_STABLE-MDL-64290-imscc-restore-temp-dir' of https://github.com/mspall/moodle into MOODLE_35_STABLE 2018-12-06 08:56:44 +08:00
AMOS bot 4745892272 Automatically generated installer lang files 2018-12-06 00:08:01 +00:00
Michael Spall 559759aa4a MDL-64290 backup imscc: Use correct tempdir for imscc restore
Use configured temp directory $CFG->tempdir instead of hard coded
$CFG->dataroot . '/temp/backup/'
Remove unused variable, $CFG
2018-12-05 11:05:44 -07:00
Tim Hunt edb89914d8 MDL-46783 accesslib: tests for MDL-63818
MDL-46783 and MDL-63818 were originally coded on the
assumption that MDL-46783 would get integrated first. However it wasn't.
Therefore, it was not easy to add unit tests for MDL-63818
at the time, but it is now, so here they are.
2018-12-05 16:41:42 +00:00
Jun Pataleta c2e3ca2f6c MDL-62922 core_user: Check auth mechanism before validating password
* External authentication mechanisms (e.g. via oauth2, etc.) don't store
  password in the user table, so we shouldn't be requiring password in
  such case when creating users via the core_user_create_users WS
  function.
2018-12-05 15:33:33 +08:00
Jake Dallimore c758547bd7 Merge branch 'MDL-64198-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-12-05 14:31:02 +08:00
Damyon Wiese f20b808629 Merge branch 'MDL-60267-35' of https://github.com/rogerbaba/moodle into MOODLE_35_STABLE 2018-12-05 12:56:48 +08:00
Tim Hunt 6a58779607 MDL-46783 permissions: let some moodle/ caps be overriden in all mods
The capabilities changed ('contextlevel' => CONTEXT_COURSE changed to
'contextlevel' => CONTEXT_MODULE) are:

* moodle/site:accessallgroups
* moodle/site:viewfullnames
* moodle/site:trustcontent
* moodle/site:viewuseridentity

This list came from reviewing the _get_extra_capabilities functions in
all core activities. They were all somewhat inconsistent, but I think it
makes sense that these capabilities are consistently overridable in all
activities. E.g. moodle/site:accessallgroups affects conditional
availability even if there is no other user of groups, and
moodle/site:viewuseridentity and moodle/site:viewfullnames affect the
logs report, if nothing else.

As a result of this, several _get_extra_capabilities functions are no
longer needed, and all the rest have been simplified.
2018-12-04 18:01:29 +00:00
Michael Aherne 83c4b6d01e MDL-64216 privacy: Split query to use indexes. 2018-12-04 15:07:40 +00:00
Luca Bösch 7c72db7d7e MDL-64198 course: set formatted modulename for togglecompletion js. 2018-12-04 14:32:09 +01:00
Eloy Lafuente (stronk7) 3aca528730 weekly release 3.5.3+ 2018-12-04 13:06:05 +01:00
Jun Pataleta 0dc3fbf069 Merge branch 'MDL-64206_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-12-04 10:49:43 +08:00
Damyon Wiese c5dab021db Merge branch 'MDL-64286_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-12-03 15:40:47 +08:00
Jake Dallimore a08ecb70f0 Merge branch 'MDL-63459_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-12-03 14:26:29 +08:00
Mark Nelson d5901aa09c MDL-64206 core: updated FB logo URL 2018-12-03 12:26:28 +08:00
Eloy Lafuente (stronk7) 394c5ae9b8 weekly release 3.5.3+ 2018-11-30 18:02:10 +01:00
Eloy Lafuente (stronk7) 79bfd75d15 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-11-30 18:02:08 +01:00
Tim Hunt 35e9c7202e MDL-64286 qtype_gapselect: removed unneeded line of code
The extended Behat test show the qtype works fine without it.
2018-11-30 12:36:06 +00:00
Jun Pataleta 4ff43a278b Merge branch 'MDL-64214-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-11-30 09:45:00 +08:00
Damyon Wiese 9485649edd MDL-64283 competency: Correct numbering in tests 2018-11-30 09:38:23 +08:00
Jun Pataleta 673340157a MDL-64214 calendar: Populate the groups select box on page load 2018-11-30 09:26:25 +08:00
Simey Lameze c6dd258bfd MDL-64214 calendar: fix failing unit tests 2018-11-30 09:26:25 +08:00
Simey Lameze f4a61ac99a MDL-64214 core_calendar: fix event types form element handling 2018-11-30 09:26:25 +08:00
AMOS bot df367e966a Automatically generated installer lang files 2018-11-30 00:07:41 +00:00
AMOS bot 276097e0d7 Automatically generated installer lang files 2018-11-29 00:08:25 +00:00
David Monllao b64e7253b2 weekly release 3.5.3+ 2018-11-28 11:23:14 +01:00
Jun Pataleta 02b46dd142 Merge branch 'MDL-64223-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-11-28 13:59:35 +08:00
Jake Dallimore ce2684efd9 MDL-64223 libraries: PHP7.1 compatibility fixes for Jabber lib 2018-11-28 09:58:58 +08:00
Eloy Lafuente (stronk7) 23c05fe9ce Merge branch 'MDL-64220-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-11-27 16:44:25 +01:00
Andrew Nicols 944d0fd395 Merge branch 'MDL-64161-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-11-27 15:17:28 +08:00
Adrian Greeve fa2a738f80 MDL-64161 gradereport_singleview: Single updates reported correctly.
If there is an error with the update of individual entries this is
also correctly displayed as not being updated.
2018-11-27 15:04:27 +08:00
Damyon Wiese 35bc9f8bb2 MDL-64220 privacy: Clean path params
We need to consistently clean the path params before we use them. This
shows a difference on windows where dir separators are different.
2018-11-27 15:02:44 +08:00
Jun Pataleta 2e36915e1f Merge branch 'MDL-64181-35-fix' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-27 14:49:22 +08:00
Andrew Nicols aad94d9332 MDL-64181 core: Ensure that self is included in parentage 2018-11-27 14:47:34 +08:00
Jun Pataleta 96c3b2042e Merge branch 'MDL-64181-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-11-27 11:45:09 +08:00
Andrew Nicols c7390b1c4f Merge branch 'MDL-64161-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-11-27 11:37:22 +08:00
Damyon Wiese 60b951eca5 MDL-64181 javascript: Recursive template fix
This change prevents template rendering from waiting for itself, but does
allow it to wait for another async call to render.
2018-11-27 08:45:15 +08:00
David Monllao 89d45109aa Merge branch 'MDL-64205-35-sarunconfirmeduser' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-11-26 15:47:53 +01:00
David Mudrák 690e03bfea 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-26 14:17:55 +01:00
Tim Hunt 37438b2a00 MDL-63459 qtype_calculatedmulti: fix incorrect hint object type 2018-11-26 12:55:15 +00:00
Tim Hunt 89e3b8aa80 MDL-63459 qtype_calculatedmulti: unit test should test this qtype
This also demonstrates the bug to be fixed
2018-11-26 12:55:12 +00:00
Adrian Greeve b87ad367d9 MDL-64161 gradereport_singleview: Behat test for out of bounds grade. 2018-11-26 11:46:06 +08:00
Adrian Greeve 27cfbf2986 MDL-64161 gradereport_singleview: Stop updating grades with errors. 2018-11-26 11:45:55 +08:00
David Mudrák 8575b451c4 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-25 07:21:59 +01:00
Eloy Lafuente (stronk7) e3a1c26bd7 Merge branch 'MDL-64186-35-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-11-24 00:27:14 +01:00
Eloy Lafuente (stronk7) 51b022db9b weekly release 3.5.3+ 2018-11-23 16:36:48 +01:00
Eloy Lafuente (stronk7) 33a95ab889 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-11-23 16:36:46 +01:00
Helen Foster a6a0cc37ed MDL-64186 lang: Import fixed English strings (en_fix) 2018-11-23 14:46:51 +01:00
Jun Pataleta e452305994 Merge branch 'MDL-64030-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-23 11:15:07 +08:00
AMOS bot 135a5df634 Automatically generated installer lang files 2018-11-23 00:07:32 +00:00
Andrew Nicols b78c5102ac MDL-64030 tool_dataprivacy: Process requests when system purpose not set 2018-11-23 07:54:20 +08:00
Eloy Lafuente (stronk7) b9c412be9f Merge branch 'MDL-62988_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-11-22 23:16:06 +01:00
Jun Pataleta 01eeab4ed2 Merge branch 'MDL-64119-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-11-22 20:31:45 +08:00
Sara Arjona 30f7f010a9 MDL-64119 output: Use context of renderer_base's page object 2018-11-22 20:31:04 +08:00
Eloy Lafuente (stronk7) f8c43813de Merge branch 'MDL-63571-35-fix' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-11-22 10:35:13 +01:00
Jun Pataleta e92bb861d4 MDL-63571 grade_report_grader: Use correct context for capability check 2018-11-22 16:20:22 +08:00
Jun Pataleta 83a6d7a194 MDL-63571 grade_report_grader: Set correct type for the context variable 2018-11-22 16:20:22 +08:00
Cory Buecker 0a39efd896 MDL-62988 LTI provider: fix missing info. for LTI 2.X tool provider
Fixes missing capabilities (user information) when membership service is called
from a LTI 2.X tool provider.
2018-11-22 12:27:07 +08:00
Jun Pataleta bfc740821b Merge branch 'MDL-64119-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-11-22 09:52:24 +08:00
Eloy Lafuente (stronk7) 9112915855 Merge branch 'MDL-63920-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-11-21 16:48:00 +01:00
Eloy Lafuente (stronk7) a6ff7933a1 Merge branch 'MDL-63798_assign_restore_groups_35' of git://github.com/davosmith/moodle into MOODLE_35_STABLE 2018-11-21 16:09:09 +01:00
Davo Smith e35c7c2c50 MDL-63798 mod_assign: fix restore when group is missing 2018-11-21 08:02:47 +00:00
David Monllao d699efb4ba Merge branch 'MDL-64063-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-11-21 06:28:46 +01:00
Adrian Greeve 0073588e27 MDL-64063 core_calendar: Unit test for suspended users. 2018-11-21 09:55:08 +08:00
Adrian Greeve 80cf67eaba MDL-64063 core_calendar: events by time considers enrolment status. 2018-11-21 09:54:58 +08:00
Víctor Déniz Falcón a23caa49b9 MDL-64077 block_site_main: modify style to fix layout in boost theme 2018-11-21 09:03:31 +08:00
Sara Arjona 0a519e70d4 MDL-64119 user: Display full names consistently for teachers
Fix an error with users with the moodle/site:viewfullnames capability
in a course context (like teachers), to display properly the full
names in pages like the participants one.
2018-11-20 19:48:23 +01:00
David Monllao dfecd50ac3 Merge branch 'MDL-63571-35' of git://github.com/peterRd/moodle into MOODLE_35_STABLE 2018-11-20 17:19:47 +01:00
David Monllao 56d8e413ba weekly release 3.5.3+ 2018-11-20 12:55:14 +01:00
David Monllao 3955dde284 Merge branch 'MDL-63394-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-11-20 10:34:09 +01:00
Damyon Wiese dc47c7fc8c MDL-63920 backup: Email string improvement
Change from semicolon to colon in email.
2018-11-20 16:28:37 +08:00
Juan Leyva d55565066c MDL-63394 mod_quiz: Do not fail overdue if there is a grace period 2018-11-20 09:27:40 +01:00
Peter 810bb4bef5 MDL-63571 gradebook: Display the name header based on config setting 2018-11-20 14:16:09 +08:00
Luca Bösch b6f83c07ed MDL-63571 grades: switch firstname/lastname order in title row 2018-11-20 14:16:09 +08:00
Andrew Nicols 58c02975d1 MDL-64049 core: Cache prefs for current user
We were already caching these preferences when a user object is provided
to get_user_preferences, or when $USER is provided.

This changee swaps get_user_preferences to use the global USER object
when the USER->id matches the userid supplied to the function.
2018-11-20 00:34:58 +01:00
Eloy Lafuente (stronk7) ef212b671e Merge branch 'MDL-63976-35-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-11-19 22:09:15 +01:00
Helen Foster cac9d8d665 MDL-63976 lang: Merge English strings from the en_fix language pack 2018-11-19 19:21:59 +01:00
David Monllao 127c4954a5 Merge branch 'MDL-63627-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-11-19 09:46:16 +01:00
Jun Pataleta af1a540eab Merge branch 'MDL-63994-35-castoken' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-11-19 15:51:27 +08:00
David Monllao 421b7b0c12 Merge branch 'MDL-63487-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-11-19 08:07:55 +01:00
David Monllao 8b508a6004 Merge branch 'MDL-63768-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-11-19 06:06:50 +01:00
Jun Pataleta 8bee660d88 Merge branch 'MDL-64001-35' of https://github.com/christianwolters/moodle into MOODLE_35_STABLE 2018-11-19 11:46:41 +08:00
Luca Bösch 54216a57a8 MDL-63768 atto_link: dialog auto width. 2018-11-16 17:27:55 +01:00
Luca Bösch d078274a59 MDL-63487 badges: Style "◀ Award badge" and "Revoke badge ▶" buttons 2018-11-16 17:27:44 +01:00
Juan Leyva 5fdb6183e5 MDL-63627 enrol: Return progress field correctly
- Check permissions and
- Return the progress for the given user
2018-11-16 15:58:07 +01:00
Eloy Lafuente (stronk7) fc2cde8f80 weekly release 3.5.3+ 2018-11-16 12:03:10 +01:00
Jun Pataleta 28ca1302e5 Merge branch 'MDL-63999-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-11-16 14:36:07 +08:00
Simey Lameze 5809488bb6 MDL-63999 calendar: double escape event name on templates 2018-11-16 13:59:29 +08:00
Jun Pataleta 4e0886f362 Merge branch 'MDL-63660-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-11-16 10:52:58 +08:00
Jun Pataleta 96388117d4 MDL-63660 tool_dataprivacy: Increase expected export file size 2018-11-16 10:49:10 +08:00
Mark Johnson c2650d3f3d MDL-63660 privacy: Include CSS locally for data export 2018-11-16 10:49:10 +08:00
Damyon Wiese e7fd2113c0 Merge branch 'wip-MDL-63854-MOODLE_35_STABLE' of https://github.com/kwiliarty/moodle into MOODLE_35_STABLE 2018-11-16 10:45:05 +08:00
Eloy Lafuente (stronk7) 32878692e3 Merge branch 'MDL-64012_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-11-16 01:03:45 +01:00
Kevin Wiliarty 696ecda501 MDL-63854 competencies, themes: misplaced dropdown arrows 2018-11-15 15:52:45 -05:00
David Mudrák 31bfc01e9d 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 20:44:03 +01:00
David Mudrák f779e204ac 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 20:35:46 +01:00
David Mudrák 9ccfa64940 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 20:27:58 +01:00
Jun Pataleta 2ca939b838 Merge branch 'MDL-63832-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-11-15 23:11:55 +08:00
Christian Wolters d04454fed5 MDL-64001 boost: Dropdown menu adding missing href 2018-11-15 12:39:09 +01:00
Jun Pataleta 315ffed9fb Merge branch 'MDL-62287-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-15 17:10:43 +08:00
Tim Hunt 9933be5fad MDL-64012 filters: activity name autolinking fails for names with () 2018-11-14 16:07:48 +00:00
Juan Leyva 1978e7205d MDL-63832 assign: Check current user can view submission 2018-11-14 16:54:17 +01:00
Eloy Lafuente (stronk7) 307ee3442b Merge branch 'MDL-63653-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-11-14 16:30:25 +01:00
Eloy Lafuente (stronk7) 42add6e9ef Merge branch 'MDL-63842-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-14 16:12:01 +01:00
David Monllao 6e493f59b8 Merge branch 'MDL-61990_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-11-14 12:09:59 +01:00
Damyon Wiese 512547df7e Merge branch 'MDL-64005-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-11-14 16:00:34 +08:00
Andrew Nicols 971d02f6f4 MDL-62287 tool_usertours: Pull v0.12.3 for background click fix 2018-11-14 14:14:17 +08:00
Adrian Greeve 841a0bc6e2 MDL-64005 gradebook: Fix to remove debugging message.
The progress bar is being used when there are a large amount
of grades being recalculated. This was throwing a debugging message
about NO_OUTPUT_BUFFERING being unset.
2018-11-14 12:42:30 +08:00
Damyon Wiese 17439b2c46 Merge branch 'MDL-63868-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-11-14 12:08:26 +08:00
Luca Bösch b675d6c5ee MDL-63653 tool_xmldb: style form elements 2018-11-13 18:14:16 +01:00
Tim Hunt edfc17d7ba MDL-61990 outputrequirementslib: improve debug message 2018-11-13 17:13:09 +00:00
David Monllao ab16666f05 Merge branch 'MDL-63882-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-11-13 16:07:27 +01:00
David Monllao 628a7bb557 weekly release 3.5.3+ 2018-11-13 13:10:48 +01:00
David Monllao 058db3e6c8 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-11-13 13:10:46 +01:00
Adrian Greeve ca2f0f052c MDL-63868 core_calendar: Clicking link leads to events.
The mini calendar did not link to the daily events properly.
2018-11-13 15:36:17 +08:00
Damyon Wiese 7ef3dbf5f4 Merge branch 'MDL-63927_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-11-13 11:57:55 +08:00
Damyon Wiese f0e03e6918 MDL-63882 core_message: Use validate_parameters
external_api::validate_parameters() sets defaults, and does cleaning and sanitisation of the
inputs. It returns the cleaned params. We can't be re-using the arguments to this function
instead of the results, because they have not been cleaned.
2018-11-13 11:02:40 +08:00
AMOS bot bb56746b61 Automatically generated installer lang files 2018-11-13 00:07:39 +00:00
Andrew Nicols 11a19454d7 Merge branch 'm35_MDL-63953' of https://github.com/danmarsden/moodle into MOODLE_35_STABLE 2018-11-13 07:54:18 +08:00
Eloy Lafuente (stronk7) aa4f226f5c Merge branch 'MDL-63907-35' of git://github.com/cescobedo/moodle into MOODLE_35_STABLE 2018-11-12 12:08:41 +01:00
David Monllao a3c626c7e7 Merge branch 'MDL-63925-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-12 11:57:54 +01:00
David Monllao 8ff8f4e558 Merge branch 'MDL-63940-35-recaptchaurl' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-11-12 10:56:12 +01:00
David Monllao 63510633b4 Merge branch 'MDL-63823-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-11-12 10:09:46 +01:00
Simey Lameze 82447d7ded MDL-63823 tool_lpimportcsv: supress progress bar debugging 2018-11-12 09:53:49 +08:00
Dan Marsden 6dae57a102 MDL-63953 mod_scorm: Use correct value for first attempt. 2018-11-12 10:06:39 +13:00
Eloy Lafuente (stronk7) 789dcd6975 Moodle release 3.5.3 2018-11-10 20:36:27 +01:00
Eloy Lafuente (stronk7) b908052850 weekly release 3.5.2+ 2018-11-10 12:53:24 +01:00
Eloy Lafuente (stronk7) f96287e91b Merge branch 'MDL-63960-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-10 10:28:16 +01:00
Andrew Nicols 55df5e4c05 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) 68987522ea 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:00:46 +08:00
Eloy Lafuente (stronk7) cd496c0307 Merge branch 'MDL-63596-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-10 03:51:34 +01:00
Andrew Nicols 977632b715 MDL-63596 backup: Add missing include 2018-11-10 10:49:19 +08:00
Eloy Lafuente (stronk7) d8231aee5e Merge branch 'MDL-63596-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-09 19:32:53 +01:00
Damyon Wiese d3b5e29e2e MDL-63596 backup: Deal with controller types
For privacy requests, deal with all 3 controller types.
2018-11-09 17:56:05 +08:00
Mihail Geshoski 8f7f422709 MDL-63596 core_backup: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-11-09 17:56:05 +08:00
Andrew Nicols 4c7f930af8 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:36:10 +08:00
cescobedo 7625b00200 MDL-63907 core_message: Pass user object to show_online_status.
Fix create_contact() to pass user object as parameter in show_online_status.
2018-11-09 10:02:13 +01:00
Jun Pataleta df2ed3c8bc Merge branch 'MDL-63867-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-09 15:30:30 +08:00
Michael Hawkins b2e67dc913 MDL-63867 tool_dataprivacy: Add site purpose checks to tasks
If a site purpose is not defined, requests cannot be processed.
2018-11-09 15:21:24 +08:00
Michael Hawkins a000765a13 MDL-63867 tool_dataprivacy: Fix context expired/unprotected user check
It now checks the system context has been defined, since that is
required for data privacy to be set up correctly, and the check
to be valid. This also fixes an error being thrown when checking
pending delete requests in cron.
2018-11-09 15:21:24 +08:00
Jun Pataleta e1921cead6 Merge branch 'MDL-63946-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-09 13:41:33 +08:00
Andrew Nicols a3c30c9fc8 MDL-63946 plagirism: Filename typo fix 2018-11-09 13:25:33 +08:00
Jun Pataleta 3af336b41a Merge branch 'MDL-63924-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-09 11:57:31 +08:00
Jun Pataleta a757b3f80b Merge branch 'MDL-63942-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-09 11:42:50 +08:00
Peter d206c98d98 MDL-62415 repo_filepicker: Display search results in tree view
Disable overriding call to list all files in tree view and show search results instead
2018-11-09 10:54:58 +08:00
Andrew Nicols a2793d3850 Merge branch 'MDL-63919-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-09 10:52:34 +08:00
Jun Pataleta ee522a9f42 Merge branch 'MDL-62589-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-09 10:36:44 +08:00
Damyon Wiese 23c79dff60 Merge branch 'wip-MDL-63894-MOODLE_35_STABLE' of https://github.com/kwiliarty/moodle into MOODLE_35_STABLE 2018-11-09 09:51:51 +08:00
Andrew Nicols b94e3e5f56 MDL-63942 dataprivacy: Disable deleted user task by default 2018-11-09 09:31:16 +08:00
Damyon Wiese f4e1906562 Merge branch 'MDL-63671-35-2' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-09 09:14:06 +08:00
David Mudrák bdbecafdc0 MDL-63940 recaptcha: Use www.recaptcha.net in the service endpoint URL
There are countries where www.google.com is a being blocked and
recaptcha does not work there. As shown at
https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally
it is enough to simply use www.recaptcha.net in the service endpoint
URL.
2018-11-08 22:59:28 +01:00
Eloy Lafuente (stronk7) e1f1cf9bec Merge branch 'MDL-62564-integration-35-1' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-08 16:50:15 +01:00
Andrew Nicols a07e8d7023 MDL-63924 privacy: Add shared user providers to subsytsems 2018-11-08 21:27:22 +08:00
Andrew Nicols a14a44ee13 MDL-63924 core_privacy: Add shared_userlist provider 2018-11-08 21:27:22 +08:00
Eloy Lafuente (stronk7) 763f4e1e52 MDL-63927 performance: Update the plan to support the new login token 2018-11-08 10:26:31 +01:00
Mihail Geshoski 60987ecfae MDL-63671 tool_cohortroles: Limit data to the system context in provider 2018-11-08 16:07:44 +08:00
Michael Hawkins 20957bbd12 MDL-63925 privacy: Fixed Moodle URL in template 2018-11-08 15:54:42 +08:00
Mihail Geshoski 4b56876fe4 MDL-63919 privacy: Store the creation method of the data request 2018-11-08 14:50:08 +08:00
Mihail Geshoski 33ae40340d MDL-63919 privacy: Disable notifications for auto created data requests 2018-11-08 14:50:08 +08:00
Jun Pataleta 923a97629d Merge branch 'MDL-63902-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-08 13:38:54 +08:00
Kevin Wiliarty 37e1f7dbd9 MDL-63894 themes: adds flex-wrap to boost inline date_time_selector 2018-11-07 23:51:41 -05:00
Andrew Nicols 9ae66863bc MDL-62589 dataprivacy: Add ability to resubmit a request 2018-11-08 11:45:35 +08:00
Jun Pataleta 180d76c5f8 Merge branch 'MDL-63840-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-11-08 11:30:54 +08:00
Mihail Geshoski 4913f8f270 MDL-62564 privacy: Create request for deleted users when setting enabled 2018-11-08 09:52:16 +08:00
Mihail Geshoski d364d88a51 MDL-62564 privacy: Improve bulk deletion 2018-11-08 09:51:24 +08:00
Mihail Geshoski fe552e47f0 MDL-62564 privacy: Add unit tests 2018-11-08 09:33:25 +08:00
Andrew Nicols b0ea09e200 MDL-63902 dataprivacy: Check course children not the course
When checking the expiry and protected state of a context, we need to do
so knowing what kind of use that context has.

If it is used in the user context, then only the user context matters.
If it is used within a course, then that child context must be checked
in relation to the course.
2018-11-08 09:13:15 +08:00
Andrew Nicols a1d910f2e8 MDL-62589 dataprivacy: Correct not in sql 2018-11-08 09:03:15 +08:00
Luca Bösch f7dec77e56 MDL-63840 theme: block menu cog size. 2018-11-07 18:03:41 +01:00
Eloy Lafuente (stronk7) f0f4270a8d Merge branch 'MDL-62294-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-11-07 17:25:37 +01:00
Jun Pataleta 1523c8bc6f Merge branch 'm35_MDL-63800' of https://github.com/danmarsden/moodle into MOODLE_35_STABLE 2018-11-07 15:28:07 +08:00
Damyon Wiese 68c72aadc2 Merge branch 'MDL-63839_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-11-07 14:28:46 +08:00
Damyon Wiese 4c21639e66 Merge branch 'MDL-63451_35' of https://github.com/peterspicer-catalyst/moodle into MOODLE_35_STABLE 2018-11-07 13:05:12 +08:00
Jun Pataleta ead79c8627 MDL-63183 auth_shibboleth: Don't render page when already logged in 2018-11-07 00:11:47 +01:00
Jun Pataleta 50edf48571 MDL-63183 auth_shibboleth: Add hidden login token in guest login form 2018-11-07 00:11:47 +01:00
Damyon Wiese b427ab4f70 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-07 00:11:47 +01:00
Eloy Lafuente (stronk7) 15c87e0bb1 Merge branch 'm35_MDL-63789_Search_SOLR_Fixes_Proxy_Auth' of https://github.com/scara/moodle into MOODLE_35_STABLE 2018-11-07 00:08:23 +01:00
Eloy Lafuente (stronk7) 4ead20ffea weekly release 3.5.2+ 2018-11-06 19:06:47 +01:00
Eloy Lafuente (stronk7) f37269b124 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-11-06 19:06:45 +01:00
Andrew Nicols f9de59ada9 Merge branch 'MDL-63711-35-fix' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-11-06 14:20:59 +08:00
Jun Pataleta f5cf23270a MDL-63711 core_grade: Use canonicalize for checking userlist IDs 2018-11-06 14:16:57 +08:00
Andrew Nicols 88c2c6758a Merge branch 'MDL-62563-35-3' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-06 09:47:26 +08:00
AMOS bot 0da99d3c4d Automatically generated installer lang files 2018-11-06 00:07:25 +00:00
Dan Marsden 4aa5cee144 MDL-63800 mod_assign: don't show grade if not set. 2018-11-06 10:04:54 +13:00
David Monllao ad6b515ab6 Merge branch 'MDL-63711-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-11-05 16:36:05 +01:00
Eloy Lafuente (stronk7) 56cc378799 Merge branch 'MDL-63748-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-11-05 13:11:47 +01:00
David Monllao 3b8730628b Merge branch 'MDL-63764-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-05 12:46:35 +01:00
David Monllao ce8ada051d Merge branch 'MDL-63700-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-05 11:14:47 +01:00
Mihail Geshoski d2481db6b0 MDL-62563 privacy: Create delete data request for existing deleted users 2018-11-05 16:24:02 +08:00
Mihail Geshoski e3eb339d89 MDL-62563 privacy: Add unit tests 2018-11-05 16:21:52 +08:00
Andrew Nicols d906136aa5 Merge branch 'MDL-63689-35-workshopprivacy' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-11-05 15:00:40 +08:00
Damyon Wiese c675598ac0 Merge branch 'MDL-63717-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-11-05 13:45:39 +08:00
Andrew Nicols ea321f34c4 Merge branch 'MDL-63564-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-05 13:21:33 +08:00
Mark Nelson bb34fe0838 MDL-63839 core_message: use component name for Behat tag 2018-11-05 10:36:32 +08:00
Andrew Nicols 748409df5b Merge branch 'MDL-63712-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-05 09:34:17 +08:00
Eloy Lafuente (stronk7) d07431a2c6 Merge branch 'MDL-63818-35-2' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-11-05 00:03:17 +01:00
Matteo Scaramuccia 60735e6bee MDL-63789 search_solr: use proxy creds, if any
Also use proxy port, IF any.
2018-11-02 23:34:13 +01:00
Eloy Lafuente (stronk7) 9d870a800d weekly release 3.5.2+ 2018-11-02 21:08:38 +01:00
Roger Barras 82083929b1 MDL-60267 user: Fix set default data for the user profile field 2018-11-02 20:10:31 +01:00
Adrian Greeve 52efa6a2f0 MDL-63711 core_grades: Delete data for specified users in a context. 2018-11-03 02:59:58 +08:00
Juan Leyva 8396ade620 MDL-63748 core_auth: Fix redirect parameter being ignored 2018-11-02 15:23:26 +01:00
Juan Leyva e159265c55 MDL-63717 tool_mobile: Ensure URLs are valid
We need to clean the URLs returned by the get_public_config WS because some old plugins didn't validate the stored data.
2018-11-02 14:35:34 +01:00
Mihail Geshoski 46782ecb13 MDL-63564 core_question: Avoid using UNION in get_users_in_context() 2018-11-02 14:40:38 +08:00
Shamim Rezaie 2a34d4b3fd MDL-63564 mod_quiz: Added delete_quizaccess_data_for_users to polyfill
This issue is a part of the MDL-62560 Epic.
2018-11-02 14:40:38 +08:00
Shamim Rezaie e1dca20127 MDL-63564 mod_quiz: support removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-11-02 14:40:37 +08:00
Shamim Rezaie 73b5e30559 MDL-63564 mod_quiz: Added quizaccess_user_provider interface
This issue is a part of the MDL-62560 Epic.
2018-11-02 14:40:37 +08:00
Shamim Rezaie d7c85ebb3d MDL-63564 question: Add helper to fetch users in context
This issue is a part of the MDL-62560 Epic.
2018-11-02 14:40:37 +08:00
Shamim Rezaie 44451a6a40 MDL-63564 core_question: support removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-11-02 14:40:37 +08:00
Andrew Nicols 9bb62d6cdd Merge branch 'MDL-63701-35-1' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-02 12:03:20 +08:00
Andrew Nicols 13ed41e499 Merge branch 'MDL-63666-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-02 10:52:40 +08:00
Mihail Geshoski 9825658af9 MDL-63666 logging: Rename delete_data_for_all_users() function 2018-11-02 09:42:16 +08:00
Adrian Greeve 2a939b9f7f MDL-63666 logging: Add support for removal of context users.
This creates a new interface for the logging subplugins to
implement for the users to be deleted separately.
2018-11-02 09:42:16 +08:00
Andrew Nicols 57eae451f1 MDL-63842 core_user: Remove unnecessary DB fetch 2018-11-02 09:07:49 +08:00
Andrew Nicols 30d8fed964 Merge branch 'MDL-63703-35-1' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-11-02 09:00:12 +08:00
Andrew Nicols dc23a75d68 MDL-63702 core_block: Correct phpdoc for get_users 2018-11-02 08:34:45 +08:00
Andrew Nicols 78552ea548 MDL-63422 blog: Correct phpdoc for get_users 2018-11-02 08:34:45 +08:00
Andrew Nicols 6404971918 Merge branch 'MDL-63816-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-02 08:00:49 +08:00
Eloy Lafuente (stronk7) 414ca3e1c1 Merge branch 'MDL-63746-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-11-01 15:38:34 +01:00
Andrew Nicols 4d79fb98a5 MDL-63818 core: Add all relevant module context caps
This change ensures that all capabilities at module context level are
shown within a module, only filtering out those which belong to another
module or the subplugins of another module.

This has the effect that module-level capabilities are included
providing they relate to
- the module being queried
- any subplugin of that module
- any capability at module level which does not belong to another module
  or its subplugin
- any other explicitly included capability

This allows permissions to be defined on a capability at a context
level.
2018-11-01 20:52:02 +08:00
Michael Hawkins 199d36febc MDL-63712 core_message: Unit test improvements for fetching context/user
This issue is part of the MDL-62560 Epic.
2018-11-01 16:53:07 +08:00
Mihail Geshoski 3cd0a770d6 MDL-63701 editor_atto: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-11-01 14:21:12 +08:00
Shamim Rezaie 55e5747d04 MDL-63712 core_message: support removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-11-01 13:06:27 +08:00
Shamim Rezaie 3433b0bfb5 MDL-63712 core_message: Data should be in user context, not system
This issue is a part of the MDL-62560 Epic.
2018-11-01 13:05:58 +08:00
Mihail Geshoski 7f4d3aac07 MDL-63703 core_role: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-11-01 12:13:52 +08:00
Damyon Wiese 790235e123 MDL-63746 boost: Dropdown menu styles
Remove btn styling from menu buttons as the additional padding makes things not line up.
2018-11-01 11:05:37 +08:00
Andrew Nicols 0ee1075ec4 Merge branch 'MDL-63566-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-11-01 10:11:57 +08:00
Adrian Greeve f7d57a3d96 MDL-63566 tool_dataprivacy: New interface check for userlist.
This adds another check to see if the plugins are implementing the
new core_userlist_provider.
2018-11-01 10:08:13 +08:00
Jun Pataleta bbb9ffb0c1 Merge branch 'MDL-63826_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-11-01 09:28:30 +08:00
Eloy Lafuente (stronk7) e882740a56 MDL-63826 tests: Don't rely on random array keys for testing 2018-11-01 00:35:21 +01:00
Andrew Nicols 1da0d646b3 Merge branch 'm35_MDL-63727_Wrong_SCO_Launched_Link' of https://github.com/scara/moodle into MOODLE_35_STABLE 2018-11-01 07:01:42 +08:00
Matteo Scaramuccia b193f7754d MDL-63727 mod_scorm: Fixed typo in test_launch_sco() 2018-10-31 22:42:06 +01:00
Davo Smith 73c8d34ab8 MDL-63727 mod_scorm: fix typo in sco_launched URL 2018-10-31 22:42:05 +01:00
David Monllao bf1b7ef8c9 Merge branch 'MDL-63726-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-10-31 10:21:52 +01:00
Luca Bösch 662878b6b5 MDL-63726 tool_dataprivacy: En-/disable 'Data retention summary' link 2018-10-31 09:10:48 +01:00
Andrew Nicols bc35326e3e Merge branch 'MDL-63684-35-1' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 15:13:17 +08:00
Mihail Geshoski df0a4ec014 MDL-63684 core_calendar: Fix test_get_contexts_for_userid() test
This issue is part of the MDL-62560 Epic.
2018-10-31 14:12:08 +08:00
Mihail Geshoski 8782043946 MDL-63684 core_calendar: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-31 14:12:08 +08:00
Andrew Nicols f669a63cca Merge branch 'MDL-63702-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 14:05:39 +08:00
David Monllao a25a4d30be Merge branch 'MDL-62601-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-31 06:47:43 +01:00
Michael Hawkins aae2aa2d81 MDL-63700 core_analytics: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-31 13:47:07 +08:00
Andrew Nicols 1760650216 MDL-62601 core_privacy: Ensure providers can handle deleted users 2018-10-31 13:45:00 +08:00
Andrew Nicols 52a16f409d MDL-62601 editor_atto: Do not use context_user 2018-10-31 13:45:00 +08:00
David Monllao 19768075ff Merge branch 'MDL-63582-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-10-31 06:10:38 +01:00
Jun Pataleta fdc3a96e54 Merge branch 'MDL-63690-35-integration' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 12:53:47 +08:00
Michael Hawkins a25222e9c4 MDL-63816 privacy: Corrected provider get_users_in_context descriptions 2018-10-31 12:14:43 +08:00
Michael Hawkins eaae1a7ea1 MDL-63816 privacy: Corrected interface get_users_in_context description 2018-10-31 12:14:43 +08:00
Mihail Geshoski c81cc39a02 MDL-63690 core_blog: Handle when user blog posts are not being returned 2018-10-31 11:50:35 +08:00
Jun Pataleta a3d372061a Merge branch 'MDL-60897-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-10-31 11:34:47 +08:00
Andrew Nicols 5de480c21b Merge branch 'MDL-63624-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 11:16:47 +08:00
Jun Pataleta 446de56bfc MDL-60897 qtype_multianswer: Code style fixes
And Behat steps improvement, too!
2018-10-31 11:12:53 +08:00
Jean-Michel Vedrine 5e997d239c MDL-60897 qtype_multianswer: Invalid cloze-questions saved to db 2018-10-31 11:12:53 +08:00
Mihail Geshoski cb69e0ca88 MDL-63624 tool_cohortroles: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-31 11:09:44 +08:00
Adrian Greeve 0a0e5a03a0 MDL-63702 core_block: Update to use new userlist interface.
This allows user data to be deleted on a role basis.
2018-10-31 10:05:21 +08:00
Andrew Nicols 12a3bcee4f Merge branch 'MDL-63530-35-2' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 09:22:06 +08:00
Andrew Nicols ddf649109d Merge branch 'MDL-63606-35-2' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 08:56:25 +08:00
Andrew Nicols 18b69cc33d Merge branch 'MDL-63690-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-31 08:23:28 +08:00
Luca Bösch 4283575676 MDL-63582 theme_boost: Fix the license field in the file picker width. 2018-10-30 22:42:33 +01:00
Eloy Lafuente (stronk7) e1e20d0c9b weekly release 3.5.2+ 2018-10-30 18:24:07 +01:00
Eloy Lafuente (stronk7) 009f51644d Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-10-30 18:24:05 +01:00
David Mudrák 5171746d5a MDL-63689 workshop: Add support for removal of multiple context users
The mod_workshop\privacy\provider now implements the new
core_userlist_provider interface. Deleting (erasing) data follows the
same principles as in other existing methods - we do not actually delete
data that might affect other users. Instead, we only erase the sensitive
owned personal information such as provided content.
2018-10-30 14:31:49 +01:00
Mihail Geshoski a395c8a60e MDL-63606 core_badges: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-30 16:42:24 +08:00
Mihail Geshoski 575584cb94 MDL-63530 core_notes: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-30 16:19:26 +08:00
Andrew Nicols 8fe266c09d Merge branch 'MDL-63634-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-30 12:05:15 +08:00
Andrew Nicols a7b6eaa544 Merge branch 'MDL-63626-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-30 10:59:28 +08:00
Jun Pataleta 831b2f6729 Merge branch 'MDL-63531-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-30 10:50:46 +08:00
Adrian Greeve e2f9bdd44b MDL-63531 privacy: Update to comments provider to simplfy sql.
This updates the get_users_in_context_from_sql query to include
the context id to simplify the calls made from other components.
2018-10-30 09:47:14 +08:00
Adrian Greeve 707586883b MDL-63531 assignfeedback: Update to use the new privacy interface.
This now uses the new interface to allow the deletion of users based
on a context.
2018-10-30 09:47:14 +08:00
Adrian Greeve f5029c213a MDL-63531 assignsubmission: Update to use the new interface.
This updates the plugins to use the new inteface for deleting
data for users in a context.
2018-10-30 09:47:14 +08:00
Andrew Nicols b96835a505 Merge branch 'MDL-63656-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-10-30 09:37:07 +08:00
Adrian Greeve 5e0b16e369 MDL-63531 mod_assign: Update mod assign to use new interface.
This introduces a new interface for assign sub-plugins and
updates the mod_assign provider to implement the new general
interface for deleting data for users in a context.
2018-10-30 08:47:41 +08:00
Eloy Lafuente (stronk7) 7b9e3e0336 Merge branch 'MDL-63730-35-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-10-29 13:20:24 +01:00
David Monllao eb5263ec3f Merge branch 'MDL-63528-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-10-29 11:18:21 +01:00
Michael Hawkins ac2524447e MDL-63656 tool_messageinbound: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-29 14:15:11 +08:00
Michael Hawkins ea1f81dd64 MDL-63764 core_competency: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-29 13:01:40 +08:00
Michael Hawkins 95c2cadcb8 MDL-63764 core_competency: Support bulk user delete course competencies
This issue is a part of the MDL-62560 Epic.
Also made user IDs optional, so user course competencies can be deleted by course ID only.
2018-10-29 13:01:40 +08:00
Jun Pataleta 116b0d26eb Merge branch 'MDL-63622-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-10-29 11:41:18 +08:00
Adrian Greeve 07c7af673b MDL-63622 core_grading: Add support for removal of context users.
Core grading now implements the new core_userlist_provider
interface to allow for deletion of specific users in a context.
2018-10-29 11:22:04 +08:00
Andrew Nicols a32ea64a88 Merge branch 'MDL-63665-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-29 08:57:04 +08:00
Andrew Nicols de6f2519a5 Merge branch 'MDL-63680-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-29 08:22:58 +08:00
AMOS bot 6539fe917f Automatically generated installer lang files 2018-10-29 00:07:23 +00:00
Andrew Nicols 23b8d594b5 Merge branch 'MDL-63267-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-10-27 19:20:39 +08:00
AMOS bot 7b1593ad5c Automatically generated installer lang files 2018-10-27 00:07:26 +00:00
Tim Lock b6960fa224 MDL-63267 tool_privacy: Handle modules without privacy support. 2018-10-26 18:31:11 -04:00
Adrian Greeve 5704b265ab MDL-63267 grading methods: Unit test update for fix. 2018-10-26 18:31:31 -04:00
Adrian Greeve a5915ed4fc 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:31:11 -04:00
Adrian Greeve 73a8c336c9 MDL-63267 tool_dataprivacy: Registry shows deprecated interfaces. 2018-10-26 18:26:32 -04:00
Adrian Greeve 710c4de820 MDL-63267 privacy: Create deprecated provider interface.
Any interfaces that have become deprecated should extend
this interface.
2018-10-26 18:26:19 -04:00
Adrian Greeve e6d622106c MDL-63267 core_grading: Unit test for new methods. 2018-10-26 18:26:07 -04:00
Adrian Greeve 9383ee853c MDL-63267 mod_assign: Update to providers to include adv grading. 2018-10-26 18:25:46 -04:00
Adrian Greeve fa92fcbe15 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:24:49 -04:00
Eloy Lafuente (stronk7) f2eb4c0b8f weekly release 3.5.2+ 2018-10-26 18:05:09 +02:00
Eloy Lafuente (stronk7) cc9c1ed9a0 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-10-26 18:05:07 +02:00
Damyon Wiese 76793973e5 MDL-62139 output: Menus fixes
Change the boost custom behat step to click on a button.

Revert the clean templates to unchanged versions.

(it seems that we missed this commit originally in
MOODLE_35_STABLE and it was the cause of hundreds of
failures, so cherry picked from master. More info @ MDL-63765)
2018-10-26 17:40:49 +02:00
David Mudrák d3e89fec58 MDL-63730 lang: Update Behat scenarios to use the new strings 2018-10-26 13:43:17 +02:00
Helen Foster 8757d7cb6b MDL-63730 lang: Merge English strings from the en_fix language pack
Significant string changes:

* withselectedusers_help, core - removing mention of 'Add a common note'
  as the option no longer exists
* considereddigitalminor and digitalminor_desc, core - more
  child-friendly wording
* auth_dbtype, auth_db - fixed broken link
2018-10-26 13:40:07 +02:00
Shamim Rezaie f61b827260 MDL-63634 enrol_paypal: Improve query performance
This issue is a part of the MDL-62560 Epic.
2018-10-26 19:36:39 +11:00
Shamim Rezaie 2f6a91dbdb MDL-63634 enrol_paypal: support removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-10-26 19:36:39 +11:00
Shamim Rezaie f9ba909869 MDL-63634 enrol_paypal: Fix a typo in unit tests
This issue is a part of the MDL-62560 Epic.
2018-10-26 19:36:39 +11:00
Eloy Lafuente (stronk7) 5b8e3123a9 Merge branch 'MDL-63765-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-26 10:22:36 +02:00
Damyon Wiese eaf9da10ad MDL-63765 behat: Fix behat tests opening a menu
If javascript is disabled, we don't need to open the menus at all. If it is enabled
we should use the custom step where possible.

In some tests (user profile) we have to use link_or_button because
the element that opens the menu will be a link or a button in different themes.
2018-10-26 16:12:00 +08:00
Shamim Rezaie 0f9997a4ec MDL-63665 mod_choice: no exception when course_modules doesn't exist
This issue is a part of the MDL-62560 Epic.
2018-10-26 18:49:35 +11:00
Shamim Rezaie 591b16786c MDL-63665 mod_choice: support removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-10-26 18:49:35 +11:00
Mihail Geshoski b1208d8bf8 MDL-63680 report_stats: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-26 13:09:10 +08:00
Andrew Nicols f4fcee9a9d Merge branch 'MDL-63673-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-26 11:42:10 +08:00
Andrew Nicols d320454567 Merge branch 'MDL-63682-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-26 11:03:09 +08:00
Mihail Geshoski 24eeb8ba01 MDL-63682 mnetservice_enrol: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-26 10:24:08 +08:00
Andrew Nicols 0feb84a5dd Merge branch 'MDL-63688-35-1' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-26 10:17:36 +08:00
Mihail Geshoski 90be3a42f3 MDL-63690 core_privacy: Handle when add_users() receives an empty array 2018-10-26 09:26:55 +08:00
Adrian Greeve 2b227258e0 MDL-63690 core_blog: Update providers for userlist deletion. 2018-10-26 09:26:31 +08:00
AMOS bot b6cfa1975a Automatically generated installer lang files 2018-10-26 00:07:27 +00:00
Eloy Lafuente (stronk7) 8b4a53ca75 Merge branch 'MDL-62904-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-10-26 00:03:53 +02:00
Andrew Nicols 7de43df7ad Merge branch 'MDL-63552-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-10-25 15:44:10 +08:00
Andrew Nicols 497d75ae71 Merge branch 'MDL-63679-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-25 15:13:15 +08:00
Andrew Nicols b9c01f1f70 Merge branch 'MDL-63678-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-25 15:11:53 +08:00
Andrew Nicols 3cf54a0743 Merge branch 'MDL-63655_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-10-25 14:56:58 +08:00
Michael Hawkins d7bd4532f8 MDL-63552 search_solr: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-25 11:43:21 +08:00
Michael Hawkins 1ba678ef3b MDL-63552 search_simpledb: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
Also added test_get_contexts_for_userid unit test.
2018-10-25 11:43:21 +08:00
Damyon Wiese 062bc628df Merge branch 'MDL-63755-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-25 08:37:51 +08:00
Andrew Nicols 412b9c0451 MDL-63755 theme_boost: Behat selector fix 2018-10-25 07:39:27 +08:00
Eloy Lafuente (stronk7) 5c61e693af Merge branch 'MDL-62145-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-25 01:35:21 +02:00
Eloy Lafuente (stronk7) de2b3bfdd8 Merge branch 'MDL-63714-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-25 01:08:38 +02:00
Mihail Geshoski 46120189e3 MDL-63688 core_tag: Add method that returns users in context
This issue is part of the MDL-62560 Epic.
2018-10-24 09:28:00 +08:00
Andrew Nicols 8f30163736 MDL-63619 tool_dataprivacy: Cache purposes in form 2018-10-24 08:49:24 +08:00
Andrew Nicols 62b97d84ee MDL-63619 tool_dataprivacy: Performance improvement 2018-10-24 08:49:24 +08:00
Andrew Nicols 899e07d9a8 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:49:24 +08:00
Andrew Nicols c85128260f MDL-63714 javascript: Add new core/pending module 2018-10-24 08:43:50 +08:00
Andrew Nicols 527f895e93 MDL-63714 core: Wrap doRender in pendingjs 2018-10-24 08:43:50 +08:00
Andrew Nicols abca4753b4 MDL-63714 javascript: Improve docs for pendingjs 2018-10-24 08:43:50 +08:00
Mihail Geshoski 4b91080095 MDL-63673 fileconverter_googledrive: Support removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-24 07:03:19 +08:00
David Monllao 76528a767e weekly release 3.5.2+ 2018-10-23 16:39:30 +02:00
David Monllao 16b34d53f2 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-10-23 16:39:28 +02:00
David Monllao 25934551cc Merge branch 'MDL-62139-35-fix3' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-23 11:17:08 +02:00
Damyon Wiese 4b31593964 MDL-62139 behat: Invalid test
If javascript is disabled, we can't open the menu.
2018-10-23 16:44:16 +08:00
Damyon Wiese 9c9e2d3e5e MDL-62139 output: Revert template changes
Revert accessibility changes for menus in standard themes.
2018-10-23 10:14:44 +02:00
Damyon Wiese cd786f0d0d MDL-62139 output: Revert template changes
Revert accessibility changes for menus in standard themes.
2018-10-23 15:47:20 +08:00
Mihail Geshoski 58d63790c2 MDL-63679 ltiservice_memberships: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-23 13:36:02 +08:00
Mihail Geshoski 00a7f99146 MDL-63678 ltiservice_gradebookservices: Support removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-23 13:13:48 +08:00
Andrew Nicols 6d0014c5e1 MDL-63657 completion: Coding style fix 2018-10-23 11:32:53 +08:00
Andrew Nicols 85f052f4b8 Merge branch 'MDL-63668-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-10-23 11:32:49 +08:00
Andrew Nicols 17f5816739 MDL-63657 core_files: Coding style fix 2018-10-23 11:27:26 +08:00
Andrew Nicols 9cb3947dff Merge branch 'MDL-63640-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-23 11:27:22 +08:00
Andrew Nicols efc90f828b MDL-63657 tool_mobile: Coding style fix 2018-10-23 11:22:05 +08:00
Andrew Nicols da5827b559 Merge branch 'MDL-63657-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-10-23 11:21:59 +08:00
Andrew Nicols 9f01d23302 MDL-63535 core_rss: Coding style fix 2018-10-23 11:16:56 +08:00
Andrew Nicols a4f148707e Merge branch 'MDL-63535-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-10-23 11:16:45 +08:00
Michael Hawkins 7d08cc710b MDL-63657 tool_mobile: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-23 10:59:38 +08:00
Andrew Nicols 79a266ee48 MDL-63664 tool_policy: Whitespace fix 2018-10-23 10:54:13 +08:00
Michael Hawkins ad33de11d9 MDL-63668 core_course: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-23 10:45:36 +08:00
Michael Hawkins dd897cc398 MDL-63668 core_completion: Add get and delete by approved userlist.
This issue is a part of the MDL-62560 Epic.
2018-10-23 10:45:36 +08:00
Andrew Nicols 68f64fe990 Merge branch 'MDL-63664-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-10-23 10:44:35 +08:00
Michael Hawkins a7ebe98214 MDL-63664 tool_policy: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-23 10:40:12 +08:00
Mihail Geshoski 8ac3bac143 MDL-63535 core_userkey: Add method that returns users in context
This issue is part of the MDL-62560 Epic.
2018-10-23 10:27:42 +08:00
Mihail Geshoski 4910753dce MDL-63535 core_rss: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-23 10:27:42 +08:00
Andrew Nicols d54cbaff46 Merge branch 'MDL-63649-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-23 10:17:50 +08:00
Andrew Nicols 9e715a225c Merge branch 'MDL-63713-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-23 10:09:13 +08:00
Mihail Geshoski 015e70cbcf MDL-63640 core_files: Add method that returns users in context
This issue is part of the MDL-62560 Epic.
2018-10-23 10:07:38 +08:00
Shamim Rezaie 0b28290a50 MDL-63649 enrol_lti: support removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-10-23 13:06:33 +11:00
Shamim Rezaie 63e4e0d69d MDL-63713 core: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-23 13:05:36 +11:00
Andrew Nicols aff8604240 MDL-62560 tool_dataprivacy: Behat fix for protected check 2018-10-23 09:22:05 +08:00
AMOS bot 505851f045 Automatically generated installer lang files 2018-10-23 00:07:22 +00:00
David Monllao 5cccfd2eab Merge branch 'MDL-62144-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-22 16:34:37 +02:00
David Monllao f9f8edce1d Merge branch 'MDL-61052-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-22 14:37:33 +02:00
David Monllao a9354dbbd6 Merge branch 'MDL-62560-35' into MOODLE_35_STABLE 2018-10-22 12:58:00 +02:00
Andrew Nicols 05b441c5ef MDL-62560 tool_dataprivacy: Add a purpose override cache 2018-10-22 12:57:08 +02:00
Andrew Nicols 9fc1390e35 MDL-62560 tool_dataprivacy: Fetch user purpose once 2018-10-22 12:57:07 +02:00
Andrew Nicols 360b7f43b8 MDL-62560 tool_dataprivacy: Add status logging 2018-10-22 12:57:07 +02:00
Andrew Nicols 20342fca7e MDL-62560 tool_dataprivacy: Add unit test for filtered userlist 2018-10-22 12:57:06 +02:00
Mihail Geshoski 598d0b3c20 MDL-63636 block_recent_activity: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:06 +02:00
Mihail Geshoski 3d9de9c6d6 MDL-63621 core_cohort: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:05 +02:00
Mihail Geshoski cdd2633a99 MDL-63633 block_comments: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:05 +02:00
Mihail Geshoski 7027daa89c MDL-63635 block_community: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:04 +02:00
Mihail Geshoski bd77a90267 MDL-63593 auth_mnet: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:04 +02:00
Mihail Geshoski 3288990694 MDL-63592 auth_oauth2: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:04 +02:00
Mihail Geshoski 64793424f2 MDL-63637 block_rss_client: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:03 +02:00
Mihail Geshoski 56cd4f7b84 MDL-63638 block_tag_flickr: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:03 +02:00
Mihail Geshoski fdf6d94a1b MDL-63639 core_fileconverter: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:02 +02:00
Shamim Rezaie 84a98a4202 MDL-63647 enrol_flatfile: Improving existing unit tests
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:57:02 +02:00
Shamim Rezaie f38e592d5d MDL-63647 enrol_flatfile: support removal of multiple users in context
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:57:01 +02:00
Michael Hawkins 25b6c54c68 MDL-63659 tool_monitor: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:57:01 +02:00
Mihail Geshoski 49f281ac53 MDL-63615 message_airnotifier: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:00 +02:00
Mihail Geshoski e16d2a8fc6 MDL-63616 message_email: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:57:00 +02:00
Mihail Geshoski 5447955b2e MDL-63617 message_jabber: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:59 +02:00
Adrian Greeve b1a8e921cd MDL-63618 core_plagiarism: Added support to remove users in a context.
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:55 +02:00
Mihail Geshoski 97a6d18372 MDL-63572 profilefield_checkbox: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:54 +02:00
Mihail Geshoski c2c4125f13 MDL-63575 profilefield_datetime: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:54 +02:00
Mihail Geshoski b0a2591274 MDL-63586 profilefield_menu: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:53 +02:00
Mihail Geshoski 6cbefb4064 MDL-63587 profilefield_text: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:53 +02:00
Mihail Geshoski 35fbfe23b5 MDL-63588 profilefield_textarea: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:53 +02:00
Michael Hawkins 053eb1d5b9 MDL-63538 repository_onedrive: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:52 +02:00
Michael Hawkins 5b8a649d4a MDL-63538 repository_youtube: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:52 +02:00
Michael Hawkins 29f71e53b5 MDL-63538 repository_wikimedia: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:51 +02:00
Michael Hawkins 5f6e9d5185 MDL-63538 repository_picasa: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:51 +02:00
Michael Hawkins 290c9bc478 MDL-63538 repository_merlot: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:51 +02:00
Michael Hawkins 58cc3aa1ab MDL-63538 repository_googledocs: Support removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:50 +02:00
Michael Hawkins 658b6742f5 MDL-63538 repository_flickr_public: Support removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:50 +02:00
Michael Hawkins 32a103cd69 MDL-63538 repository_flickr: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:49 +02:00
Michael Hawkins 88c29f7fbe MDL-63538 repository_dropbox: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:49 +02:00
Michael Hawkins 7902349b06 MDL-63538 repository_boxnet: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:49 +02:00
Mihail Geshoski 0857000602 MDL-63536 core_repository: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:48 +02:00
Mihail Geshoski ce55556b2e MDL-63534 core_user: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:48 +02:00
Mihail Geshoski ddcf2245a8 MDL-63532 core_portfolio: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:47 +02:00
Mihail Geshoski 39a072f09f MDL-63529 core_enrol: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:47 +02:00
Michael Hawkins d233c5d063 MDL-63514 mod_wiki: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:47 +02:00
Michael Hawkins f27a271a8e MDL-63510 mod_survey: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:46 +02:00
Michael Hawkins 38839b0afb MDL-63501 mod_scorm: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:46 +02:00
Michael Hawkins 07606ab87f MDL-63497 mod_lesson: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:45 +02:00
Michael Hawkins 9143b3db12 MDL-63497 mod_feedback: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:45 +02:00
Michael Hawkins 063f886160 MDL-63497 mod_glossary: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
Also added missing ratings include and test to mod_glossary unit tests.
2018-10-22 12:56:45 +02:00
Michael Hawkins e43fcd4373 MDL-63497 mod_data: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:44 +02:00
Michael Hawkins 4369b8d6e5 MDL-63497 block_html: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:43 +02:00
Michael Hawkins 7cb0129e59 MDL-63497 mod_chat: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:43 +02:00
Shamim Rezaie 80ead07e28 MDL-63554 cachestore_memcache: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:43 +02:00
Shamim Rezaie b6a5ae21c7 MDL-63554 cachestore_session: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:42 +02:00
Shamim Rezaie 22b0849f45 MDL-63554 cachestore_redis: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:42 +02:00
Shamim Rezaie 73a5fcacf6 MDL-63554 cachestore_mongodb: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:41 +02:00
Shamim Rezaie 25b3094acb MDL-63554 cachestore_memcached: Support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:41 +02:00
Shamim Rezaie 7fb4b0d52d MDL-63500 enrol_cohort: refactoring get_contexts_for_userid
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:40 +02:00
Shamim Rezaie 86646b27d2 MDL-63500 enrol_meta: refactoring get_contexts_for_userid
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:40 +02:00
Shamim Rezaie 1afe64dbea MDL-63500 core_group: get_contexts_for_userid should respect component
The get_contexts_for_userid method should not return contexts for all group memberships.
It should only return the contexts in where there is manual group
membership.

This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:40 +02:00
Shamim Rezaie 56c4024330 MDL-63500 enrol_cohort: Support for removal of multiple context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:39 +02:00
Shamim Rezaie 7bbf87a853 MDL-63500 enrol_meta: Support for removal of multiple context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:39 +02:00
Shamim Rezaie c55fd56b87 MDL-63500 core_group: support removal of multiple users in a context
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:38 +02:00
Mihail Geshoski 39e35674d1 MDL-63533 core_webservice: Unit tests for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:38 +02:00
Mihail Geshoski 6f3c6cf4ba MDL-63533 core_webservice: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:37 +02:00
Adrian Greeve 8159f6ae0a MDL-63513 mod_assignment: Add removal of context users.
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:37 +02:00
Michael Hawkins 6cd1ea6cf3 MDL-63498 mod_lti: Add support for removal of context users
This issue is part of the MDL-62560 Epic.
2018-10-22 12:56:36 +02:00
Michael Hawkins 4daf168de7 MDL-63495 comment: Added provider::get_users_in_context_from_sql
This issue is a part of the MDL-62560 Epic.
2018-10-22 12:56:28 +02:00
Damyon Wiese 3bea0fa387 MDL-61052 assign: Do not queue conversions
Identical submissions do not require individual conversions from the document converter. Text passing through
format_text cannot be trusted and is likely to be unique each time, even for the same text.
2018-10-22 12:01:06 +08:00
Zig Tan 6d03e5e554 MDL-61052 assign: Improve strip_images() to avoid HTML5 tags errors 2018-10-22 11:58:36 +08:00
Damyon Wiese 9d0c5cbbc4 MDL-62145 theme_boost: Custom menu fixes
Make sure the site navigation element is labelled, actionmenu items
have correct role and actionmenu trigger controls the menu.
2018-10-22 11:03:37 +08:00
Damyon Wiese 40a90154ca MDL-62144 user menu: Accessibility
Ensure user menu passes the accessibility tests.
2018-10-22 10:45:20 +08:00
Damyon Wiese e0f64e0feb MDL-62139 output: Accessible action menus
Add label on the "gear" menu and default keyboard and focus controls.
2018-10-22 10:37:25 +08:00
Eloy Lafuente (stronk7) 1a33d0869a weekly release 3.5.2+ 2018-10-22 10:37:24 +08:00
AMOS bot f2211eb823 Automatically generated installer lang files 2018-10-22 10:37:24 +08:00
AMOS bot 857cfb1429 Automatically generated installer lang files 2018-10-22 10:37:24 +08:00
AMOS bot bd6cc5c145 Automatically generated installer lang files 2018-10-22 00:07:21 +00:00
Eloy Lafuente (stronk7) 1a1d03caeb weekly release 3.5.2+ 2018-10-19 17:10:56 +02:00
Eloy Lafuente (stronk7) 7e3eaa5d6f Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-10-19 17:10:54 +02:00
AMOS bot 512091f40c Automatically generated installer lang files 2018-10-19 00:08:10 +00:00
Eloy Lafuente (stronk7) 722f0c03c0 Merge branch 'MDL-62920_35' of https://github.com/aanabit/moodle into MOODLE_35_STABLE 2018-10-18 23:51:47 +02:00
Eloy Lafuente (stronk7) 892e441634 Merge branch 'MDL-60145-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-10-18 18:57:45 +02:00
Tim Hunt e24dfc1a07 MDL-63403 filter_glossary: changes made during integration review 2018-10-18 18:46:41 +02:00
Tim Hunt 44d00da135 MDL-63403 filter_glossary: only create replacement HTML if needed 2018-10-18 18:41:42 +02:00
Tim Hunt de736d0c84 MDL-63403 filter_glossary: only prepare the replacement if used 2018-10-18 18:41:38 +02:00
Tim Hunt e78bd0cefb MDL-63403 filter_glossary: clean up filterobject class
This is another preliminary commit, rationalising the ->work... fields
and also moving the code that sets them up into a separate function.
This is also to prepare for the next commit.
2018-10-18 18:41:34 +02:00
Tim Hunt e208669c64 MDL-63403 filter_glossary: only prepare_phrases_for_filtering once 2018-10-18 18:41:30 +02:00
Tim Hunt 098d616a30 MDL-63403 filter_glossary: use preg features for fullmatch
This eliminates a lot of necessary string manipulation for something
that preg can just do with the \b assertion.

The change also extracts all the work to prepare ->work_phrase (renamed
->regexp) into a separate function. This is to pave the way for future
efficiency gains, but for now I have not done them, so it is easier to
verify by inspection that this commit does not change behaviour.

Also, some tidy-ups to the filterobject structure, to eliminate some
redundant fields.
2018-10-18 18:41:26 +02:00
Tim Hunt 995d434de1 MDL-63403 filter_glossary: cache concept list in a MUC static cache
This is a slight improvement on a suggestion by David Monllaó
2018-10-18 18:41:22 +02:00
Tim Hunt 366a85cbb4 MDL-63403 filter_glossary: Fix lang string concatenation 2018-10-18 18:41:18 +02:00
Tim Hunt 0c94037196 MDL-63403 filter_glossary: $GLOSSARY_EXCLUDEENTRY shouldn't stop caching
Also, greatly improved unit tests, to test more cases of how the filter
should work.
2018-10-18 18:41:14 +02:00
Tim Hunt fa0a861400 MDL-63403 filterlib: Fix lots of coding style 2018-10-18 18:40:50 +02:00
Jun Pataleta 068c66f242 Merge branch 'MDL-63667-35-fix2' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-18 14:38:26 +08:00
Andrew Nicols b26c30d8f9 MDL-63667 amd: Check for parent template recursion 2018-10-18 12:11:52 +08:00
Damyon Wiese ac1c36c728 MDL-63667 output: Fix for recursive templates
A template can include itself - e.g. by looping over the context children and rendering a tree.

Ensure this promise still resolves.

Example: admin/tool/lp/templates/competencies_tree.mustache.
2018-10-18 10:52:06 +08:00
Eloy Lafuente (stronk7) 069dfe5fc3 Merge branch 'MDL-63632-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-18 02:39:24 +02:00
AMOS bot b1904ba9cb Automatically generated installer lang files 2018-10-18 00:07:26 +00:00
Andrew Nicols 951ae21309 MDL-63632 forum: Delete where not discussion author 2018-10-18 07:20:29 +08:00
Eloy Lafuente (stronk7) b55b2e4f6e Merge branch 'MDL-63068_35' of https://github.com/aanabit/moodle into MOODLE_35_STABLE 2018-10-18 00:39:55 +02:00
Jun Pataleta b7d2f7e1d4 MDL-62904 tool_dataprivacy: Add data request links in profile page 2018-10-17 16:43:31 +08:00
Jun Pataleta d4a5339029 Merge branch 'MDL-63667-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-17 15:06:01 +08:00
Ryan Wyllie 6185c3455a MDL-63667 javascript: fix failed to pre-fetch the template error 2018-10-17 13:53:20 +08:00
Michael Hawkins 44b012e5fd MDL-63495 comment: Added provider::delete_comments_for_users
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Andrew Nicols b8558f33aa MDL-63495 mod_forum: Add intial support for removal of multiple context users
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Andrew Nicols 9674218469 MDL-63495 core_rating: Add helper to fetch users in context
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Andrew Nicols e3ddf1e324 MDL-63496 tool_dataprivacy: Add configuration for per-role retention
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Andrew Nicols f30b061f48 MDL-63496 tool_dataprivacy: Respect expiry with protected flag
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Andrew Nicols 7f7e78269f MDL-63496 tool_dataprivacy: Support for per-role retention
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Michael Hawkins 744ee9322c MDL-63497 tool_dataprivacy: Add support for removal of context users
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Andrew Nicols bdf43538fb MDL-63495 privacy: Add support for removal of multiple users in a context
This issue is a part of the MDL-62560 Epic.
2018-10-17 13:13:49 +08:00
Eloy Lafuente (stronk7) 0bf20c560e weekly release 3.5.2+ 2018-10-16 16:05:38 +02:00
Damyon Wiese 7817df3048 Merge branch 'MDL-62960-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-16 11:43:53 +08:00
Shamim Rezaie 8baf412ef7 MDL-62960 calendar: Visuall indicator for valid course event drop zones 2018-10-16 13:09:33 +11:00
Shamim Rezaie 86eff20271 MDL-62960 calendar: Rename get_module_timestamp_min/max_limit methods
The methods get_module_timestamp_min_limit() and get_module_timestamp_max_limit()
are doing generic stuff and can be used for other calendar event types as well.
I have renamed them to get_timestamp_min_limit() and get_timestamp_max_limit() respectively.
2018-10-16 13:09:26 +11:00
Shamim Rezaie 81e11e98f7 MDL-62960 calendar: Implementing course events validity check callback 2018-10-16 13:04:05 +11:00
Shamim Rezaie 4dee91aabf MDL-62960 calendar: Added range check callback for course events
Added core_calendar_get_valid_event_timestart_range callback for course events
2018-10-16 13:04:05 +11:00
Andrew Nicols 5c74c84c9d Merge branch 'MDL-63421_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-10-16 08:50:22 +08:00
Eloy Lafuente (stronk7) 8c682cd317 Merge branch 'MDL-63583-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-10-16 00:29:09 +02:00
Eloy Lafuente (stronk7) 0603eabe6e Merge branch 'MDL-63456-35' of git://github.com/merrill-oakland/moodle into MOODLE_35_STABLE 2018-10-16 00:17:13 +02:00
Eloy Lafuente (stronk7) 5ffe934241 Merge branch 'MDL-62319-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-16 00:03:04 +02:00
Andrew Nicols 2816fa5725 Merge branch 'MDL-63542-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-10-15 11:32:50 +08:00
Eloy Lafuente (stronk7) f29fda6b3e MDL-63655 core: Fix miss-placed parenthesis 2018-10-15 01:02:51 +02:00
Amaia Anabitarte de103e2329 MDL-63068 user: Allow underscore for profile custom fields shortname 2018-10-11 16:41:10 +02:00
Amaia Anabitarte 8b32d682a5 MDL-62920 user: Uploading deleted user and adding to a cohort 2018-10-11 16:28:18 +02:00
Shamim Rezaie 117006e9be MDL-63626 mod_quiz: Fixed a bug when there was no attempt on the quiz 2018-10-12 01:04:01 +11:00
Eloy Lafuente (stronk7) 18c5565a1e weekly release 3.5.2+ 2018-10-11 13:22:56 +02:00
Eloy Lafuente (stronk7) 60c2d866de Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-10-11 13:22:54 +02:00
Damyon Wiese 6324e6cbdf MDL-62319 assignfeedback_editpdf: Disable touch scrolling
Assignment editpdf canvas provides it's own scroll tool and the native browser one breaks
the rest of the tools for the canvas. Turn it off.
2018-10-11 13:31:27 +08:00
Nathan Nguyen 107a70c18c MDL-62319 Assign: Grading interface is broken when using iOS 11.3 devices 2018-10-11 13:31:23 +08:00
AMOS bot 38c7b40532 Automatically generated installer lang files 2018-10-11 00:07:18 +00:00
Eloy Lafuente (stronk7) d19ca2c152 Merge branch 'MDL-62891-35-callable_name' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-10-10 23:33:11 +02:00
Eloy Lafuente (stronk7) d61e7f1e3f Merge branch 'MDL-63225-35' of git://github.com/jleyva/moodle into MOODLE_35_STABLE 2018-10-10 23:25:52 +02:00
Peter Spicer 7331c8af56 MDL-63451 core_message: Properly escape URLs for notification popup 2018-10-10 13:04:24 +01:00
Jake Dallimore 9477b1d568 MDL-60145 course: fix bug with group restictions button during mod edit
If a mod doesn't support groups, then we remove the grouping element,
and now also the 'add group/group restriction' helper button too.
2018-10-10 08:42:07 +08:00
AMOS bot 38f9254cd4 Automatically generated installer lang files 2018-10-10 00:07:15 +00:00
Eloy Lafuente (stronk7) 6e5c713b60 Merge branch 'MDL-63439-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-10 00:48:20 +02:00
Eloy Lafuente (stronk7) 9ab9aa573b Merge branch 'MDL-62395-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-10-09 23:06:36 +02:00
Eloy Lafuente (stronk7) 1ca1b28e52 Merge branch 'MDL-63401-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-09 11:36:26 +02:00
David Monllao 5f13e8876b Merge branch 'MDL-62738_35' of https://github.com/mfabriczy/moodle into MOODLE_35_STABLE 2018-10-09 09:24:17 +02:00
David Monllao a16f2c8b55 Merge branch 'MDL-51969-MOODLE_35_STABLE' of https://github.com/dmitriim/moodle into MOODLE_35_STABLE 2018-10-09 09:21:08 +02:00
Jun Pataleta 3addb66f2d Merge branch 'MDL-62491-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-10-09 14:39:41 +08:00
Jun Pataleta 505a25b403 Merge branch 'MDL-63241-35-take2' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-10-09 12:48:48 +08:00
Adrian Greeve bb76ffff21 MDL-62491 navigation: Update to tree.js to allow callbacks. 2018-10-09 09:40:06 +08:00
Adrian Greeve 375eb5e975 MDL-62491 mod_assign: Fill in the rewrite plugin urls properly. 2018-10-09 09:40:06 +08:00
Adrian Greeve 1db088f085 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 09:40:06 +08:00
Damyon Wiese 100e479daf MDL-63439 assign: Unicode charsets for pdf
Prefer to use freesans font for generated pdfs as it has the biggest charset support.
2018-10-09 09:30:24 +08:00
Jun Pataleta 191d470949 Merge branch 'MDL-63194_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-10-09 08:55:39 +08:00
Andrew Nicols c62c4c9c40 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 ac4f0d0e51 MDL-63401 tool_dataprivacy: Simplify where processing 2018-10-09 08:22:42 +08:00
Andrew Nicols 76075bcf6b MDL-63401 tool_dataprivacy: Move final cap check to endpoint 2018-10-09 08:22:42 +08:00
Eric Merrill f55f89162d MDL-63456 qtype_multichoice: Return a default options object if missing 2018-10-08 16:31:27 -04:00
Eric Merrill 2d5d5d7902 MDL-63456 question: Improve Aiken error handling and multichoice errors 2018-10-08 16:31:27 -04:00
Tim Hunt a83a1d9ee0 MDL-63194 quiz editing: subtle drag-drop bug if moving between sections 2018-10-08 17:46:32 +01:00
Juan Leyva aba0de88be MDL-63583 course: Include course/lib.php always
core_course_get_courses_by_field WS must include always course/lib.php to not depend on functions including it.
2018-10-08 16:56:00 +02:00
Andrew Nicols a996aa4ea6 MDL-63401 tool_dataprivacy: Allow expiriration of users without end date 2018-10-08 20:51:41 +08:00
Andrew Nicols 3a6fe7db1b 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:51:09 +08:00
Andrew Nicols cfb272d0ae 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:51:09 +08:00
Andrew Nicols 0de0739e8e MDL-63401 tool_dataprivacy: Move cap checks to endpoints from API 2018-10-08 20:51:09 +08:00
David Monllao 593d8d250f Merge branch 'MDL-63323-35-wrong-classes-attribute-into-activity_navigation' of https://github.com/davidherney/moodle into MOODLE_35_STABLE 2018-10-08 11:30:21 +02:00
Luca Bösch b0147a4737 MDL-63241 calendar: Add missing table alias in SQL query. 2018-10-07 21:45:22 +02:00
Marcus Fabriczy 975c91dec8 MDL-62738: editor_tinymce: Fix call stack size bug on Safari
The call stack size was being exceeded via a recursive loop brought
by MDL-61189.

To fix, this item removes MDL-36803 and MDL-41328 as the iOS keyboard works
nicely now on Safari with TinyMCE.
2018-10-06 17:29:26 +09:30
Juan Leyva 5166e72190 MDL-63542 core_course: Support stealth activities in WS 2018-10-05 14:51:19 +02:00
Eloy Lafuente (stronk7) 8946ee7937 MDL-63421 env: Moodle 3.4.x and 3.5.x do not support PHP 7.3 2018-10-05 13:34:29 +02:00
David Mudrák 4edb56a2a5 MDL-62891 core: Stop using var_export() to describe callables
Make use of the newly added function get_callable_name() when reporting
that an exception happened during shutdown.
2018-10-05 08:51:56 +02:00
David Mudrák 02459b6527 MDL-62891 core: Introduce new get_callable_name() function 2018-10-05 08:51:56 +02:00
Dmitrii Metelkin f9f8a9575e MDL-51969 mod_lti: Fixed missing tool proxy GUID 2018-10-05 15:14:54 +10:00
Jun Pataleta 7daf207db7 weekly release 3.5.2+ 2018-10-05 10:29:07 +08:00
Jun Pataleta 41ae59a8de Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-10-05 10:29:06 +08:00
Jun Pataleta 20d47f1b5f 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:21:22 +08:00
Jun Pataleta a779b53b2f MDL-62395 bootstrapbase: Add more w-XX classes for BS4 compatibility
I added w-25, w-50 and w-75.
2018-10-05 09:21:22 +08:00
Jun Pataleta 7e0e5b02c5 MDL-62395 tool_dataprivacy: Use BS4 table classes
* Use standard Bootstrap4 table classes for the categories and purposes
  tables.
* Set w-25 for the name and description columns of the purposes table.
* Set w-50 for the description column of the categories table.
* Set a minimum width for the actions column of these tables.
2018-10-05 09:21:22 +08:00
Jun Pataleta 054937e378 Merge branch 'MDL-35788-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-04 12:45:43 +08:00
Jun Pataleta a8e4094570 MDL-63528 auth: Behat scenario for "Remember username" display 2018-10-04 12:24:39 +08:00
Jun Pataleta 78cee5d64c MDL-63528 auth: Show "Remember username" on login form only when needed 2018-10-04 11:43:22 +08:00
Andrew Nicols 4d5cb2751b MDL-35788 mod_forum: Move post creation fetch 2018-10-04 11:41:48 +08:00
AMOS bot 9890340b21 Automatically generated installer lang files 2018-10-04 00:07:22 +00:00
Eloy Lafuente (stronk7) 9cd03b8b63 Merge branch 'MDL-63141-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-04 00:24:21 +02:00
Eloy Lafuente (stronk7) 15ce896d08 Merge branch 'MDL-63142-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-04 00:18:45 +02:00
Eloy Lafuente (stronk7) 2d6d31fcb7 Merge branch 'MDL-63143-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-10-04 00:02:34 +02:00
David Monllao 44c460c6be Merge branch 'MDL-62558-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-10-03 17:16:01 +02:00
Adrian Greeve 8855e0041d MDL-62558 tool_dataprivacy: Create a summary page for users. 2018-10-03 10:38:12 +08:00
Jun Pataleta 1209d00d26 Merge branch 'MDL-35788-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-10-03 10:13:08 +08:00
Eloy Lafuente (stronk7) 084180ba7d Merge branch 'MDL-60685-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-10-03 00:24:08 +02:00
Eloy Lafuente (stronk7) 0a11a740b3 Merge branch 'MDL-63435-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-10-02 23:48:17 +02:00
Juan Leyva 86196f93fb MDL-63225 webservice: Return proper debuginfo on WS exceptions
In order to make developers easy, we should give some clues about the type of the data generating exceptions.
2018-10-02 13:15:24 +02:00
Andrew Nicols 7a2eac678c Merge branch 'MDL-63493-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-10-02 11:41:36 +08:00
Jun Pataleta 9af75abf69 MDL-63493 core_message: Fix field parameters for the index definitions
* The fields for the ID number-related indexes that were created for
  the messages and message_user_actions tables in MDL-36941 should not
  be in comma-separated strings.
2018-10-02 11:34:06 +08:00
Andrew Nicols 1c6390697e MDL-35788 forum: Show post creation time not modified 2018-10-01 08:16:56 +08:00
David Herney 9b40d154ff MDL-63323 course: Fixed wrong classes key in activity_navigation class
Replaced "classes" by "class" attribute, when the prev and next links are builded into the activity navigation.
2018-09-27 18:42:50 -05:00
Luca Bösch aae67cd6dd MDL-63435 gradebook: let activity icons re-appear. 2018-09-27 18:17:50 +02:00
Eloy Lafuente (stronk7) 5211af1658 weekly release 3.5.2+ 2018-09-27 15:18:00 +02:00
Eloy Lafuente (stronk7) 663de3c3e6 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-09-27 15:17:57 +02:00
Eloy Lafuente (stronk7) b469c777ed Merge branch 'MDL-63113-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-09-26 12:28:12 +02:00
Bas Brands e8954b775d MDL-63113 theme boost: initialsbar wrapping 2018-09-26 10:55:40 +02:00
Eloy Lafuente (stronk7) 5802130950 Merge branch 'MDL-61981-35_nestedmath' of git://github.com/Mankro/moodle into MOODLE_35_STABLE 2018-09-25 23:40:42 +02:00
Eloy Lafuente (stronk7) 140e251310 Merge branch 'MDL-62279-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-09-25 23:26:28 +02:00
Eloy Lafuente (stronk7) 359a6c32e8 Merge branch 'MDL-62714-35-boost_course_visibility_in_navbar' of https://github.com/fskandalis/moodle into MOODLE_35_STABLE 2018-09-25 23:19:34 +02:00
Eloy Lafuente (stronk7) 9cd49fbad4 Merge branch 'MDL-60028-35' of https://github.com/paulholden/moodle into MOODLE_35_STABLE 2018-09-25 16:58:36 +02:00
David Monllao 713385769d Merge branch 'MDL-63119-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-09-25 12:36:29 +02:00
David Monllao 5874ac4f4a Merge branch 'MDL-62930-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-09-25 12:28:15 +02:00
David Monllao 9708b6e9b3 Merge branch 'MDL-62717-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-09-25 12:18:54 +02:00
Andrew Nicols 16c5c24d19 Merge branch 'MDL-63346_35' of https://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-09-25 08:50:30 +08:00
Eloy Lafuente (stronk7) 57b938da30 MDL-63020 tests: ensure that searching for is null values also works ok 2018-09-24 17:41:47 +02:00
Tim Hunt ce774c66a1 MDL-63020 dml: improve the unit tests 2018-09-24 17:41:47 +02:00
Eloy Lafuente (stronk7) d82567eb7f Merge branch 'MDL-63020_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-09-24 17:40:10 +02:00
AMOS bot a0f1d5f0eb Automatically generated installer lang files 2018-09-22 00:07:34 +00:00
Paul Holden e273e709bf MDL-60028 enrol: fix counting of enrolled users in a given context.
Previously users enrolled in a course via multiple enrolment methods
would be counted multiple times.
2018-09-21 10:21:42 +01:00
Tim Hunt ade905e248 MDL-63020 ddl: fix nullable unique indexes in OCI and MS SQL
This works-around the default non-standard behaviour of these DB engines.
2018-09-20 17:50:28 +01:00
Tim Hunt 8f76f17fc6 MDL-63020 xmldb: Improve PHPdoc comments for better IDE autocomplete 2018-09-20 17:50:26 +01:00
Luca Bösch a93eb1182e MDL-62717 forum: Create announcements forum with HTML format intro. 2018-09-20 17:47:20 +02:00
Markku Riekkinen e4099493ca MDL-61981 filter_mathjaxloader: add unit tests 2018-09-20 15:44:38 +03:00
Markku Riekkinen 71d3c750eb MDL-61981 filter_mathjaxloader: nested math environments
The MathJax filter used to break mathematics if inline math was
used inside display math. Nolink spans were added around
the inline math even though it was nested inside another math
environment. This fix is aware of the nesting so that the spans
may be inserted only at the outer math environment. No regular
expressions are used because they do not support detecting
arbitrary, unlimited nesting of parentheses in the text, which
is now needed.
2018-09-20 15:44:02 +03:00
Eloy Lafuente (stronk7) 02a31d5f96 weekly release 3.5.2+ 2018-09-20 12:12:10 +02:00
Eloy Lafuente (stronk7) 906525ce89 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:52:41 +02:00
Eloy Lafuente (stronk7) 9a74362a48 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:47:50 +02:00
David Monllao 8682e82694 Merge branch 'MDL-63140-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-09-19 13:31:16 +02:00
Shamim Rezaie eeef0230ef MDL-63140 mod_glossary: Check if the module is visible to the user 2018-09-19 21:27:54 +10:00
Shamim Rezaie 71ae8603f5 MDL-63140 mod_glossary: Add userid param to glossary calendar callbacks 2018-09-19 21:27:54 +10:00
David Monllao de8c8a4f81 Merge branch 'MDL-63135-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-09-19 13:13:10 +02:00
Eloy Lafuente (stronk7) d2bceb2031 Merge branch 'MDL-63116-35-1' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-09-18 23:48:12 +02:00
Mihail Geshoski 9f1fd9955a MDL-61908 privacy: Fix heading in data privacy pages 2018-09-18 18:51:14 +02:00
Eloy Lafuente (stronk7) 9093ace988 Merge branch 'MDL-63109-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-09-18 18:32:04 +02:00
David Monllao 7c0ecc11e7 Merge branch 'MDL-63138-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-09-18 16:41:29 +02:00
David Monllao 4e6ec59f8c Merge branch 'MDL-63139-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-09-18 16:38:00 +02:00
David Monllao a266d8a24f Merge branch 'MDL-63117-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-09-18 16:34:30 +02:00
David Monllao 1d67ef4cc7 Merge branch 'MDL-63333_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-09-18 12:18:54 +02:00
Mihail Geshoski 39116eeac9 MDL-63109 privacy: Add behat tests 2018-09-18 15:07:23 +08:00
Mihail Geshoski 60e45d8e0d MDL-63109 privacy: Fix the initialisation of the policy acceptance modal 2018-09-18 15:04:46 +08:00
Jun Pataleta 3135d3f048 Merge branch 'm35_MDL-63319_MySQL_MSSQL_Rename_Field_Reserved_Word' of https://github.com/scara/moodle into MOODLE_35_STABLE 2018-09-18 14:08:53 +08:00
Tim Schroeder f7035a839d MDL-63119 calendar: now showing event end in modal
When clicking on an event in the month view, a modal opens with
    information about it. That modal did previously only show the start time
    of the event. It now also shows the end (if the event has one).
2018-09-18 13:41:43 +08:00
Damyon Wiese ffdc051c71 MDL-62930 theme_boost: Remove custom-select styles
These styles cause the custom select fields to overlap their containers. See the license field in the file picker single file info dialogue for an example.
2018-09-18 08:42:35 +08:00
Eloy Lafuente (stronk7) aa5c98350f Merge branch 'MDL-58781_35' of https://github.com/aanabit/moodle into MOODLE_35_STABLE 2018-09-17 23:43:22 +02:00
Eloy Lafuente (stronk7) 206285819c Merge branch 'MDL-63009-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-09-17 23:30:41 +02:00
Eloy Lafuente (stronk7) 31867d8880 Merge branch 'MDL-63184-35-2' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-09-17 23:11:36 +02:00
Eloy Lafuente (stronk7) d4e17846f6 Merge branch 'MDL-63371_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-09-17 23:09:28 +02:00
Eloy Lafuente (stronk7) e34e5486cf Merge branch 'MDL-63354-35' of https://github.com/HuongNV13/moodle into MOODLE_35_STABLE 2018-09-17 23:06:47 +02:00
Jun Pataleta d283cf2cee MDL-63184 tool_dataprivacy: Ensure PO roles have correct capability 2018-09-17 22:35:23 +08:00
David Monllao 3a832ad75e Merge branch 'MDL-62554-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-09-17 13:45:50 +02:00
Eloy Lafuente (stronk7) 9cab25219d Merge branch 'MDL-63215-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-09-17 10:43:07 +02:00
Mark Nelson ff5ec88a55 MDL-63333 core: removed unused setting 'messaginghidereadnotifications' 2018-09-17 10:04:55 +08:00
Mihail Geshoski 996090515e MDL-63116 privacy: Add bulk actions to the privacy request table 2018-09-17 08:19:28 +08:00
Mihail Geshoski 6d682a5e5f MDL-63116 privacy: Add behat tests 2018-09-17 08:19:28 +08:00
Mihail Geshoski b7c157aeb2 MDL-63116 privacy: Add unit tests 2018-09-17 08:19:27 +08:00
Michelle Melton 5e1b7e4d28 MDL-63215 block_rss_client: Escaping tags in RSS entries 2018-09-16 23:47:36 +02:00
Matteo Scaramuccia d408738313 MDL-63319 ddl: Added getRenameFieldSQL() coverage 2018-09-14 22:43:03 +02:00
Matteo Scaramuccia 3702ecbe48 MDL-63319 ddl: Improved rename_field() coverage
DB API should allow to rename fields, including renaming
those columns named using a reserved word.
2018-09-14 22:43:03 +02:00
Matteo Scaramuccia 6e73318877 MDL-63319 ddl: MSSQL should rename reserved words 2018-09-14 22:36:31 +02:00
Matteo Scaramuccia d3931b12d2 MDL-63319 ddl: MySQL should rename reserved words 2018-09-14 22:36:31 +02:00
Tim Hunt 8f95b79590 MDL-63371 question: USE_RAW_DATA case in check_qt_var_name_restrictions 2018-09-14 16:44:31 +01:00
Amaia Anabitarte f426155996 MDL-58781 mod_feedback: Inconsistency in analysis exported to excel 2018-09-14 12:37:17 +02:00
Shamim Rezaie a7ee1a75e5 MDL-63143 mod_lesson: Check if the module is visible to the user 2018-09-14 20:35:43 +10:00
Shamim Rezaie a756b01f52 MDL-63143 mod_lesson: Add userid param to mod_lesson calendar callbacks 2018-09-14 20:35:43 +10:00
Eloy Lafuente (stronk7) 9c1fc95578 weekly release 3.5.2+ 2018-09-14 11:06:55 +02:00
Shamim Rezaie 5e8396aa71 MDL-63142 mod_label: Check if the module is visible to the user 2018-09-14 17:55:57 +10:00
Shamim Rezaie 0d20375579 MDL-63142 mod_label: Add userid param to mod_label calendar callbacks 2018-09-14 17:55:56 +10:00
Shamim Rezaie 412697a70f MDL-63141 mod_imscp: Check if the module is visible to the user 2018-09-14 17:54:24 +10:00
Shamim Rezaie c0bb26aaf9 MDL-63141 mod_imscp: Add userid param to mod_imscp calendar callbacks 2018-09-14 17:54:24 +10:00
Huong Nguyen fee8383edc MDL-63354 Group: Rollover text on long group names is not consistent 2018-09-14 09:44:26 +07:00
Eloy Lafuente (stronk7) 29eb2d47be MDL-61169 iplookup: Change to another block still remaining to SF 2018-09-13 19:06:44 +02:00
Jun Pataleta d82e50f0a9 MDL-62554 tool_dataprivacy: Integration review fixes 2018-09-13 14:49:45 +08:00
Michael Hawkins ee1e93d511 MDL-63009 tool_dataprivacy: Added site name/link to request emails 2018-09-12 17:45:49 +08:00
Jun Pataleta aed6a3f001 MDL-62294 lib: Take note of cherry-picked upstream commit
* Make a note of the applied upstream patch in
  lib/lessphp/moodle_readme.txt which fixes the PHP 7.2 compatibility
  issue when counting ruleset rules.
2018-09-12 16:47:21 +08:00
Michaël Marinetti 3446e3a257 MDL-62294 lessphp: Fix for PHP 7.2 compatibility
From upstream commit:
https://github.com/oyejorge/less.php/commit/669acc51817a8da162b5f1b7137e79f0e4acc636
2018-09-12 16:47:21 +08:00
Jun Pataleta 385caa2e24 Merge branch 'mdl63321-moodle35' of https://github.com/tlock/moodle into MOODLE_35_STABLE 2018-09-12 15:33:48 +08:00
Jun Pataleta 7f08932a68 Merge branch 'MDL-63231-set-default-value-for-int-param-MOODLE_35' of https://github.com/guillalva06/moodle into MOODLE_35_STABLE 2018-09-12 09:59:36 +08:00
Jun Pataleta 4540e074bc Merge branch 'MDL-50314_35' of git://github.com/do-you-even-curl/moodle into MOODLE_35_STABLE 2018-09-12 09:43:35 +08:00
Eloy Lafuente (stronk7) d05c1e47db Merge branch 'MDL-63130_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-09-11 23:56:33 +02:00
Eloy Lafuente (stronk7) 442e48c4f2 Merge branch 'MDL-63136-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-09-11 23:52:11 +02:00
Eloy Lafuente (stronk7) 8235feb1ec Merge branch 'MDL-62528-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-09-11 23:05:30 +02:00
Guillermo Alvarez 5b75de57f2 MDL-63231 filepicker: Skype validation for null values
In file_get_all_files_in_draftarea function an integer could get
a null value so it is necessary to execute this function only
when is called with a valid integer value.
2018-09-11 09:04:55 -05:00
Fotis Skandalis 11334441a8 MDL-62714 theme_boost: Course visibility in navigation bar
Added check in navbar.mustache to add dimmed_text class if needed.
Added is_hidden() in navigationlib.php to return if the class needs
to be added.
2018-09-10 17:06:22 +03:00
Jun Pataleta e7a3f9b34a MDL-62554 tool_dataprivacy: Bump version for new WS functions 2018-09-10 12:23:51 +08:00
Jun Pataleta 646c0464ec MDL-62554 tool_dataprivacy: Fix display of activity options 2018-09-10 12:22:54 +08:00
Jun Pataleta 6c48400b67 MDL-62554 tool_dataprivacy: Fix template for Bootstrapbase compatibility 2018-09-10 12:22:54 +08:00
Jun Pataleta 4c6cc6c42a MDL-62554 tool_dataprivacy: Remove defaults form
With the changes in this issue, this form is no longer being used.
2018-09-10 12:22:54 +08:00
Jun Pataleta fbbfd84803 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:22:54 +08:00
Jun Pataleta 1e9477e238 MDL-62554 tool_dataprivacy: Unit tests for new WS/API functions 2018-09-10 12:22:54 +08:00
Jun Pataleta 701f3fdb5c 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:22:54 +08:00
Jun Pataleta 8a45db9903 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:22:54 +08:00
Tim Lock e0d05a355a MDL-63321 block: Fix warning in Calendar month block after MDL-60587 2018-09-10 07:44:38 +09:30
Eloy Lafuente (stronk7) bd27666c42 Moodle release 3.5.2 2018-09-08 16:49:53 +02:00
AMOS bot d1787723db Automatically generated installer lang files 2018-09-08 00:07:35 +00:00
Tim Schroeder 408b3e0952 MDL-50314 mod_quiz: removed hardcoded export columns
* The columns 'institution', 'department' and 'email' are no longer
  automatically included in all quiz result exports.
* These columns can still be used, but have to be configured under
  'showuseridentity'.
2018-09-07 14:18:06 +02:00
Shamim Rezaie 7e973b26de MDL-63139 mod_forum: Check if the module is visible to the user 2018-09-07 16:56:30 +10:00
Shamim Rezaie a82893ac5f MDL-63139 mod_forum: Add userid param to mod_forum calendar callbacks 2018-09-07 16:56:30 +10:00
Shamim Rezaie a027e6dfdc MDL-63138 mod_folder: Check if the module is visible to the user 2018-09-07 15:06:28 +10:00
Shamim Rezaie 275a590d6b MDL-63138 mod_folder: Add userid param to mod_folder calendar callback 2018-09-07 15:06:28 +10:00
Jun Pataleta 7ba9f14fe0 weekly release 3.5.1+ 2018-09-07 11:12:45 +08:00
Jun Pataleta 0d70e9b8e9 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-09-07 11:12:29 +08:00
Shamim Rezaie 168028b097 MDL-63136 mod_data: Check if the module is visible to the user 2018-09-06 20:19:58 +10:00
Shamim Rezaie 77a2e5621f MDL-63136 mod_data: Add userid param to mod_chat calendar callbacks 2018-09-06 20:19:58 +10:00
Mark Nelson af142c1c2d MDL-63130 core_message: add unit test 2018-09-06 18:17:47 +08:00
Pavel Sokolov 6e2fc5e5e1 MDL-63130 message: message_count_unread_message counts sent messages 2018-09-06 18:17:47 +08:00
Shamim Rezaie e64487ed57 MDL-63135 mod_choice: Check if the module is visible to the user 2018-09-06 20:15:03 +10:00
Shamim Rezaie a2b7e585b2 MDL-63135 mod_choice: Add userid param to mod_choice calendar callback 2018-09-06 20:15:03 +10:00
Shamim Rezaie b223dc24a6 MDL-63135 mod_choice: Added choice_get_user_response function
A new function to return choice answers records of a given user in a given choice activity.
2018-09-06 20:15:03 +10:00
Shamim Rezaie b3da43961a MDL-63117 mod_book: Check if the module is visible to the user 2018-09-06 17:46:10 +10:00
Shamim Rezaie edeaac4f7e MDL-63117 mod_book: Add userid param to mod_book calendar callbacks 2018-09-06 17:46:10 +10:00
Jake Dallimore 432d3cc846 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:13:28 +08:00
Jake Dallimore 59de100b8b 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:13:28 +08:00
AMOS bot f1e0f1c194 Automatically generated installer lang files 2018-09-05 00:07:29 +00:00
Damyon Wiese 444d046f9e 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 22:41:46 +02:00
Mihail Geshoski 64722afd45 MDL-62857 theme_boost: Better filtering on navbar / breadcrumbs 2018-09-04 22:41:46 +02:00
Tim Hunt be092b7309 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 22:41:46 +02:00
Eloy Lafuente (stronk7) 005b2bc8a0 Merge branch 'MDL-63249-35-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-09-04 22:35:03 +02:00
Helen Foster f4bd5f5e1a MDL-63249 lang: Merge English strings from the en_fix language pack
Significant string changes:

* completionpass_help, gradetopassnotset in mod_quiz - grade to pass
  set in quiz settings not gradebook
* namecolumnmissing,core_cohort - fixing incorrect message about adding
  users to a cohort
2018-09-04 15:12:17 +02:00
David Monllao 1a8342d9d4 Merge branch 'MDL-62342-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-09-04 14:34:07 +02:00
Neill Magill 6a87753e12 MDL-62944 calendar: Clarify the intended use of $checkcapability 2018-09-04 12:19:12 +02:00
David Monllao 1d16496ee6 Merge branch 'MDL-62944-m35' of https://github.com/NeillM/moodle into MOODLE_35_STABLE 2018-09-04 12:15:50 +02:00
David Monllao e7dc32ad80 Merge branch 'MDL-55993-35' of https://github.com/albertolarah/moodle into MOODLE_35_STABLE 2018-09-04 10:53:23 +02:00
Jun Pataleta fde541a1eb Merge branch 'MDL-60901_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-09-04 10:17:36 +08:00
Dan Poltawski 95ee02b253 MDL-48846 filter: Stop reffering to invalid moodle.org/download/mimetex 2018-09-03 23:45:05 +02:00
Eloy Lafuente (stronk7) f562290f78 Merge branch 'MDL-61325-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-09-03 23:33:52 +02:00
Eloy Lafuente (stronk7) 1273a6665b Merge branch 'MDL-63255-35' of git://github.com/vmdef/moodle into MOODLE_35_STABLE 2018-09-03 23:31:10 +02:00
Eloy Lafuente (stronk7) 74ea4a91e1 Merge branch 'MDL-62848-35-placeholderwidth' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-09-03 23:01:49 +02:00
David Monllao d035bebb1e Merge branch 'MDL-38301-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-09-03 12:44:57 +02:00
Alberto Lara Hernández 4c51211800 Merge branch 'MDL-55993-35' of https://github.com/albertolarah/moodle into MDL-55993-35 2018-09-03 12:34:14 +02:00
Alberto Lara Hernández 61edefee12 MDL-55993 mod_assign: web services don´t set a URL. 2018-09-03 12:34:09 +02:00
David Monllao 304bd3b26b Merge branch 'MDL-55744-35_atto_should_not_autosave_on_not_loggedin_users' of https://github.com/dravek/moodle into MOODLE_35_STABLE 2018-09-03 12:27:57 +02:00
Eloy Lafuente (stronk7) 57c5494741 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:14:51 +02:00
Víctor Déniz Falcón f1019f4616 Merge branch 'MDL-63255-35' of git://github.com/vmdef/moodle into MDL-63255-35-test 2018-09-03 06:56:27 +01:00
Mihail Geshoski bbdb62fffe MDL-62342 privacy: Fix strings used in the consent on behalf modal 2018-09-03 13:00:04 +08:00
Mihail Geshoski cce2977770 MDL-62342 privacy: Use singular/plural form in labels 2018-09-03 13:00:04 +08:00
Mihail Geshoski 60357a5551 MDL-56998 blog: Remove associated blog posts when module is deleted 2018-09-03 08:37:19 +08:00
AMOS bot ce5ea7e187 Automatically generated installer lang files 2018-09-02 00:07:27 +00:00
AMOS bot 36b7d0471c Automatically generated installer lang files 2018-09-01 00:07:28 +00:00
David Mudrák 580a0918b4 MDL-62848 form: Fix width of the autocomplete input field
Make sure that the autcomplete input field is wide enough to display the
placeholder text.
2018-08-31 14:55:08 +02:00
David Mudrák 6aaf582982 MDL-62848 form: Fix missing support for {{#js}} helper in autocomplete
The form-autocomplete module uses three Mustache templates
core/form_autocomplete_input, core/form_autocomplete_suggestions and
core/form_autocomplete_selection. However, it did not support executing
the JS that is eventually part of those templates.
2018-08-31 14:55:08 +02:00
Simey Lameze d6cbf8b8fb MDL-62944 calendar: Fix actionevent logic on template
During the calendar work, we have added the feature of
drag and drop activity event or "action events".

The problem at that time was, action events could not be
editable through the calendar UI, but should be allowed
to drag and drop. So, we couldn't use the return of
calendar_edit_event_allowed() because it can return true
(in case of teachers, that have permission to change the
activity) and the attribute isactionevent was created to
control on the UI if the user is viewing an action event.

So what my patch does is just add the check if we are
viewing an action event, and in this case, it won't display
the cog to edit that event.
2018-08-31 10:17:53 +01:00
Neill Magill e760287046 MDL-62944 activities: Activities should not require capability
Activities should not require the user manipulating them to have
calendar capabilities for their events to be added to the Moodle
calendar
2018-08-31 10:17:53 +01:00
Neill Magill f0b924f980 MDL-62944 activities: Test adding activities with no calendar capability 2018-08-31 10:17:53 +01:00
Neill Magill 1260ed9e33 MDL-62944 completion: Calendar capabilities should not be required
The completion API should not require a user to have the capability to
add calendar events for it to function correctly.
2018-08-31 10:17:52 +01:00
Neill Magill 79df6aee8c MDL-62944 completion: Test that completion dates add events to calendar
When a user does not have calendar capabilities completion dates should
still be created correctly.
2018-08-31 10:17:52 +01:00
Neill Magill ee9c931987 MDL-62944 mod_data: Action menu missing from database pages
The action menu was missing from many of the database activity pages,
which meant that it could not be accessed until enough had been done to
the activity to make the editing tabs that used view.php accessible.
2018-08-31 10:17:52 +01:00
Eloy Lafuente (stronk7) 01389839df weekly release 3.5.1+ 2018-08-30 19:04:28 +02:00
Eloy Lafuente (stronk7) 88a78e9307 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-08-30 19:04:25 +02:00
Dan Marsden 99e51855a6 MDL-61325 use correct value when looking for string. 2018-08-30 09:11:15 +08:00
AMOS bot 5263019b3a Automatically generated installer lang files 2018-08-30 00:08:05 +00:00
Eloy Lafuente (stronk7) 442dc8270a MDL-63165 question: Bump expected size of question exports (take II) 2018-08-29 14:39:07 +02:00
Jake Dallimore dab8d7a0dc Merge branch 'MDL-63165-35-fix' of https://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-08-29 12:33:43 +08:00
Andrew Nicols 9f256a8380 MDL-63165 question: Bump expected size of question exports 2018-08-29 10:46:00 +08:00
Eloy Lafuente (stronk7) 6f0614ef19 Merge branch 'MDL-63094-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-08-29 00:20:29 +02:00
Eloy Lafuente (stronk7) 43dfc84482 Merge branch 'MDL-62914-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-08-28 23:16:31 +02:00
Victor Deniz 5a7e921f70 MDL-63255 core_user: fix hardcoded sql table aliases 2018-08-28 11:55:17 +01:00
David Monllao 0220cc4bdf Merge branch 'wip-MDL-63165-35' of https://github.com/Beedell/moodle into MOODLE_35_STABLE 2018-08-28 12:28:39 +02:00
Jake Dallimore d470ef4c27 Merge branch 'MDL-63101-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-08-28 16:55:53 +08:00
Andrew Nicols efecaa34a4 Merge branch 'MDL-62782-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-28 07:07:37 +08:00
Eloy Lafuente (stronk7) 11694360bd Merge branch 'MDL-62989-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-08-27 17:51:30 +02:00
Jun Pataleta 060e39220c MDL-63187 tool_dataprivacy: Behat fixes for confirmation lang strings 2018-08-27 22:44:35 +08:00
David Monllao e016281821 Merge branch 'MDL-63174-35-emptyusername' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-08-27 15:09:04 +02:00
David Monllao 2267c9fe7f Merge branch 'MDL-63179-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-08-27 12:22:46 +02:00
David Monllao 8bcca194ce MDL-63187 tool_dataprivacy: Update delete purpose and category lang strings 2018-08-27 12:12:04 +02:00
David Monllao 9bb1a66c3c Merge branch 'MDL-63187-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-08-27 11:58:04 +02:00
David Monllao 627b0a7a45 Merge branch 'MDL-63210_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-08-27 11:46:56 +02:00
David Monllao f873ff9ad2 Merge branch 'MDL-62867-35-missing_colon_in_query' of https://github.com/bitumin/moodle into MOODLE_35_STABLE 2018-08-27 11:22:39 +02:00
Andrew Nicols 0dfef1313f Merge branch 'MDL-53024-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-27 15:46:34 +08:00
Damyon Wiese e647f49fe4 MDL-53024 assign: Remove incorrect logic in query
We are simplifying this query so it will return grades if the attempt matches
the submission attempt, or there is no submission. The case where there is a grade
but no submission can happen only for group assignments.
2018-08-27 15:41:12 +08:00
Zig Tan 088f6b973a MDL-38301 mod_assign: Fix date validation logic & message consistency 2018-08-27 15:33:21 +08:00
Andrew Nicols 3c613eb0f9 MDL-63101 cache: Improve accuracy of cache event invalidation 2018-08-27 14:44:11 +08:00
John Beedell 05403f4989 MDL-63165 Question: xml import of question categories 2018-08-24 16:10:07 +01:00
Alberto Lara Hernández a0a10c4d4c MDL-55993 mod_assign: web services don´t set a URL. 2018-08-24 16:33:48 +02:00
Mitxel Moriana f92552d372 MDL-62867 tags: Fix missing colon in sql placeholder
Due to a missing colon in an sql query field value placeholder
the calculation of the ordering field for a newly added tag
was ignoring itemid.
2018-08-24 15:03:13 +02:00
Luca Bösch 769987cf28 MDL-63179 enrol_self: String and @return tag corrected. 2018-08-24 13:38:08 +02:00
Mihail Geshoski 3bee541afc MDL-62989 privacy: Improve data request order for users 2018-08-24 15:33:19 +08:00
Jake Dallimore 069bf5a9dd weekly release 3.5.1+ 2018-08-24 13:08:59 +08:00
Mark Nelson b5c2fef399 MDL-63210 core: convert for loop to forEach in JS 2018-08-24 11:34:17 +08:00
Jun Pataleta 6c5b701bb5 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:54:24 +08:00
Jun Pataleta 696e063e65 MDL-63187 tool_dataprivacy: Behat tests for purposes/categories CRUD 2018-08-23 16:54:24 +08:00
Jun Pataleta 0807b60603 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:54:24 +08:00
Mark Nelson 2e7dbfaabb MDL-63210 core: touch-up after review
1. Reworded the string.
2. Removed logic handling form data.
3. Do not use window.location.replace.

Backport of MDL-54551.
2018-08-23 12:38:57 +08:00
Thom Rawson d6e31537bc MDL-63210 core: AJAX call redirects to login page when session expired
Backport of MDL-54551.
2018-08-23 12:36:47 +08:00
Jake Dallimore d01d3d8eef MDL-36255 core_grade: fix to ensure correct context used for filters
Some grade object (outcomes,scales) can be created at site or course
context, so this patch just makes sure we use the respective context
when applying format_string to the name in the get_name() function.
2018-08-22 17:57:24 +08:00
Andrew Nicols 7ac4ad9a8b 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 9d349b8170 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:27:27 +08:00
Andrew Nicols bd20ed1a7c 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:24:04 +08:00
Henning Bostelmann f5d6dcfd06 MDL-62914 Calendar: handle TZURL iCal property 2018-08-22 10:11:03 +08:00
Andrew Nicols 466ae79141 Merge branch 'MDL-62660_35' of https://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-08-22 08:03:36 +08:00
Eloy Lafuente (stronk7) 821001ce9c 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:02:27 +02:00
Jake Dallimore ac7cf1cfeb Merge branch 'MDL-36255-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-08-21 16:06:31 +08:00
Jake Dallimore f2057a1115 Merge branch 'MDL-62534-35' of https://github.com/mackensen/moodle into MOODLE_35_STABLE 2018-08-21 11:44:20 +08:00
Andrew Nicols 61c36e125e Merge branch 'MDL-62660-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-08-21 11:10:45 +08:00
Jun Pataleta 629c7f75b3 MDL-62660 tool_dataprivacy: Behat fix for data deletion 2018-08-21 10:37:56 +08:00
Jun Pataleta bdefa64956 MDL-62660 tool_dataprivacy: CI fixes 2018-08-21 09:31:25 +08:00
Charles Fulton bfc047cef0 MDL-62534 course formats: do not purge empty sections on upgrade 2018-08-20 09:15:24 -04:00
Michael Hawkins 9c9227995c MDL-62660 tool_dataprivacy: Behat additions for deletion and data expiry 2018-08-20 16:28:03 +08:00
Michael Hawkins 3a69718c58 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-20 16:28:03 +08:00
Michael Hawkins cb8a553af9 MDL-62660 tool_dataprivacy: Add scheduled task to expire data requests 2018-08-20 16:28:03 +08:00
Michael Hawkins 83dc898b00 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-20 16:27:45 +08:00
Andrew Nicols 3e6e80f96f MDL-62660 tool_dataprivacy: Add method for unit tests to run adhoc tasks 2018-08-20 15:32:41 +08:00
Luca Bösch 1b052996ff MDL-36255 grades: allow multilang filters in grades 2018-08-20 08:33:18 +02:00
Jake Dallimore e516332576 Merge branch 'MDL-63097_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-08-20 13:52:25 +08:00
Andrew Nicols 50615d7105 Merge branch 'MDL-63176-35' of https://github.com/spvickers/moodle into MOODLE_35_STABLE 2018-08-20 11:17:08 +08:00
Jake Dallimore 867e35d77f Merge branch 'MDL-57741-35' of git://github.com/cescobedo/moodle into MOODLE_35_STABLE 2018-08-20 10:12:06 +08:00
Eloy Lafuente (stronk7) 72337efdf7 weekly release 3.5.1+ 2018-08-17 17:37:48 +02:00
Eloy Lafuente (stronk7) c0d8a1c3e1 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-08-17 17:37:46 +02:00
David Mudrák f6be98b942 MDL-63174 user: user_create_user to throw exception on empty username
The core API function user_create_user() did not check the case when the
given username was empty.

Also adding a missing string 'usernamelowercase' for the existing lower
case check and unit tests.
2018-08-17 13:06:25 +02:00
David Mudrák d694bf2435 MDL-63174 user: core_user_create_users to throw exception on empty names
To be consistent with the web administration UI, we should not allow to
create invalid user records with empty username, lastname or firstname
via the web services.
2018-08-17 13:06:25 +02:00
Jake Dallimore 2f58da96bb Merge branch 'MDL-62884-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-17 16:04:47 +08:00
Damyon Wiese 66f63a3bf6 MDL-62884 privacy: Additional fix for popper.js
For a table with action menus inside a scrollable region, we need to ensure
the table is at least big enough.
2018-08-17 15:13:55 +08:00
Stephen Vickers 349ac5cbd7 MDL-63176 enrol_lti: Add $COURSE to global
Ensure that capabilities using $COURSE can be parsed.
2018-08-16 22:44:18 +01:00
cescobedo 7985a77474 MDL-57741 enrol_lti: Add Launch URL to Published as LTI tools 2018-08-16 09:11:29 +02:00
AMOS bot 9df6a3972d Automatically generated installer lang files 2018-08-16 00:08:18 +00:00
Bas Brands c24997f074 MDL-62528 forum: Fixed vertical position when loading permalinks 2018-08-15 11:38:14 +02:00
Jake Dallimore 5e26c462d3 Merge branch 'MDL-63050-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-08-15 10:59:54 +08:00
Andrew Nicols bd547eb6dd Merge branch 'MDL-62600-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-08-15 08:55:13 +08:00
AMOS bot b4db1fd8ad Automatically generated installer lang files 2018-08-15 00:07:40 +00:00
Luke Carrier 8cdf03c5a9 MDL-61238 cohort: require_once all libraries 2018-08-15 00:13:12 +02:00
Eloy Lafuente (stronk7) 17567f33c9 Merge branch 'MDL-62341-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-08-15 00:06:06 +02:00
Eloy Lafuente (stronk7) 8509759768 Merge branch 'MDL-58409-35_needs_clean_in_autocomplete_element' of https://github.com/dravek/moodle into MOODLE_35_STABLE 2018-08-14 17:50:29 +02:00
Eloy Lafuente (stronk7) 20eb1297ef Merge branch 'MDL-61650-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-14 16:57:26 +02:00
Michael Hawkins ddf605c655 MDL-62600 tool_dataprivacy: Corrected availability of PO pages to admins 2018-08-14 11:30:09 +08:00
Andrew Nicols 6efc731aab Merge branch 'MDL-62643-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-14 09:12:04 +08:00
Andrew Nicols 6ea7ce8acb Merge branch 'MDL-62896-35' of git://github.com/k-holland/moodle into MOODLE_35_STABLE 2018-08-14 08:05:21 +08:00
David Monllao 74a30640d6 Merge branch 'MDL-62761-35' of https://github.com/jmvedrine/moodle into MOODLE_35_STABLE 2018-08-13 15:30:08 +02:00
David Monllao 105bfe1d95 Merge branch 'MDL-62886-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-08-13 14:25:39 +02:00
David Monllao e87bbfbe94 Merge branch 'MDL-59015-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-08-13 12:19:28 +02:00
Karen Holland 5450a19dec MDL-62896 Privacy: External plugin Additional label update. 2018-08-13 08:39:57 +00:00
Jake Dallimore 094730c7d2 Merge branch 'MDL-60114_35_radio-option-choose' of https://github.com/awagner/moodle into MOODLE_35_STABLE 2018-08-13 16:03:27 +08:00
Mihail Geshoski 76f2ed9c6a MDL-59015 blocks: Confusing View more button in course overview block 2018-08-13 08:54:03 +08:00
Mihail Geshoski 0af0d1d7f6 MDL-62341 privacy: Add behat test 2018-08-13 07:57:48 +08:00
Mihail Geshoski 6415201009 MDL-62341 privacy: Add link to previous page in viewall policies page 2018-08-13 07:57:48 +08:00
Jean-Michel Vedrine 99566b5a60 MDL-62761 lesson: wrong deadlines on index with overrides 2018-08-12 07:49:57 +02:00
Andreas Wagner 06a92988fa MDL-60114 mod_data: adding db activity lost radio field choose option 2018-08-11 07:43:32 +02:00
Luca Bösch 4202a6935e MDL-62886 boost: radio form element required icon 2018-08-10 17:11:17 +02:00
Eloy Lafuente (stronk7) 7c730dfe01 weekly release 3.5.1+ 2018-08-10 13:22:06 +02:00
Andrew Nicols 6e0f59fe32 Merge branch 'MDL-62619_35' of git://github.com/stronk7/moodle into MOODLE_35_STABLE 2018-08-10 08:45:06 +08:00
Eloy Lafuente (stronk7) 6ed9356daf MDL-63097 phpunit: decouple user lastping and session start in tests
Previously the test was assuming that the user last ping, when
accessing to a chat should match the first (enter) system message
sent to the chat. And that's ok 99% of times but, if seconds advance
between the creation of the chat_users record and the chat_messages
one, then they don't match.

So now, the assumption is not used anymore and the expectation
is the timestamp of that first (enter) system message, explicitly.
2018-08-10 02:31:59 +02:00
Eloy Lafuente (stronk7) 8539f6168e 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:16:12 +02:00
Eloy Lafuente (stronk7) 9e6ea43d93 Merge branch 'MDL-62581-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-09 00:09:21 +02:00
Andrew Nicols 6b65d96d70 Merge branch 'MDL-62993_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-08-08 13:10:59 +08:00
Eloy Lafuente (stronk7) 98e96df381 Merge branch 'MDL-62619-35-privacycontexts' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-08-08 01:03:12 +02:00
Eloy Lafuente (stronk7) 127b7446a0 Merge branch 'MDL-43334-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-08 00:16:30 +02:00
Eloy Lafuente (stronk7) a032e58de6 Merge branch 'MDL-61839-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-08-07 23:56:25 +02:00
Eloy Lafuente (stronk7) 4db39b4571 Merge branch 'MDL-61870-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-08-07 23:48:39 +02:00
Eloy Lafuente (stronk7) a9b13cfac3 Merge branch 'MDL-62750_35' of https://github.com/aanabit/moodle into MOODLE_35_STABLE 2018-08-07 17:23:51 +02:00
Eloy Lafuente (stronk7) c1d4386dd7 Merge branch 'MDL-63026_35' of git://github.com/dmonllao/moodle into MOODLE_35_STABLE 2018-08-07 16:00:20 +02:00
David Matamoros b3065a69ed MDL-55744 EDITOR_ATTO: Now atto does not autosave for not loggedin user 2018-08-07 15:28:20 +02:00
David Matamoros aba27d5317 MDL-58409 core_form: needs clean in autocomplete element 2018-08-07 11:03:04 +02:00
Andrew Nicols 7391dfc208 Merge branch 'MDL-62798-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-08-07 13:08:54 +08:00
Andrew Nicols 6d2fedaa47 Merge branch 'wip-MDL-61424-35' of git://github.com/marinaglancy/moodle into MOODLE_35_STABLE 2018-08-07 09:29:41 +08:00
Amaia Anabitarte 02f31bb65b MDL-62750 lang: Trim langlist setting values 2018-08-06 09:40:15 +01:00
Andrew Nicols 1e2f780503 MDL-61515 theme: Pre-compile boost 2018-08-06 15:21:50 +08:00
Andrew Nicols 57bebf7c4b Merge branch 'MDL-61515-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-08-06 15:06:04 +08:00
Andrew Nicols f940b5dcd9 Merge branch 'MDL-62996-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-08-06 12:28:03 +08:00
Mihail Geshoski 7cccc7e03a MDL-62798 repositories: Report sections by name in server repository 2018-08-06 09:56:30 +08:00
Mihail Geshoski 76483d4f45 MDL-61839 glossary: One letter search in glossary returns all entries 2018-08-06 09:47:10 +08:00
Andrew Nicols d3ef856e79 Merge branch 'MDL-62969_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-08-06 09:28:20 +08:00
Damyon Wiese 5c4aef2664 MDL-61650 assign: More consistent empty text
Consistently check for empty submissions with count_words(trim($text)).
2018-08-03 16:53:45 +08:00
Damyon Wiese ff7ec97918 MDL-62279 assign: Properly set the defaults
Before an assignment has been created, we need to use the site defaults,
not the per instance defaults for the settings.
2018-08-03 15:13:22 +08:00
Andrew Nicols 4bd4b46070 weekly release 3.5.1+ 2018-08-03 11:30:29 +08:00
Andrew Nicols 2392c10734 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-08-03 11:30:27 +08:00
Damyon Wiese 6b74d76fa9 MDL-60685 assign: Do not falsely use admin user
When an assign_grades record is automatically populated, do not use the admin user as the default grader.

This would generate false information on the assignment summary screen and send false notifications from
the admin user.
2018-08-02 14:19:59 +08:00
Damyon Wiese f86bd7ece0 MDL-61870 mod_assign: Conditionally backup groups
When group info is not backed up, do not backup assignment submissions or overrides that related to a specific group.
We are already correctly not restoring them, but it is more robust not to include them in the backup file at all.
2018-08-02 13:03:06 +08:00
Zig Tan 805417c33d MDL-61870 mod_assign: Fix/clean up imported group override duedates
Applying patch supplied from Damyon Wiese to address the root-cause
of this issue in the backup/restore logic.
2018-08-02 13:03:05 +08:00
Zig Tan 127b54ffbe MDL-61870 mod_assign: Fix/clean up imported group override duedates
- Prevent group override duedate events from being imported when groups are excluded
- Clean up any existing group override duedate events when editing assignment in upgradelib.php
- Updated lib.php unit tests
2018-08-02 13:03:05 +08:00
Andrew Nicols 8450d9948a Merge branch 'MDL-59847-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-08-02 11:40:08 +08:00
Andrew Nicols 3eca9bf311 Merge branch 'MDL-62983-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-08-02 11:22:50 +08:00
Andrew Nicols 6b3738bc20 MDL-63050 cachestore_redis: Update hExists to check empty 2018-08-02 11:00:27 +08:00
Andrew Nicols 1974156194 MDL-63050 redis: Make session check compatible with Redis 4.0 2018-08-02 11:00:27 +08:00
David Mudrák f0a4839154 MDL-59847 core: Fix display of hidden identity fields in user selectors
User selectors now respect the user's permission to view other users'
hidden profile fields.
2018-08-02 10:37:07 +08:00
Eloy Lafuente (stronk7) 3ab6d8ba2b Merge branch 'MDL-57666-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-08-02 01:27:55 +02:00
Eloy Lafuente (stronk7) 1d2b6c84a2 Merge branch 'MDL-62965-35-userprofilesignup' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-08-02 00:41:36 +02:00
Eloy Lafuente (stronk7) 266744a6f9 Merge branch 'MDL-59595-35-2' of git://github.com/snake/moodle into MOODLE_35_STABLE 2018-08-02 00:21:35 +02:00
Eloy Lafuente (stronk7) c3a1ccc244 Merge branch 'MDL-62977_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-08-01 16:42:10 +02:00
Jonathon Fowler 96df578e8e MDL-62983 core_user: Hold multiple filters when clicking any link 2018-08-01 12:11:13 +02:00
Eloy Lafuente (stronk7) 6cf6523cc7 Merge branch 'MDL-41847-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-07-31 23:50:52 +02:00
David Monllao 43eadce6fc Merge branch 'm35_MDL-58015' of https://github.com/danmarsden/moodle into MOODLE_35_STABLE 2018-07-31 13:37:11 +02:00
David Monllao 98fe6eee32 Merge branch 'MDL-59611-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-07-31 13:31:07 +02:00
David Monllao 671ad6b497 Merge branch 'MDL-44538-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-07-31 11:21:16 +02:00
Jun Pataleta 49bfb1e5f9 Merge branch 'MDL-58768-35-2' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-07-31 16:18:26 +08:00
David Monllao 000a5d0bec MDL-63026 course: Fix reference to unexisting capability 2018-07-31 08:32:00 +02:00
Shamim Rezaie 936ed6067f MDL-58768 mod_assign: Fixed incorrect version number in upgrade.php 2018-07-31 16:23:56 +10:00
Shamim Rezaie 238006f649 MDL-58768 mod_assign: Added userid param to calendar callbacks
The mod_assign_core_calendar_is_event_visible and mod_assign_core_calendar_provide_event_action
functions now accept a new parameter ($userid) so they are not always dependet to the logged in user.
2018-07-31 16:15:38 +10:00
Shamim Rezaie d11038dd43 MDL-58768 core: Fix a typo for callback name 2018-07-31 16:15:38 +10:00
Shamim Rezaie 6b8b06911b MDL-58768 mod_assign: Add $user param to assign::can_grade 2018-07-31 16:15:38 +10:00
Shamim Rezaie fe1ae23939 MDL-58768 calendar: Add $requestinguser to the container class
The requesting user is not always the same as the current user. The container class now allows a different user
to be set as the requesting user. All capability checks are done against the requesting user.

Also, the $requestinguser is passed to core_calendar_provide_event_action and core_calendar_is_event_visible
callback functions. These callback functions need to be updated in all activity modules to accept a $user parameter.
2018-07-31 16:15:38 +10:00
Shamim Rezaie 64b5d44abc MDL-58768 coursecat: coursecat::get to accept a $user parameter 2018-07-31 16:15:37 +10:00
Shamim Rezaie 526e3e8c97 MDL-58768 Calendar: Update get_raw_events_legacy_implementation
Update get_raw_events_legacy_implementation to work with the passed $users parameter instead of the logged in user.
Also, fixed a bug where get_raw_events_legacy_implementation was failing when $users was set to true, and a list of
$groups was given.
2018-07-31 16:15:37 +10:00
Shamim Rezaie 8668391630 MDL-58768 Calendar: Added user param to calendar_set_filters
Decoupled calendar_set_filters from $USER.
Also removed the backward compatibility bit from the function because it was a left over.
2018-07-31 16:15:06 +10:00
Shamim Rezaie f35e7a0eeb MDL-58768 Calendar: Added userid param to calendar_get_default_courses 2018-07-31 16:07:15 +10:00
Eloy Lafuente (stronk7) 949f522c13 Merge branch 'MDL-57977-35' of git://github.com/vmdef/moodle into MOODLE_35_STABLE 2018-07-30 21:10:08 +02:00
Eloy Lafuente (stronk7) ac6bef4e38 Merge branch 'MDL-63002_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-07-30 16:15:27 +02:00
Marina Glancy 72bb7ac302 MDL-61424 admin: reset registration if token is rejected 2018-07-30 16:12:39 +02:00
Mark Nelson 1b4cd6be30 MDL-63002 core: fixed incorrect calls to non-existent delete_record() 2018-07-30 21:00:25 +08:00
Adrian Perez 10dae695ec MDL-63002 core_grade: fixed deletion of overriden letters 2018-07-30 21:00:24 +08:00
Jun Pataleta fc12d8d459 Merge branch 'MDL-62931-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-07-30 16:34:36 +08:00
Claude Vervoort c46c85055b MDL-62993 ltiservice_memberships: make message an array per spec
Also added resultSourcedId to member for BC.
2018-07-30 10:07:22 +08:00
Mihail Geshoski f4411ba700 MDL-57666 repositories: Error when copying file from Dropbox repository 2018-07-30 09:29:51 +08:00
AMOS bot 37002deeda Automatically generated installer lang files 2018-07-29 00:07:19 +00:00
David Mudrák 06e4325bda MDL-62965 core_user: Fix missing user profile fields on signup page 2018-07-27 10:36:56 +02:00
Jun Pataleta 8aa12f5bff Merge branch 'MDL-62950-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-07-27 16:19:02 +08:00
David Mudrák 84bef5413c 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:38 +02:00
David Mudrák df26945105 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:38 +02:00
Andrew Nicols b9b8d94716 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:38 +02:00
David Mudrák ec92a0762d MDL-59847 core: Fix display of hidden identity fields in user lists
Places that display list of users (such as course participants page)
with additional identifier fields now respect the user's permission to
view other users' hidden profile fields.
2018-07-27 10:04:56 +02:00
Jun Pataleta 9bd851f39c MDL-62996 tool_dataprivacy: Add 3.4/3.3 upgrade steps to 3.5+
This is to ensure that sites that have the early dev version of the tool
can upgrade smoothly to 3.5 and up.
2018-07-27 15:57:01 +08:00
Sara Arjona 502b17bebd MDL-41847 core_user: Add preferred lang field to the user creation form
The preferred language has been added only to the user creation form.
For existing users, you should access to the user profile, click over
Preferences and go to the Preferred language page.
2018-07-27 09:07:22 +02:00
Sara Arjona 2857ca0c9f MDL-41847 core_user: Add lang to login URL email for new user accounts 2018-07-27 09:07:22 +02:00
Andrew Nicols 5a1aa729df MDL-62950 behat: Disable editing for boost test 2018-07-27 10:59:46 +08:00
Andrew Nicols 6020745280 MDL-62950 behat: Correct navbar references 2018-07-27 10:59:46 +08:00
Andrew Nicols 948485dbd6 MDL-62950 behat: Correct use of nav 2018-07-27 10:59:45 +08:00
Andrew Nicols 1da88f2c6a MDL-62950 behat: Correct path to P&P 2018-07-27 10:59:45 +08:00
Mark Nelson ab38db069e MDL-62977 mod_wiki: style buttons appropriately 2018-07-27 10:51:25 +08:00
Dan Marsden c1a29fc252 MDL-58015 mod_scorm: set orgidentifier correctly for popup. 2018-07-27 10:36:43 +12:00
Eloy Lafuente (stronk7) a1d8976005 weekly release 3.5.1+ 2018-07-26 19:56:50 +02:00
Vadim Tabunshchik c0171e6090 MDL-62871 quiz: incorrect foreign key declaration 2018-07-26 11:38:51 +02:00
Eloy Lafuente (stronk7) bdf7f7613e Merge branch 'MDL-62771-35_move_plag_disclosure' of https://github.com/jmcgettrick/moodle into MOODLE_35_STABLE 2018-07-26 00:39:10 +02:00
Jun Pataleta b42457028a Merge branch 'MDL-62852-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-07-25 16:38:11 +08:00
Sara Arjona 783f2a8b8e MDL-62852 policy: Display policy audience in the viewall.php page 2018-07-25 10:20:37 +02:00
Jun Pataleta ac496f22af Merge branch 'MDL-62807-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-07-25 13:44:36 +08:00
Andrew Nicols a1c3aedf5b Merge branch 'MDL-60826-35-fix' of https://github.com/lameze/moodle into MOODLE_35_STABLE 2018-07-25 13:42:58 +08:00
Simey Lameze a2c9e82c42 MDL-60826 calendar: fix group event type logic 2018-07-25 12:54:50 +08:00
Andrew Nicols 82d2d27cd3 Merge branch 'MDL-62897-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-07-25 12:42:58 +08:00
Tony Butler e8d8bccb25 MDL-59611 mod_assign: Move help icons for subplugin types out of labels 2018-07-25 12:22:50 +08:00
Damyon Wiese d5859dacc8 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-07-25 12:09:31 +08:00
Cory Buecker d738ae91e7 MDL-62969 mod_lti: fix parsed value for LtiLink.memberships.url
Fixes a typo in the function that replaces the LTI placeholder variable
LtiLink.memberships.url with the actual endpoint. Without this change,
the placeholder variable, "$LtiLink.memberships.url" is not replaced in
the LTI launch parameters.
2018-07-25 12:00:10 +08:00
Shamim Rezaie daff43e9c7 MDL-62897 Questions: Find duplicate top cats per context and fix them 2018-07-25 12:29:04 +10:00
Andrew Nicols 6aedf45bee Merge branch 'MDL-60826-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-07-25 09:19:43 +08:00
Simey Lameze fa9fffdc9a MDL-60826 calendar: further performance improvements
This commit does few tweaks and other performance improvements to
calendar_get_allowed_event_types to be more efficient. Also some minor
queries optimizations on the event form to fetch the list of courses
without groups.
2018-07-25 08:59:04 +08:00
Simey Lameze 7ad1d28554 MDL-60826 calendar: restrict course fields to be retrieved 2018-07-25 08:59:04 +08:00
Simey Lameze 3f3385c46a MDL-60826 calendar: update manage subscription page to use new function 2018-07-25 08:59:04 +08:00
Simey Lameze 11ebc149ed MDL-60826 calendar: lazy load groups on event form 2018-07-25 08:59:04 +08:00
Simey Lameze c985ad6263 MDL-60826 calendar: tests for calendar_get_allowed_event_types 2018-07-25 08:59:04 +08:00
Simey Lameze 6a6d683283 MDL-60826 calendar: introduce calendar_get_allowed_event_types function 2018-07-25 08:59:04 +08:00
Andrew Nicols ab9f7ab790 Merge branch 'MDL-61652-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-07-25 08:34:39 +08:00
Eloy Lafuente (stronk7) 3248a578e9 Merge branch 'MDL-62961_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-07-25 01:24:52 +02:00
Eloy Lafuente (stronk7) 3cd9d0a689 Merge branch 'MDL-62935-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-07-25 01:17:22 +02:00
Andrew Nicols 8ee6b2a201 MDL-62653 behat: Ensure that tasks run properly from behat 2018-07-25 00:39:04 +02:00
Eloy Lafuente (stronk7) 692049a9c1 Merge branch 'MDL-60874-35-enrolautocomplete' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-07-24 19:06:07 +02:00
Eloy Lafuente (stronk7) 36895ee702 Merge branch 'MDL-62925_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-07-24 18:32:49 +02:00
Jun Pataleta 68e2d67939 Merge branch 'MDL-62919-35' of git://github.com/cescobedo/moodle into MOODLE_35_STABLE 2018-07-24 16:24:04 +08:00
Bas Brands dd6f34e334 MDL-62935 boost: slideshow view page overflow 2018-07-24 10:23:20 +02:00
Jun Pataleta 310eff99fc Merge branch 'MDL-62939-35-cleanhtmluserdesc' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-07-24 11:58:20 +08:00
sam marshall 5047c83b9c MDL-61652 tool_dataprivacy: Add capabilities to control data downloads 2018-07-24 08:47:09 +08:00
Jake Dallimore 5b531aabee MDL-59595 admin: Make sure $ADMIN is properly unset when changing users 2018-07-24 08:31:22 +08:00
Jake Dallimore 5723130d84 MDL-59595 admin: unit test covering the adminroot cache reset scenario
The adminroot cache should be reset when the current user is changed,
which can occur during cron or during unit tests.
2018-07-24 08:31:22 +08:00
Victor Deniz f490505b23 MDL-57977 search: GlobalSearch by alternate name fields
Enable support for searching all name fields in search
2018-07-23 10:11:37 +01:00
Damyon Wiese ed90c88864 MDL-62581 backup: CSS fixes
Sanitise the CSS for backup and restore. Main fixes are to sanitise the form-inline styles and set a fixed
width for the search forms within the restore form.
2018-07-23 16:20:01 +08:00
Mark Nelson faabcf6d76 MDL-62961 mod_feedback: style buttons appropriately 2018-07-23 15:49:06 +08:00
Andrew Nicols 8837262d2c Merge branch 'MDL-35977-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-07-23 15:31:17 +08:00
Jun Pataleta e30562a127 Merge branch 'MDL-62948-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-07-23 15:18:49 +08:00
Jun Pataleta b43f63c11f Merge branch 'MDL-62949-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-07-23 14:53:57 +08:00
Andrew Nicols 78ccc2c951 Merge branch 'MDL-62962-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-07-23 14:51:43 +08:00
Jun Pataleta 9835dc02dd MDL-62962 tool_dataprivacy: Move user generation within the test 2018-07-23 13:36:14 +08:00
Shamim Rezaie 5420485076 MDL-62897 Questions: Fix multi top cats in question_save_from_deletion 2018-07-23 14:37:48 +10:00
Shamim Rezaie e69a3f20ee MDL-62897 restore: Fix component name in get_string() 2018-07-23 14:37:48 +10:00
Shamim Rezaie 5e29146127 MDL-62931 Questions: Escaping lang strings properly 2018-07-23 14:26:43 +10:00
Andrew Nicols 421619e6e3 Merge branch 'MDL-61707-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-07-23 11:40:53 +08:00
Andrew Nicols 39ffa8c6e3 Merge branch 'MDL-29701-35' of https://github.com/phuchau1509/moodle into MOODLE_35_STABLE 2018-07-23 09:44:02 +08:00
Mihail Geshoski 53e622ba88 MDL-61707 authentication: Remove presignup session upon user signup 2018-07-23 09:33:21 +08:00
Mihail Geshoski 3f70323ba7 MDL-35977 comments: Missing label in blog and assignment comment area 2018-07-23 08:55:51 +08:00
Andrew Nicols 99d1a03857 weekly release 3.5.1+ 2018-07-20 15:09:13 +08:00
Andrew Nicols a2ddeab20e Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-07-20 15:09:11 +08:00
Andrew Nicols bb05597274 MDL-62949 calendar: Fix behat nav to calendar 2018-07-20 11:48:44 +08:00
Andrew Nicols cb8c503098 MDL-62948 tool_policy: Increase window size
In the default window size the floating footer detaling the privacy
policies covers the "Login as guest" button.
2018-07-20 11:12:06 +08:00
Jake Hau dfe3389cf5 MDL-29701 editor: mform disableif doesn't work on editor element 2018-07-19 11:37:36 +07:00
David Mudrák aac5333538 MDL-62939 user: Fix field type of the user profile description
As this is an editor element field, the it is supposed to be PARAM_RAW.
Otherwise it cleans-up certain non-HTML syntax such as Markdown
blockquote characters.
2018-07-18 14:29:53 +02:00
Jun Pataleta c9038ad429 Merge branch 'MDL-62811_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-07-18 16:12:07 +08:00
Jun Pataleta 6d0a80dfc1 Merge branch 'MDL-62769_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-07-18 14:54:25 +08:00
Jun Pataleta f22286dd83 Merge branch 'MDL-62781-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-07-18 10:05:29 +08:00
Jun Pataleta c4030220a4 MDL-62781 core_question: Remove unnecessary assertion 2018-07-18 09:59:37 +08:00
Tim Hunt e088a1f198 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 09:59:36 +08:00
AMOS bot b6f35bf63e Automatically generated installer lang files 2018-07-18 00:07:34 +00:00
cescobedo b5282bfb67 MDL-62919 auth_shibboleth: Use the right shibboleth_auth_instructions
Change $CFG->auth_instructions by shibboleth/auth_instructions in the index_form.html
2018-07-17 21:07:53 +02:00
Nick Phillips 5721a09e82 MDL-59447 question: GIFT import to respect multichoice defaults
Fix GIFT import to respect default answernumbering for new multichoice
questions (introduced in MDL-50011). Default for shuffleanswers is still
ignored, as this is a rather coarse-grained way to do it, and the wrong
setting for shuffleanswers would irreversibly alter the meaning of some
questions.

This is not an ideal long-term fix, but will save significant effort
for anyone who needs to be able to set answernumbering style for
questions imported in GIFT format.
2018-07-17 14:37:19 +02:00
David Monllao 21e4f90ca8 Merge branch 'MDL-54830-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-07-17 13:55:11 +02:00
David Monllao 9cb0f99c37 Merge branch 'MDL-61351_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-07-17 12:32:08 +02:00
Tim Schroeder 4c66ff31c8 MDL-61351 auth_shibboleth: removed redundant session handler class check 2018-07-17 16:46:26 +08:00
Tim Schroeder 2542cb94e4 MDL-61351 core: added \core\session\manager\get_handler_class()
* This is needed e.g. by the shibboleth logout handler to check which
type of sessions are used.
2018-07-17 16:46:26 +08:00
Mark Nelson 97be43646f MDL-61351 auth_shibboleth: move new functions to separate class
This reduces the amount of code to an already confusing logout.php
file and prevents conflicts if someone else happens to be using
the same function name in a file.
2018-07-17 16:46:26 +08:00
Tim Schroeder 4b1adeef8d MDL-61351 shibboleth: Logout: fix session handler class not being used
* use $CFG->session_handler_class to determine which type of session is
  used
* if not set, use $CFG->dbsession instead
2018-07-17 16:46:26 +08:00
David Monllao 0ef5bc0b18 Merge branch 'MDL-62868-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-07-17 10:20:23 +02:00
David Monllao 8632026272 Merge branch 'MDL-62663_35' of https://github.com/andrewhancox/moodle into MOODLE_35_STABLE 2018-07-17 09:58:41 +02:00
David Monllao 706ba3f3af Merge branch 'MDL-60924-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-07-17 09:39:06 +02:00
Andrew Nicols c2c97dee39 Merge branch 'MDL-62889_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-07-17 12:13:01 +08:00
Mark Nelson c74949b071 MDL-62889 message_popup: redirect using moodle_url
Without this relative URLs passed (like /message/index.php)
were being redirected to HOST/message/index.php rather than
$CFG->wwwroot/message/index.php.
2018-07-17 12:00:14 +08:00
Mark Nelson 6dc3988fa0 MDL-62889 message_popup: redirect to notification page if url is empty
If you pass a URL that is not a valid URL (for example
';') it is cleaned to an empty string which redirects to
$CFG->wwwroot/message/output/popup/ which is not a valid
page.
2018-07-17 12:00:13 +08:00
Mark Nelson 1d59ae4286 MDL-62889 message_popup: allow redirects to external URLs
Many institutions create notifications that link to an
external service. We need to allow redirects to these
systems.
2018-07-17 12:00:13 +08:00
Mark Nelson 38d309036c MDL-62889 message_popup: only add redirecturl if it exists
We allow notifications to have a null value for the 'contexturl'.
In this case we should not be appending a 'redirecturl' param to
the URL when it is going to be empty.
2018-07-17 12:00:13 +08:00
Mark Nelson 3c2fa60af3 MDL-62925 mod_wiki: style 'Search wikis' button 2018-07-17 11:31:47 +08:00
Andrew Nicols a9b2db5880 Merge branch 'MDL-62849-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-07-17 10:35:54 +08:00
Adrian Greeve 3482ea90a6 MDL-62849 files lib: Add method to return all files.
Added a function which returns all the files from all directories
in the draftarea.
2018-07-17 10:27:19 +08:00
AMOS bot c50377f55f Automatically generated installer lang files 2018-07-17 00:08:18 +00:00
David Mudrák 2a9787beee MDL-60874 enrol_manual: Improve searching of users in quick enrol UI
* The maximum of users to be displayed raised from 30 to 100. This is
  consistent with the legacy users selector available via enrolment
  methods page.
* If there are more users found matching the search criteria, notify the
  user instead of displaying them. This is to prevent false impression
  that there are only that many users.
* Added behat tests for the new behaviour.
2018-07-16 21:25:40 +02:00
David Mudrák 73df15dd67 MDL-60874 core_form: allow autocomplete AJAX handlers return a string
Custom AJAX handlers for the form autocomplete fields can now optionally
return string in their processResults() callback. If a string is
returned, it is displayed instead of the list of suggested items.

The string is displayed same way as we inform about no available
suggestions.
2018-07-16 21:25:40 +02:00
sarjona 3fe9524aaf MDL-60924 core_user: Hold participant filters when clicking some link 2018-07-16 18:06:12 +02:00
David Monllao 0ec785bfa5 Merge branch 'MDL-49025-m35' of https://github.com/NeillM/moodle into MOODLE_35_STABLE 2018-07-16 13:35:06 +02:00
David Monllao c6fa9f2ea2 Merge branch 'MDL-61019-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-07-16 13:25:13 +02:00
Damyon Wiese c485c0c443 MDL-62807 output: Support .list-unstyled class
Category management lists should not have bullets.

We support this class in "boost" theme but not in bootstrapbase and there are uses of it in core.
2018-07-16 14:04:03 +08:00
Andrew Nicols 474ec7e94b Merge branch 'MDL-45851_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-07-16 13:18:16 +08:00
Jake Dallimore d7b6ef5729 MDL-54830 core_course: fix maxbytes logic in course_update web service
The service should allow the special value of 0 (site limit) to be set.
2018-07-16 10:42:53 +08:00
Jake Dallimore fb6ae10135 MDL-62868 core_group: clear group_grouping cache when changing grouping 2018-07-16 10:36:04 +08:00
Jake Dallimore cadff587be Merge branch 'MDL-45389-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-07-16 10:11:58 +08:00
Neill Magill 74fbce7560 MDL-49025 assign: Submissions of an empty directory should not save
Before this change if only a folder was submitted the submission_is_empty
check would return true. This meant that the draft area would be saved.
The is_empty check would then be called which would return false. This
caused the submission form to indicate to the user that the submission
had failed.

Now is_empty and submission_is_empty should both return false if only
folders are submitted.
2018-07-13 14:04:53 +01:00
Neill Magill 6ede900c49 MDL-49025 assign: Test than an empty directory will not submit 2018-07-13 14:04:53 +01:00
Luca Bösch 4f7cf4088d MDL-61019 Theme boost: lesson display issues with buttons/progress bar. 2018-07-13 09:25:40 +02:00
Luca Bösch 76486ab639 MDL-45389 theme_bootstrapbase: Forum index page alignment in clean. 2018-07-13 09:25:23 +02:00
Luca Bösch a3809c4fc6 MDL-44538 action_menu: rounded corner of highlights only where needed. 2018-07-13 09:25:17 +02:00
Jake Dallimore b80a22f5ba weekly release 3.5.1+ 2018-07-13 12:26:16 +08:00
Jake Dallimore ef6bdbae8e Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-07-13 12:26:14 +08:00
Andrew Nicols 85abbf80e2 Merge branch 'MDL-62746-35-fix' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-07-12 16:19:52 +08:00
Jun Pataleta f2757d51a9 MDL-62746 tag: Behat fix for combining tags scenario 2018-07-12 16:09:10 +08:00
David Monllao 24e83b2807 MDL-59599 navigation: Respect previous activity-level navigation
The original patch was overwriting grading methods navigation.

All credit goes to Andrew Nicols.
2018-07-12 10:25:52 +08:00
AMOS bot 4e37604528 Automatically generated installer lang files 2018-07-12 00:08:29 +00:00
David Monllao 8b2ae94442 Merge branch 'MDL-62618-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-07-11 18:48:16 +02:00
Jake Dallimore 4dec82b25b Merge branch 'MDL-62712-35' of https://github.com/HuongNV13/moodle into MOODLE_35_STABLE 2018-07-11 09:02:28 +08:00
Andrew Nicols e3796a0678 Merge branch 'MDL-52318-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-07-11 08:58:37 +08:00
Andrew Nicols 104c9ffae9 Merge branch 'MDL-62026-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-07-11 08:15:57 +08:00
AMOS bot c5a4536bb8 Automatically generated installer lang files 2018-07-11 00:09:53 +00:00
Andrew Hancox 740db2e5c3 MDL-62663 core_user: Enforce viewalldetails capability when editing user 2018-07-10 13:28:18 +01:00
David Monllao 9fc60dfeb1 Merge branch 'MDL-62262-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-07-10 13:32:38 +02:00
David Monllao c270b92ddb Merge branch 'MDL-62823-m35' of https://github.com/NeillM/moodle into MOODLE_35_STABLE 2018-07-10 11:44:52 +02:00
Andrew Nicols 835d570210 Merge branch 'MDL-61578-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-07-10 16:46:26 +08:00
Huong Nguyen 7a81bcdb34 MDL-62712 Filepicker: Improve upload process
Lost the uploading file when user press Submit before upload process has done
2018-07-10 15:20:18 +07:00
David Monllao 9818499ed8 Merge branch 'MDL-62814-35' of https://github.com/HuongNV13/moodle into MOODLE_35_STABLE 2018-07-10 07:50:53 +02:00
Jun Pataleta 5e0c2c9815 MDL-62026 tool_dataprivacy: Behat tests for contacting PO 2018-07-10 12:31:26 +08:00
Jun Pataleta 50e48b78e9 MDL-62026 tool_dataprivacy: Allow manual completion of general enquiries 2018-07-10 12:31:26 +08:00
Andrew Hancox f3a11484ad MDL-61578 backup: Added unit test for is_course_modified check. 2018-07-10 11:02:23 +08:00
Michael Hawkins af9b4f1f3b MDL-61578 backup: Prevent backup logging triggering fresh backups.
Thanks to Renaat Debleu for their contribution to this solution.
2018-07-10 11:02:23 +08:00
Andrew Nicols 25c946f628 Merge branch 'MDL-62746-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-07-10 10:08:52 +08:00
Jake Dallimore 110f3a3d68 Merge branch 'MDL-62772-35_always_show_disclosure' of https://github.com/jmcgettrick/moodle into MOODLE_35_STABLE 2018-07-10 09:00:34 +08:00
David Monllao b20a4bc15c Merge branch 'mdl35_MDL-62753' of https://github.com/OdyX/moodle into MOODLE_35_STABLE 2018-07-09 15:16:00 +02:00
David Monllao d4315bf53e Merge branch 'MDL-56881-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-07-09 14:39:14 +02:00
David Monllao f31105a9f5 Merge branch 'MDL-62037-35' of https://github.com/lethevinh/moodle into MOODLE_35_STABLE 2018-07-09 13:48:53 +02:00
David Monllao 771c8455cb Merge branch 'MDL-62831-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-07-09 13:36:24 +02:00
David Monllao 8297de9fa3 MDL-59599 navigation: Strict zero-position comparision 2018-07-09 12:27:36 +02:00
David Monllao e336f07cd7 Merge branch 'MDL-59599-35' of https://github.com/Kathrin84/moodle into MOODLE_35_STABLE 2018-07-09 12:00:10 +02:00
John McGettrick 2ff2c49c85 MDL-62772 mod_assign: Show disclosure for all submitters 2018-07-09 09:15:52 +01:00
Andrew Nicols 2f7aaf1d53 Merge branch 'MDL-33509-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-07-09 14:01:55 +08:00
Andrew Nicols 551f57926a Merge branch 'MDL-62808_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-07-09 13:30:13 +08:00
Damyon Wiese b8daa178de MDL-43334 backup: Fix cancel JS
The cancel button is a form element within a div that has the confirmcancel class. When we
accept a cancel modal, we should attempt to click the most specific form element of the
div, not the entire div.
2018-07-09 12:18:43 +08:00
Damyon Wiese 8405e6f1cc MDL-43334 backup: Lanuage for cancel
Cancel backup buttons need clear lang strings.
2018-07-09 12:18:43 +08:00
Andrew Nicols 89076b49fb MDL-56093 auth_db: Remove issue no. from comment 2018-07-09 12:14:33 +08:00
Andrew Nicols 8fc16d9e6a Merge branch 'MOODLE_35_STABLE_MDL_56093' of https://github.com/eledia/moodle into MOODLE_35_STABLE 2018-07-09 12:14:29 +08:00
Jake Dallimore 5136441985 Merge branch 'MDL-61359-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-07-09 12:11:05 +08:00
Mark Nelson 1afa3903ae MDL-62808 core_message: fixed 'useridto' being incorrectly set to 0 2018-07-09 12:00:40 +08:00
Jake Dallimore 4c32762ca7 MDL-52318 core_form: allow multiple filepicker-enabled url elements
In cases when filepicker support is enabled, this patch adds a wrapper
div around the url element and includes the filepicker id in its id.
This allows the js callback code to target the specific input field
with which it is associated.
2018-07-09 11:33:09 +08:00
Jake Dallimore 313531b030 MDL-56881 mod_choice: add unit tests for choice_user_submit_response
Tests cover option limits and multiple option responses.
2018-07-09 11:06:51 +08:00
Jake Dallimore 2b37ee6049 MDL-56881 mod_choice: clarify lang string for limit-reached scenarios 2018-07-09 11:06:51 +08:00
Jake Dallimore a5f3967e57 MDL-56881 mod_choice: fix bug with limits when saving existing choice
The check determining whether a choice option's limit was exceeded was
including the user's existing answers in its checks, meaning a user
couldn't save an existing choice answer, or select further options,
if all a choice option limit was reached. This patch fixes that.
2018-07-09 11:06:51 +08:00
Eloy Lafuente (stronk7) 5b78584fdb Moodle release 3.5.1 2018-07-07 06:54:07 +02:00
AMOS bot ce390c4725 Automatically generated installer lang files 2018-07-07 00:08:27 +00:00
Luca Bösch 942d4df55b MDL-62262 quiz: quiz index page showing wrong dates. 2018-07-06 14:47:55 +02:00
vinhle 23628a98fc MDL-62037 Filepicker: Review image not change when overwrite same 2018-07-06 17:52:24 +07:00
John McGettrick 2daceaf000 MDL-62771 mod_forum: Move print_disclosure 2018-07-06 09:25:35 +01:00
Andrew Nicols 8675c2c908 weekly release 3.5+ 2018-07-06 14:05:15 +08:00
Andrew Nicols 70ffd8bb2b Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-07-06 14:05:13 +08:00
Huong Nguyen 6c1aa273f2 MDL-62814 Question: Improve column base display_header
Column base does not support custom tip if sortable is an array
2018-07-06 10:58:55 +07:00
Tim Hunt 8cc2d39207 MDL-62811 question bank: rendering shouldn't load individual questions 2018-07-05 17:01:35 +01:00
Benjamin Wolf cac9523423 MDL-56093 auth_db: Save custom profile fields in auth_db 2018-07-05 11:15:49 +02:00
Didier 'OdyX' Raboud cb4bb6efc5 MDL-62753 shibboleth: Logout: fix DB sessions kill
* Use the Moodle 2+ {sessions} table, and decode the session
  correctly using session_decode (adodb_unserialize is deprecated).
* Kill all user sessions using \core\session\manager's
  kill_user_sessions.
2018-07-05 09:06:14 +02:00
Michael Hawkins 750121698f MDL-62618 user: Display full names consistently on users page 2018-07-05 10:17:15 +08:00
Michael Hawkins a78f6cd123 MDL-62618 forum: Made full names displayed consistent 2018-07-05 10:17:15 +08:00
Jake Dallimore c2fea8ae43 Merge branch 'MDL-62670-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-07-05 09:26:05 +08:00
AMOS bot 0a520053cb Automatically generated installer lang files 2018-07-05 00:08:24 +00:00
Kathrin Osswald 1da4edbaf0 MDL-59599 navigation: Improve activity status for nav nodes. 2018-07-04 19:59:41 +02:00
Tim Hunt 9eff7d9535 MDL-45851 restore: random questions must always be created
They are really a pointer from a particular quiz to a question category.
They should never be shared, because if they are, unexpected things
happend when they are edited.
2018-07-04 16:34:38 +01:00
Sara Arjona e154204728 MDL-62670 policy: viewall.php must display always all active policies
The viewall.php page is called from the mobile app to display the policies
to the users before agree them. That's why it must display all the active
policies (even for guests), to guarantee the users agree all current
policies from the app.
2018-07-04 12:18:52 +02:00
Sara Arjona 2d90a7d91a MDL-62670 policy: Remove footer link when there are not active policies 2018-07-04 12:18:52 +02:00
Tim Hunt 8ac7c623d3 MDL-62820 question import: properly escape output 2018-07-04 15:40:58 +08:00
Jake Dallimore caa9881c0d Merge branch 'wip-nolink-bug-35' of https://gitlab.di.unito.it/rabellino/moodle into MOODLE_35_STABLE 2018-07-04 14:38:13 +08:00
Damyon Wiese b01258d7f0 MDL-62782 assign: viewgrades allows files
Grade viewer role should be able to see feedback files for an assignment submission.
2018-07-04 14:19:17 +08:00
Jake Dallimore f0188d7b66 Merge branch 'MDL-41463-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-07-04 12:59:21 +08:00
Damyon Wiese 4e52235a23 MDL-62831 mod_assign: Download as folders help
The help string for the download as folders setting should not mention the number of files
submitted by the student. It is not related to that, it is only dependant on the value of this setting.
2018-07-04 12:20:45 +08:00
Jake Dallimore ab94fee038 Merge branch 'MDL-44081-35' of git://github.com/zig-moodle/moodle into MOODLE_35_STABLE 2018-07-04 10:50:48 +08:00
Andrew Nicols 2d64d63373 MDL-62834 behat: Move include to function 2018-07-04 10:45:25 +08:00
Eloy Lafuente (stronk7) 6cb165d681 Merge branch 'wip-MDL-62526-35' of git://github.com/marinaglancy/moodle into MOODLE_35_STABLE 2018-07-04 04:19:24 +02:00
Adrian Greeve 55fc303a5f MDL-62616 tool_log: Add a setting to disable exporting logs. 2018-07-04 09:59:32 +08:00
Eloy Lafuente (stronk7) c453ea21e1 Merge branch 'MDL-62288_35' of https://bitbucket.org/ujiapps/moodle-uji into MOODLE_35_STABLE 2018-07-04 01:21:19 +02:00
Eloy Lafuente (stronk7) 24e4ec9d60 Merge branch 'MDL-62792_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-07-04 00:55:22 +02:00
Eloy Lafuente (stronk7) 25e9be8ea9 Merge branch 'MDL-62838_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-07-04 00:52:14 +02:00
Eloy Lafuente (stronk7) 9ffcaa7db7 Merge branch 'MDL-62834_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-07-04 00:46:40 +02:00
Eloy Lafuente (stronk7) 936d21d2dd Merge branch 'MDL-62211-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-07-03 23:33:48 +02:00
David Monllao d20b4b2f23 Merge branch 'MDL-61296-35' of git://github.com/cescobedo/moodle into MOODLE_35_STABLE 2018-07-03 23:33:47 +02:00
David Monllao d587c4bd90 Merge branch 'MDL-62656-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-07-03 23:33:47 +02:00
David Monllao 3828033e98 Merge branch 'MDL-60014-35-catcherror-nopermission-annotatepdfcron' of https://github.com/Eva-Dv/moodle into MOODLE_35_STABLE 2018-07-03 23:33:46 +02:00
David Monllao 4892662238 Merge branch 'MDL-62691-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-07-03 23:33:46 +02:00
David Monllao ccbd142a63 Merge branch 'MDL-62813-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-07-03 23:33:45 +02:00
David Monllao 3b52f52351 Merge branch 'MDL-62654-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-07-03 15:19:34 +02:00
David Monllao 173968b25d Merge branch 'MDL-32232-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-07-03 14:00:20 +02:00
David Mudrák 4cdd9e77dc MDL-62796 tool_policy: Fix a regression in Behat test
We now correctly show the popup with links to policies that apply to
anonymous visitors of the site. So the name of the guests only policy
became visible in this popup in more cases and it caused false failures.

The point of the lines affected by this patch is to make sure that the
policy does / does not appear in the list of policies to be accepted. So
I am making it more specific of where the text is / is not supposed to
be seen.
2018-07-03 11:05:18 +02:00
Michael Hawkins 7805012e59 MDL-61359 message: Updated sender contact text to text node content 2018-07-03 14:48:57 +08:00
Jake Dallimore 483d8fefa8 Merge branch 'MDL-59261-35' of git://github.com/ryanwyllie/moodle into MOODLE_35_STABLE 2018-07-03 14:44:53 +08:00
Bas Brands 654eac4f13 MDL-59261 theme_boost: compiled css fallback 2018-07-03 14:14:05 +08:00
Michael Hawkins 86699bedb0 MDL-62767 theme: Remove old localcaches when clearing/updating theme cache 2018-07-03 14:00:40 +08:00
Michael Hawkins 914e26d916 MDL-62801 themes: Added missing filelib include 2018-07-03 13:08:33 +08:00
Marina Glancy ced6691934 MDL-62790 course: cap check in core_course_get_categories 2018-07-03 12:02:18 +08:00
Mihail Geshoski 07e1fa272f MDL-33509 forms: Change button type in url resource editing page 2018-07-03 11:59:14 +08:00
Jake Dallimore ff1fbf2992 Merge branch 'MDL-62715-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2018-07-03 08:49:52 +08:00
Eloy Lafuente (stronk7) 49e435c00a Merge branch 'MDL-62763-35-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-07-02 20:09:15 +02:00
Eloy Lafuente (stronk7) b67ae5a0ad Merge branch 'MDL-62621-35-ixcomment' of git://github.com/mudrd8mz/moodle into MOODLE_35_STABLE 2018-07-02 20:02:25 +02:00
Helen Foster b2b1bb2d29 MDL-62763 lang: Merge English strings from the en_fix language pack
Significant string changes:

* various strings in tool_dataprivacy and tool_policy - 'Data Protection
  Officer' changed to 'privacy officer'
* idnumbermod_help,core - mentioning resource and custom reporting
2018-07-02 13:36:29 +02:00
David Monllao 5a13884a0f Merge branch 'wip-MDL-62796-35' of git://github.com/marinaglancy/moodle into MOODLE_35_STABLE 2018-07-02 12:21:52 +02:00
David Monllao d42328e223 Merge branch 'MDL-62713-m35' of https://github.com/sammarshallou/moodle into MOODLE_35_STABLE 2018-07-02 10:42:18 +02:00
Jake Dallimore 3879b520b7 Merge branch 'MDL-62801-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-07-02 16:19:10 +08:00
David Mudrák 6a3cffd3c6 MDL-62621 comments: Add missing indexes on comments table
The composite index over the fields contextid, commentarea and itemid
helps the comments API to load all comments for the given area
effectively.

The index over the userid field helps the privacy API to load all
comments by the given user.
2018-07-02 10:13:00 +02:00
Mihail Geshoski 40790755ce MDL-62656 course: Align elements in course and category management 2018-07-02 08:25:34 +08:00
AMOS bot 310a1f0895 Automatically generated installer lang files 2018-06-30 00:08:23 +00:00
Tim Hunt ed3468e40b MDL-62838 filters: dont use statics inside functions. Use MUC 2018-06-29 17:25:13 +01:00
Tim Hunt 9bedb84419 MDL-62834 behat: new step for enabling filters 2018-06-29 11:27:46 +01:00
sam marshall c843630aee MDL-62713 core_search: Cron error indexing sections in some cases 2018-06-29 10:52:53 +01:00
Neill Magill 94f5d062bd MDL-62823 filter: Save changes button should be styled 2018-06-29 08:41:06 +01:00
Michael Hawkins 233847bdb7 MDL-62801 themes: Remove old mustache caches when new one generated 2018-06-29 10:50:21 +08:00
Jun Pataleta f29d518ba8 MDL-62211 tool_dataprivacy: Request filtering and pagination
* Make data requests page use table_sql to make it sortable
* Also, add an autocomplete element that enables filtering by
  status and request type.
2018-06-29 10:32:25 +08:00
Zig Tan 749c7cc5a9 MDL-44081 assign: Fix Grader annotation button accessibility ordering 2018-06-29 09:28:45 +08:00
Luca Bösch bb3ce79c7e MDL-41463 course: Reset remove groups section advanced toggle. 2018-06-28 23:05:40 +02:00
Luca Bösch 4b52a5b2c6 MDL-32232 lesson: changing a page type chooser option for consistency 2018-06-28 23:05:29 +02:00
Sergio Rabellino af344834e1 MDL-61403 filter: Fix for the nolink tag ignored
Fix for the nolink tag ignored if there are some other attributes defined in the same span
2018-06-28 11:01:01 +02:00
Simey Lameze cdc9e56198 MDL-62813 calendar: add behat test for form validation 2018-06-28 14:12:18 +08:00
Simey Lameze 0e0fd72d98 MDL-62813 calendar: fix dmlreadexception on event form 2018-06-28 14:12:18 +08:00
Jake Dallimore 543005a342 weekly release 3.5+ 2018-06-28 14:03:04 +08:00
Marina Glancy 5c33665a74 MDL-62526 admin: ask privacy policy agreement for moodle.net 2018-06-28 11:44:38 +08:00
Eloy Lafuente (stronk7) 362518ef34 Merge branch 'MDL-62325-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-06-28 02:06:03 +02:00
Eloy Lafuente (stronk7) ff246f2260 Merge branch 'wip-MDL-62755-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-06-28 01:56:37 +02:00
Eloy Lafuente (stronk7) fc1b13ede9 Merge branch 'MDL-61889-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-06-28 01:49:31 +02:00
Eloy Lafuente (stronk7) 458b0fb900 Merge branch 'MDL-62747_35' of https://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-06-28 00:32:34 +02:00
Matteo Scaramuccia 5df53c769e MDL-51080 env: warn when running MariaDB w/ wrong dbtype 2018-06-28 00:15:28 +02:00
Jake Dallimore 380a437428 Merge branch 'MDL-61732-35' of https://github.com/Dagefoerde/moodle into MOODLE_35_STABLE 2018-06-27 12:18:13 +08:00
Eloy Lafuente (stronk7) 5f6539e379 Merge branch 'MDL-45088-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-27 03:23:22 +02:00
Eloy Lafuente (stronk7) b44ddd4a7f Merge branch 'MDL-62703-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-06-27 03:17:45 +02:00
Eloy Lafuente (stronk7) 833e2398ef Merge branch 'MDL-62471_35' of git://github.com/dmonllao/moodle into MOODLE_35_STABLE 2018-06-27 02:41:53 +02:00
Eloy Lafuente (stronk7) df9443db71 Merge branch 'MDL-62735-35-refactor' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-06-26 10:44:24 +02:00
Jake Dallimore 4d23edfb37 Merge branch 'MDL-62698_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-06-26 15:30:32 +08:00
Juan Segarra Montesinos 11489b20a5 MDL-62288 mod_glossary: Add links to glossary entries for all users 2018-06-26 08:41:21 +02:00
Jun Pataleta b3483e7ede MDL-61889 tool_dataprivacy: Fix CI errors 2018-06-26 13:21:51 +08:00
Marina Glancy dd05e0d29e MDL-62796 tool_policy: show popup for users who are not logged in 2018-06-26 12:33:24 +08:00
Eloy Lafuente (stronk7) 1b37fe78f9 Merge branch 'MDL-59047-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-06-26 03:05:27 +02:00
Eloy Lafuente (stronk7) 82f21031a3 Merge branch 'MDL-62730-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-06-26 02:54:41 +02:00
Eloy Lafuente (stronk7) 6545e50f62 Merge branch 'MDL-51419-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-06-26 02:41:59 +02:00
Eva Davitasvili 3d7630a158 MDL-60014 assign: Annotate PDF - catch no permission error 2018-06-25 14:10:41 -04:00
Tim Hunt 2588126d66 MDL-62792 filters: fix some bad PHPdocs 2018-06-25 15:52:33 +01:00
Tim Hunt de71bef7cb MDL-62792 filter_mathjaxloader: don't use statics for once-only items 2018-06-25 15:52:31 +01:00
Tim Hunt e310f10818 MDL-62698 quiz_overview: new index should not be unique 2018-06-25 15:51:03 +01:00
Shamim Rezaie 9b6938f99b MDL-62325 enrol_paypal: Added some keys and indexes to enrol_paypal.
- Added these keys to the enrol_paypal db: courseid, userid, instanceid
- Added these indexes to the enrol_paypal db: business, receiver_email
2018-06-25 12:34:43 +10:00
Adrian Greeve 9c42327dd1 MDL-62755 mod_assign: Update to unit tests.
Overrides are now checked to be exported properly.
2018-06-25 09:50:50 +08:00
Adrian Greeve 28631a1455 MDL-62755 mod_assign: Fix override export problem.
The cutoffdate and allowsubmissionsfromdate were not
being exported.
2018-06-25 09:50:50 +08:00
Christian Glahn aa7deee9cc MDL-61732 filters: Have emoticons respect nolink
Also extracts static emoticon cache to a field in order to increases
testability of filter_emoticon, because we are now able to reset the
static emoticon cache before tests, particularly for observing the
response to changing $CFG->emoticons.
2018-06-22 10:29:53 +02:00
Jake Dallimore 009a9ca500 MDL-51419 core_files: Support for utf8 in file names in MS Edge 2018-06-22 10:29:56 +08:00
Jake Dallimore 7de4759799 MDL-62730 themes: Fix calendar icon alignment for datetime in Boost 2018-06-22 10:17:04 +08:00
Jake Dallimore 33433bb8a1 MDL-62735 core_search: searchareas page now respects indexing support 2018-06-22 10:14:34 +08:00
Luca Bösch 9efe4b84ce MDL-45088 xmldb: Fix table alignment 2018-06-21 19:37:26 +02:00
Tim Hunt 12d4fe9ee6 MDL-62769 quiz statistics: graph should show all, not last, variant 2018-06-21 14:55:53 +01:00
Eloy Lafuente (stronk7) 8793e120b9 weekly release 3.5+ 2018-06-21 14:01:11 +02:00
Eloy Lafuente (stronk7) 43f742e1e5 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-06-21 14:01:09 +02:00
Bas Brands 3f4db3e89e MDL-61515 Theme boost: add disabled carousel transitions back in 2018-06-21 10:14:38 +02:00
Andrew Nicols bea7b33840 MDL-62715 mod_assign: Bump submissions back when grading in tests 2018-06-21 08:41:09 +08:00
Jun Pataleta 4900f375d7 Merge branch 'MDL-62017-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-20 19:40:22 +08:00
David Monllao a94b1fed30 Merge branch 'MDL-61482-35' of git://github.com/zig-moodle/moodle into MOODLE_35_STABLE 2018-06-20 13:29:28 +02:00
David Monllao 9337d85eb3 MDL-62471 tool_dataprivacy: Test compatible with 32 bits OS 2018-06-20 12:59:15 +02:00
cescobedo 2a8f28520d MDL-61296 auth_ldap: remove PHP notices when field does not exist. 2018-06-20 11:43:28 +02:00
Luca Bösch e972b9e909 MDL-62017 tool_customlang: format language customisation buttons. 2018-06-20 10:54:13 +02:00
Andrew Nicols 74c35a9133 Merge branch 'wip-MDL-62391-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-06-20 09:40:16 +08:00
Adrian Greeve b4b5124c73 MDL-62391 tool_dataprivacy: Added more detail about data requests.
This adds the detail about who requested the data request to the
tables and data request information page.
2018-06-20 09:01:16 +08:00
Andrew Nicols af963ad2ae Merge branch 'MOODLE_35_STABLE_MDL-62705_results_order_no_context' of https://github.com/mattporritt/moodle into MOODLE_35_STABLE 2018-06-20 08:36:51 +08:00
Eloy Lafuente (stronk7) 2341e8695e Merge branch 'MDL-62014-35' of git://github.com/nashtechdev01/moodle into MOODLE_35_STABLE 2018-06-20 00:57:28 +02:00
Eloy Lafuente (stronk7) f55516fd46 Merge branch 'MDL-36424-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-19 19:20:37 +02:00
Eloy Lafuente (stronk7) 36e337b176 Merge branch 'wip-MDL-61786-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-06-19 19:16:39 +02:00
Eloy Lafuente (stronk7) 23e9fea9a5 Merge branch 'MDL-62358-35' of https://github.com/HuongNV13/moodle into MOODLE_35_STABLE 2018-06-19 19:08:52 +02:00
Eloy Lafuente (stronk7) 1408aa8bc8 Merge branch 'MDL-44791-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-19 18:59:50 +02:00
Eloy Lafuente (stronk7) 2f86cad04e Merge branch 'MDL-62449-35' of git://github.com/sarjona/moodle into MOODLE_35_STABLE 2018-06-19 18:52:31 +02:00
Zig Tan fd77e38fc9 MDL-61482 mod_assign: Clean html in offline grading CSV 2018-06-19 23:30:11 +08:00
Tim Hunt 0191113d24 MDL-62747 accesslib: rdef caching should handle roles with no caps 2018-06-19 16:26:21 +01:00
Jun Pataleta 016e029ea6 MDL-62746 tag: Make forms in tag modals use proper BS classes 2018-06-19 23:01:25 +08:00
Bas Brands 8bc0bbf1ee MDL-62691 Theme boost: editpdf hide dropdown close btn 2018-06-19 16:57:38 +02:00
Bas Brands 1dc4c34d07 MDL-62703 Themes: remove hover styles in course listings 2018-06-19 16:32:31 +02:00
Bas Brands 5339d26259 MDL-59047 question type: short answer boost styles 2018-06-19 14:34:33 +02:00
David Monllao bc7a379947 Merge branch 'MDL-62588-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-06-19 12:53:19 +02:00
Jun Pataleta e9553d973a Merge branch 'MDL-61894-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-19 17:57:13 +08:00
David Monllao 4f7fa18201 Merge branch 'MDL-58702-35' of git://github.com/zig-moodle/moodle into MOODLE_35_STABLE 2018-06-19 11:54:39 +02:00
Andrew Nicols 697a28aadf Merge branch 'MDL-60950-35' of git://github.com/damyon/moodle into MOODLE_35_STABLE 2018-06-19 11:40:45 +08:00
Damyon Wiese 26f5750319 MDL-60950 assign: Update incompatible unit test
Maintain compatibility with unit test changes after MDL-55609.
2018-06-19 10:59:12 +08:00
Damyon Wiese d4fea9b20f MDL-60950 assign: Rename class
testable_assign was renamed to mod_assign_testable_assign
2018-06-19 10:59:12 +08:00
Damyon Wiese af280e5929 MDL-60950 assign: Robust filtering of grading table
For assignments with gradetype set to none, we need special handling when generating the query to filter submissions that need grading.
2018-06-19 10:59:12 +08:00
Damyon Wiese fa8d508f36 MDL-60950 assign: Test for the requires grading filter
This test covers when the requires grading filter is applied, but the
assignment has no grade and the feedbackcomments plugin is disabled.
2018-06-19 10:59:12 +08:00
Andrew Nicols e7bf43ba0d Merge branch 'MDL-62519_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-06-19 10:34:16 +08:00
Andrew Nicols 793da897d1 Merge branch 'MDL-62597-35' of https://github.com/HuongNV13/moodle into MOODLE_35_STABLE 2018-06-19 09:43:54 +08:00
Eloy Lafuente (stronk7) e1aa99e99d Merge branch 'MDL-62638-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-06-19 02:04:00 +02:00
Eloy Lafuente (stronk7) 06b8272791 Merge branch 'MDL-62571_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-06-19 01:49:52 +02:00
Sara Arjona 15703fb9f1 MDL-62449 scorm: Move related data to data in the privacy export 2018-06-18 10:43:36 +02:00
Sara Arjona 20d8c489d8 MDL-62449 scorm: Add SCORM data to privacy export (name, description) 2018-06-18 10:43:36 +02:00
Jun Pataleta 1d97a7818e Merge branch 'MDL-62614-35' of git://github.com/zig-moodle/moodle into MOODLE_35_STABLE 2018-06-18 16:12:23 +08:00
Jun Pataleta 7c64406d4d Merge branch 'MDL-62709_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-06-18 15:15:56 +08:00
Jun Pataleta f71b39b960 Merge branch 'MDL-62658_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-06-18 14:36:47 +08:00
Jun Pataleta 44dd58756b Merge branch 'wip-MDL-62474-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-06-18 14:32:20 +08:00
Mark Nelson 1dbbcaf9ba MDL-62658 message_popup: fixed issue with malformed URL being generated 2018-06-18 14:31:14 +08:00
Adrian Greeve d0e1b2921d 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:06:11 +08:00
Willian Mano 72107631d7 MDL-62474 theme_boost: Fix Boost privacy provider 2018-06-18 14:06:01 +08:00
Huong Nguyen 888cfdc75f MDL-62614 Privacy: Core tag provider delete_item_tags function not work 2018-06-18 14:00:37 +08:00
Andrew Nicols 9886790075 Merge branch 'MDL-62320-35' of git://github.com/vmdef/moodle into MOODLE_35_STABLE 2018-06-18 13:58:50 +08:00
Shamim Rezaie f3323c22fe MDL-62588 enrol_paypal: Add missing instanceid field 2018-06-18 15:18:04 +10:00
Andrew Nicols d8e011f721 Merge branch 'MDL-62234-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-06-18 09:52:33 +08:00
Andrew Nicols 3b63cb559e Merge branch 'MDL-57968-35' of git://github.com/cescobedo/moodle into MOODLE_35_STABLE 2018-06-18 09:09:53 +08:00
AMOS bot f5189e3aa3 Automatically generated installer lang files 2018-06-16 00:08:05 +00:00
Victor Deniz 2c643bb988 MDL-62320 mimetypes: Add JSON to the default mime types list 2018-06-15 13:12:31 +01:00
Damyon Wiese 54e6ea018d MDL-62643 assign: Skip empty online text files
Online text assignment submissions with no text entered into the editor should not
create a blank html document for providing feedback.
2018-06-15 14:13:21 +08:00
HungTran c5fd53af5d MDL-62014 course: Add title to activity completion button 2018-06-15 10:19:59 +07:00
Zig Tan e275b0c4a6 MDL-58702 backup: Fix restoration of role mappings labels
Changes:
- Add logic to retrieve all the existing current roles.
- Apply the backup's role shortname by matching on the archetype.
- Added backup lang string 'undefinedrolemapping'.
2018-06-15 09:56:15 +08:00
Matt Porritt 9482a77a04 MDL-62705 Global Search: results order selection does not display
As of 3.5 a search plugin can define the result orders it supports.
When a search plugin defines multiple sort orders and the search is
conducted without a context (no context query param in the URL),
the code to load the available sort orders from the enabled search
backend is never called.

This patch gets the supported search orders for the enabled search
engine regardless of context being provided or not. As prior, the
search order selection will not display if the only result order
option is relevance.
2018-06-15 11:32:28 +10:00
Luca Bösch fab105578b MDL-61894 tag: Modal buttons style. 2018-06-14 17:32:40 +02:00
Luca Bösch b4dcfe9a06 MDL-44791 admin: Open server check doc links in new window. 2018-06-14 17:31:45 +02:00
Ed Basset a31a20a5c6 MDL-36424 wiki: set group so subwiki tabs/pages display correctly 2018-06-14 17:31:28 +02:00
Huong Nguyen e6e7a85087 MDL-62358 Question: Improve number function
Support all 'question numbers' that might be needed
2018-06-14 19:46:36 +07:00
Huong Nguyen 612c718696 MDL-62597 Privacy: Request date column should include time 2018-06-14 19:41:28 +07:00
Eloy Lafuente (stronk7) 180988c999 weekly release 3.5+ 2018-06-14 10:56:06 +02:00
Eloy Lafuente (stronk7) 59380a9a64 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-06-14 10:56:04 +02:00
Mark Nelson d2593fadb6 MDL-62709 core_message: convert NULL format types in task 2018-06-14 13:19:17 +08:00
David M Penner 904aec69a0 MDL-62709 core_message: fix for silently ignored errors in task 2018-06-14 13:19:17 +08:00
Mark Nelson d851bb5c2a MDL-31443 core_backup: change review heading to 'Restore settings' 2018-06-13 18:39:00 +02:00
Luca Bösch 6fa61e69a7 MDL-31443 core_backup: revert changes to util/ui/renderer.php 2018-06-13 18:39:00 +02:00
Bas Brands c0e65fbbf8 MDL-62234 Theme boost: Improve atto dialogue styling 2018-06-13 11:37:09 +02:00
Jun Pataleta cc0b367755 Merge branch 'MDL59980_block_bug_35_STABLE' of git://github.com/kristian-94/moodle into MOODLE_35_STABLE 2018-06-13 09:59:26 +08:00
AMOS bot 1dc3e88cca Automatically generated installer lang files 2018-06-13 00:08:07 +00:00
Kristian Ringer d04e88b78f MDL-59980 core_admin: Fix bug preventing block modification in settings 2018-06-13 09:37:42 +10:00
Simey Lameze d77209e8c5 MDL-62446 mod_glossary: optimize provider code 2018-06-12 18:27:39 +02:00
Simey Lameze 3f27598353 MDL-62446 mod_glossary: improve privacy unit tests 2018-06-12 18:27:38 +02:00
David Monllao 334ced71a1 Merge branch 'MDL-58063-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-06-12 11:05:47 +02:00
Andrew Nicols f3a817e959 MDL-55609 mod_assign: Deprecate base_test 2018-06-12 10:22:18 +02:00
Andrew Nicols 9712975595 MDL-55609 assignfeedback_comments: Remove shared setUp 2018-06-12 10:22:11 +02:00
Andrew Nicols 4656db12e1 MDL-55609 mod_assign: Move fixture to folder 2018-06-12 10:22:06 +02:00
Andrew Nicols a1ec9e389e MDL-55609 mod_assign: Remove shared setUp for all tests 2018-06-12 10:22:01 +02:00
Andrew Nicols 3714b3d104 MDL-55609 testing: Add a create_and_enrol helper 2018-06-12 10:21:57 +02:00
Mark Nelson 251358b364 MDL-62519 atto_recordrtc: detach click listener when recording again 2018-06-12 14:37:40 +08:00
Jun Pataleta d2b6ce1555 Merge branch 'wip-MDL-62574-35' of git://github.com/abgreeve/moodle into MOODLE_35_STABLE 2018-06-12 11:56:53 +08:00
Adrian Greeve 3c22418e0a 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:26:23 +08:00
Adrian Greeve 94269e4505 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:26:11 +08:00
Andrew Nicols 0f272f06ea Merge branch 'wip_MDL-62576_M35' of git://github.com/gjb2048/moodle into MOODLE_35_STABLE 2018-06-12 10:49:14 +08:00
Andrew Nicols 08ca997c76 Merge branch 'MDL-62628-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-06-12 09:49:58 +08:00
Andrew Nicols 7d52baba45 Merge branch 'MDL-60819-35' of https://github.com/snake/moodle into MOODLE_35_STABLE 2018-06-12 09:21:04 +08:00
Eloy Lafuente (stronk7) 72f73d595d Merge branch 'MDL-62649-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-06-12 01:45:26 +02:00
Eloy Lafuente (stronk7) bf67536031 Merge branch 'MDL-62460-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-06-12 01:41:49 +02:00
Eloy Lafuente (stronk7) 7a78ae6c10 Merge branch 'MDL-31443-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-12 01:37:57 +02:00
Michael Aherne 2cdbfd1dca MDL-62577 unit tests: Quote class name in re-run command. 2018-06-12 00:08:37 +02:00
Mark Nelson d8924ce166 MDL-62571 mod_forum: fixed redirection URLs 2018-06-11 18:31:03 +08:00
David Monllao 9ccbf4386c Merge branch 'MDL-61484-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-06-11 10:34:30 +02:00
Jun Pataleta fe9d8a7274 MDL-61484 mod_chat: Add CHAT_SESSION_GAP constant 2018-06-11 16:01:03 +08:00
Jun Pataleta 9ec5fa2208 MDL-61484 mod_chat: Fix chat_get_sessions logic 2018-06-11 16:01:03 +08:00
Jun Pataleta bffc50ae9d MDL-61484 mod_chat: More unit tests for chat
* For chat_get_sessions()
* Fix externallib test
2018-06-11 16:01:03 +08:00
Leon Stringer a634c88ced MDL-61786 auth: Changed strings for Test Settings to lang strings
The output strings for the Test Settings function were hard-coded English
strings. They've been changed to standard language strings which can be
translated and maintained via AMOS.
2018-06-11 14:16:31 +08:00
Jun Pataleta 2eb7c6d6cb Merge branch 'MDL-62594-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-06-11 13:55:19 +08:00
Jake Dallimore 8ae9044ef4 MDL-60819 core_files: fix exception thrown from generate_image_thumbnail
This function now requires gdlib.php.
2018-06-11 10:45:27 +08:00
Andrew Nicols eef776a0b4 Merge branch 'MDL-62270-Cron-task-fails-on-data-privacy-related-task' of https://github.com/adamtppaw/moodle into MOODLE_35_STABLE 2018-06-11 09:53:06 +08:00
Luca Bösch ef57748905 MDL-31443 backup: Title and cancel messages stating import and restore 2018-06-08 11:34:06 +02:00
Bas Brands 9903e25871 MDL-62654 Theme Boost: update readme on @supports prefixer issues 2018-06-08 10:15:10 +02:00
Mihail Geshoski 557a9b5674 MDL-62628 resource: Folder name formatting issue when displayed inline 2018-06-08 08:36:04 +08:00
Mihail Geshoski 0ff993ffe3 MDL-58063 blocks: Cannot access manage files in HTML block in clean 2018-06-08 08:29:26 +08:00
Gareth J Barnard a8a9876c1d MDL-62576 theme_boost: Course jump to menu not centred 2018-06-07 20:43:42 +01:00
Eloy Lafuente (stronk7) fd308528cd weekly release 3.5+ 2018-06-07 17:00:05 +02:00
Eloy Lafuente (stronk7) b0f0c2af7e Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2018-06-07 17:00:02 +02:00
Bas Brands ba5dde0347 MDL-62638 Theme boost: remove broken css 2018-06-07 16:44:53 +02:00
Bas Brands 22cab09558 MDL-62594 question type: cloze question theme boost 2018-06-07 14:07:32 +02:00
Jun Pataleta b02f319b78 Merge branch 'MDL-61826-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-06-06 11:22:31 +02:00
Jun Pataleta e03ef397fc Merge branch 'MDL-46513_35' of git://github.com/markn86/moodle into MOODLE_35_STABLE 2018-06-06 11:22:27 +02:00
Jun Pataleta cddd2b461b Merge branch 'MDL-62586-35' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-06-06 11:22:24 +02:00
Andrew Nicols ec41f1ce1b Merge branch 'm35_MDL-61702_MariaDB_InnoDB10d3d1_Params_Removed' of https://github.com/scara/moodle into MOODLE_35_STABLE 2018-06-06 11:22:21 +02:00
Eloy Lafuente (stronk7) 7ce29a90af Merge branch 'MDL-39486-35' of git://github.com/mihailges/moodle into MOODLE_35_STABLE 2018-06-06 11:22:17 +02:00
David Monllao 4dda9579b3 Merge branch 'MDL-46344-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-06-06 11:22:14 +02:00
Mark Nelson 9b77f93fa0 MDL-62336 media_videojs: ensure videos have unique IDs 2018-06-06 11:22:11 +02:00
Jun Pataleta 4fc3adb35b Merge branch 'MDL-61778-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2018-06-06 11:22:08 +02:00
cescobedo d3e3f0f109 MDL-57968 core_message: Remove multiple unnecessary AJAX requests
Remove AJAX requests core_message_get_unread_conversations_count and
message_popup_get_unread_popup_notification_count and
render the unread messages and notifications into mustache template.
2018-06-06 11:00:54 +02:00
Mark Nelson 8bc2ee1a4a MDL-46513 mod_chat: pass has_capability result to fullname function 2018-06-06 15:34:17 +08:00
Jun Pataleta 93e51e74b4 MDL-61826 auth: Make Facebook endpoints more maintainable 2018-06-06 12:44:43 +08:00
Łukasz Szeremeta 148ef43015 MDL-61826 auth: Facebook OAuth2 - getting a better-quality profile photo
Get 200x200 px instead of 50x50 px user profile picture from Facebook OAuth2.
2018-06-06 12:44:43 +08:00
Mihail Geshoski 8b5dfd0b06 MDL-62649 course: Course and category management items displayed wrongly 2018-06-06 11:38:48 +08:00
Michael Hawkins 1ebd063f4e MDL-61778 message: Renamed online status icon and replaced Boost icon 2018-06-05 16:36:42 +08:00
Luca Bösch 40579dc143 MDL-62605 core_user: course name filter in unenrol modal. 2018-06-05 08:04:41 +02:00
AMOS bot f26629e665 Automatically generated installer lang files 2018-06-05 00:07:04 +00:00
adamtppaw ab96a452c6 MDL-62270 Privacy: Cron task fails when admin directory renamed 2018-06-03 21:59:15 +02:00
Luca Bösch 50e1bbb583 MDL-46344 outputrenderers: Localize 'This page is'. 2018-06-01 11:44:02 +02:00
Jun Pataleta 09b6fbdaca MDL-62460 tool_dataprivacy: Render selected user properly on validation 2018-06-01 16:09:17 +08:00
Matteo Scaramuccia 8f9c8a5826 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 22:56:49 +02:00
David Monllao 9c607c0b8c weekly release 3.5+ 2018-05-31 08:39:38 +02:00
Bas Brands 6dc178e448 MDL-62586 Themes: use Boostrap media compontent for author display 2018-05-30 12:41:36 +02:00
David Monllao 187d2a26c0 Merge branch 'MDL-62532-35' of git://github.com/rezaies/moodle into MOODLE_35_STABLE 2018-05-30 10:20:09 +02:00
Andrew Nicols c6755d6f34 Merge branch 'MDL-57486-35-fix' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-05-30 13:21:25 +08:00
Jun Pataleta bea2c40f53 MDL-57486 tool_recyclebin: Close the files recordset properly 2018-05-30 13:16:11 +08:00
Andrew Nicols 363622d03e Merge branch 'MDL-57600-35' of git://github.com/lameze/moodle into MOODLE_35_STABLE 2018-05-30 09:17:57 +08:00
Andrew Nicols 649c96c259 MDL-57486 tool_recyclebin: Coding style fix 2018-05-30 09:02:00 +08:00
Dan Marsden ac32f752d9 MDL-57486 tool_recyclebin: Delete items when context already deleted. 2018-05-30 09:02:00 +08:00
David Monllao db79038541 MDL-62381 editor_tinymce: Rearrange style modification 2018-05-29 16:36:14 +02:00
David Monllao c390ae4f8d Merge branch 'MDL-62381_moodle35' of https://github.com/Syxton/moodle into MOODLE_35_STABLE 2018-05-29 16:34:46 +02:00
Matt Davidson e0a8cfdb6d MDL-62381 editor: Fix tinymce icons
Icon sizing is being overwritten on images.
2018-05-29 09:03:00 -04:00
Jun Pataleta 52f03c6fc6 Merge branch 'MDL-62544-35' of git://github.com/Dagefoerde/moodle into MOODLE_35_STABLE 2018-05-29 16:24:26 +08:00
Michael Hawkins 9f2c4a9277 MDL-56498 message: Ensure clicked notifications are marked read 2018-05-29 09:37:53 +08:00
David Monllao 575e209b93 Merge branch 'MDL-62316_oauth_guess_image_fix_35_STABLE' of https://github.com/leonstr/moodle into MOODLE_35_STABLE 2018-05-28 16:48:50 +02:00
David Monllao 0b363e6c4b Merge branch 'wip-MDL-62584-35' of git://github.com/marinaglancy/moodle into MOODLE_35_STABLE 2018-05-28 16:32:17 +02:00
Eloy Lafuente (stronk7) 5d8821ae05 MDL-62478 upgrade: add 3.5.0 separation line to all upgrade scripts 2018-05-28 13:16:21 +02:00
David Monllao 512a916207 Merge branch 'MDL-42779-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-05-28 11:42:53 +02:00
Mark Nelson 86a7cf0909 MDL-57742 core_lib: Updated readme_moodle.txt 2018-05-28 09:17:18 +02:00
David Balch f93f81a9f6 MDL-57742 core_lib: Make tool_consumer_instance_guid optional
In the specification, tool_consumer_instance_guid is only listed
as recommended. However, the library was requiring it.

https://www.imsglobal.org/specs/ltiv2p0/implementation-guide#toc-42
2018-05-28 09:17:17 +02:00
Matteo Scaramuccia bf6f0f03cf MDL-59596 ddl: Support MariaDB 10.2.4+ SQL_MODE new default value. 2018-05-28 08:20:14 +02:00
Matteo Scaramuccia 903182e4a3 MDL-59596 ddl: Added has_breaking_change_sqlmode() 2018-05-28 08:20:12 +02:00
Jun Pataleta e7554e4b67 Merge branch 'wip-MDL-62493-35' of git://github.com/marinaglancy/moodle into MOODLE_35_STABLE 2018-05-28 12:57:37 +08:00
Jun Pataleta 167420bd2e MDL-62515 templates: Remove unnecessary button role for submit button 2018-05-28 12:29:59 +08:00
Marina Glancy f479887c77 MDL-62584 xmlrpc: return correct content-length 2018-05-28 11:20:21 +08:00
Simey Lameze 5441c8fef4 MDL-57600 course: fix typo on courselib test 2018-05-28 09:41:58 +08:00
Alex b104cede75 MDL-57600 format: Fix spelling errors 2018-05-28 09:41:57 +08:00
Jun Pataleta c3c8223ca2 Merge branch 'MDL-61832-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-05-28 09:21:22 +08:00
Andrew Nicols d4773c32d6 MDL-62543 mod_forum: Add RSS Unit tests 2018-05-28 08:56:44 +08:00
Tobias Reischmann c8511e2808 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-28 08:56:44 +08:00
Shamim Rezaie b23e6519fe MDL-62532 core_question: Include standard tags in the tags autocomplete 2018-05-28 10:55:05 +10:00
Mark Johnson dc7c48a9d7 MDL-62408 behat: Correct PHPUNIT_TEST checks in behat_config_util 2018-05-28 02:14:08 +02:00
Marina Glancy a210233bfb MDL-62493 tool_policy: correct redirection
do not override session->wantsurl
do not redirect guests to user.php, they get an error there
2018-05-25 14:32:03 +08:00
Marina Glancy 9dcf28af7d MDL-62493 tool_policy: allow guests to sign up 2018-05-25 14:32:03 +08:00
Mihail Geshoski c030a94b68 MDL-39486 lesson: Length of label text too narrow in matching questions 2018-05-25 09:46:01 +08:00
Jan Dageförde bd8b3a91e0 MDL-62544 privacy: error notification type 2018-05-24 16:46:02 +02:00
Jan Dageförde 582eb6e6e2 MDL-62544 privacy: Allow data requests on behalf of a user by admin/DPO 2018-05-24 16:46:02 +02:00
Luca Bösch 9b127bcb70 MDL-61832 lesson: don't hide lesson page editing options 2018-05-24 15:44:00 +02:00
Luca Bösch 171a764049 MDL-42779 course: Pagination in management page preserves view action 2018-05-24 15:43:41 +02:00
David Monllao 0da8ef69a8 weekly release 3.5+ 2018-05-24 10:46:02 +02:00
David Monllao 815be4e003 NOBUG: Fixed SVG browser compatibility 2018-05-24 10:46:01 +02:00
Simey Lameze d897846115 MDL-61189 editor_tinymce: save data before submitting 2018-05-23 10:48:40 +08:00
Jake Dallimore 5427855d50 Merge branch 'MDL-61932-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2018-05-23 10:01:46 +08:00
Leon Stringer 6d7093e370 MDL-62316 OAuth 2: Only guess image if base URL set.
If you added an OAuth 2 service with no base URL guess_image() would try to
parse this empty value looking for a favicon.ico.  We now check if the base
URL is empty beforehand and skip this if so.
2018-05-22 19:45:04 +01:00
Jun Pataleta d7c805a9f0 MDL-61932 mod_glossary: Fetch the correct front page section 1 ID 2018-05-22 17:16:30 +08:00
Jun Pataleta 2032603ae9 MDL-61932 mod_glossary: Display site-level glossaries on section 1
* Glossary activities created on the front page by importing
entries are being added to section 0, but the front page only shows
activities on section 1.
2018-05-22 17:16:29 +08:00
John Beedell e7e91b8bae MDL-62239 QTYPE: iOS 11.3 broke Moodle drag-drop question types 2018-05-22 10:05:51 +08:00
Andrew Nicols 30cbfbdd61 Merge branch 'MDL-62482_35' of git://github.com/timhunt/moodle into MOODLE_35_STABLE 2018-05-22 09:39:17 +08:00
Andrew Nicols ab7694d82e Merge branch 'MDL-62488_35_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_35_STABLE 2018-05-22 09:31:19 +08:00
Jake Dallimore 61ee9a304e Merge branch 'MDL-61015-35' of https://github.com/lucaboesch/moodle into MOODLE_35_STABLE 2018-05-22 08:15:11 +08:00
Andrew Nicols f59bc1b152 MDL-62500 tag: Update checkbox label when updating tag 2018-05-21 13:46:49 +02:00
Matteo Scaramuccia 3aa57f20af 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-21 12:05:54 +02:00
Matteo Scaramuccia b875ad18b5 MDL-61893 JavaScript: Accept any node version but Carbon (LTS) 2018-05-21 12:05:46 +02:00
Luca Bösch 2d69b0fbe7 MDL-61015 course: consistently display hidden sections
This commit moves the availability info on top.
2018-05-21 11:34:04 +02:00
Marina Glancy c8663da1a9 MDL-61015 course: consistently display hidden sections
When section is hidden but the course setting says to show hidden sections display the hidden
sections consistently with how we display the sections with access restriction.

The only difference is that we didn't previously display the summary of the hidden section
in this case, the new logic repeats this behavior
2018-05-21 11:34:04 +02:00
Andrew Nicols 1de2735b16 MDL-62516 mod_forum: Only delete files for requested user 2018-05-21 11:17:10 +02:00
Juan Leyva 4a5adabe95 MDL-62229 tool_mobile: Add missing supported module
The Workshop module it’s been supported since Moodle 3.4 so it
should’ve been included here.
2018-05-21 17:05:07 +08:00
Andrew Nicols b566d8002a Merge branch 'wip-MDL-62481-35' of git://github.com/marinaglancy/moodle into MOODLE_35_STABLE 2018-05-21 15:55:12 +08:00
Tim Hunt 2e404995a0 MDL-62440 participants: out-of-memory is many site-wide role assigns 2018-05-21 14:55:42 +08:00
Andrew Nicols 654ad71526 Merge branch 'MDL-62386-master' of git://github.com/bmbrands/moodle into MOODLE_35_STABLE 2018-05-21 09:11:28 +08:00
Marina Glancy 2b50975563 MDL-62481 atto_recordrtc: add svg icons 2018-05-18 10:40:33 +08:00
Mark Johnson 81fb99ed66 MDL-62488 filebrowser: Stop unit tests assuming number of categories 2018-05-17 16:01:36 +01:00
Tim Hunt deb358f16c MDL-62482 file_storage: may have many files with same content hash 2018-05-17 11:25:52 +01:00
Bas Brands 881098f39c MDL-62386 theme: Fix audio playback display for HTML audio
Fixed for boost
Fixed for bootstrapbase
2018-05-11 14:51:04 +02:00
1368 changed files with 79486 additions and 12848 deletions
+1
View File
@@ -2,6 +2,7 @@
theme/bootstrapbase/style/
theme/clean/style/custom.css
theme/more/style/custom.css
theme/boost/style/moodle.css
node_modules/
vendor/
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
+3 -1
View File
@@ -56,7 +56,7 @@ matrix:
include:
# Run grunt/npm install on highest version ('node' is an alias for the latest node.js version.)
- php: 7.2
env: DB=none TASK=GRUNT NVM_VERSION='8.9'
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
exclude:
# MySQL - it's just too slow.
@@ -263,7 +263,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
+14 -2
View File
@@ -137,6 +137,16 @@ module.exports = function(grunt) {
}
}
},
sass: {
dist: {
files: {
"theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss"
}
},
options: {
includePaths: ["theme/boost/scss/"]
}
},
watch: {
options: {
nospawn: true // We need not to spawn so config can be changed dynamically.
@@ -214,7 +224,8 @@ module.exports = function(grunt) {
'# Generated by "grunt ignorefiles"',
'theme/bootstrapbase/style/',
'theme/clean/style/custom.css',
'theme/more/style/custom.css'
'theme/more/style/custom.css',
'theme/boost/style/moodle.css'
].concat(thirdPartyPaths);
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
};
@@ -362,6 +373,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-stylelint');
@@ -374,7 +386,7 @@ module.exports = function(grunt) {
grunt.registerTask('js', ['amd', 'yui']);
// Register CSS taks.
grunt.registerTask('css', ['stylelint:scss', 'stylelint:less', 'less:bootstrapbase', 'stylelint:css']);
grunt.registerTask('css', ['stylelint:scss', 'sass', 'stylelint:less', 'less:bootstrapbase', 'stylelint:css']);
// Register the startup task.
grunt.registerTask('startup', 'Run the correct tasks for the current directory', tasks.startup);
+364
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,366 @@
</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>
<MOODLE version="3.7" requires="3.2">
<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.1.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="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>
+3 -2
View File
@@ -40,8 +40,9 @@ if ($unregistration && \core\hub\registration::is_registered()) {
if ($siteunregistrationform->is_cancelled()) {
redirect(new moodle_url('/admin/registration/index.php'));
} else if ($data = $siteunregistrationform->get_data()) {
if (\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
$data->unpublishalluploadedcourses)) {
\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
$data->unpublishalluploadedcourses);
if (!\core\hub\registration::is_registered()) {
redirect(new moodle_url('/admin/registration/index.php'));
}
}
+1 -1
View File
@@ -1990,7 +1990,7 @@ class core_admin_renderer extends plugin_renderer_base {
if (empty($CFG->docroot) or $environment_result->plugin) {
$report = get_string($stringtouse, 'admin', $rec);
} else {
$report = $this->doc_link(join($linkparts, '/'), get_string($stringtouse, 'admin', $rec));
$report = $this->doc_link(join($linkparts, '/'), get_string($stringtouse, 'admin', $rec), true);
}
// Enclose report text in div so feedback text will be displayed underneath it.
$report = html_writer::div($report);
+85 -5
View File
@@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for core_role.
*
@@ -20,7 +21,9 @@
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_role\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
@@ -28,6 +31,8 @@ use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\userlist;
use \core_privacy\local\request\approved_userlist;
/**
* Privacy provider for core_role.
@@ -39,7 +44,8 @@ class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\provider,
\core_privacy\local\request\subsystem\plugin_provider,
\core_privacy\local\request\user_preference_provider {
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\core_userlist_provider {
/**
* Get information about the user data stored by this plugin.
@@ -151,6 +157,50 @@ class provider implements
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
if (empty($userlist)) {
return;
}
$context = $userlist->get_context();
// Include users who created or modified role capabilities.
$sql = "SELECT modifierid as userid
FROM {role_capabilities}
WHERE contextid = :contextid";
$params = [
'contextid' => $context->id
];
$userlist->add_from_sql('userid', $sql, $params);
// Include users that have a role assigned to them.
$sql = "SELECT userid
FROM {role_assignments}
WHERE contextid = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
// Include users who created or modified the role assignment.
// Differentiate and exclude special cases where tool_cohortroles adds records through the
// "Assign user roles to cohort" feature into the role_assignments table.
// These records should be separately processed in tool_cohortroles.
$sql = "SELECT modifierid as userid
FROM {role_assignments}
WHERE contextid = :contextid
AND component != 'tool_cohortroles'";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
@@ -315,6 +365,32 @@ class provider implements
// Remove data from role_assignments.
$DB->delete_records('role_assignments', ['contextid' => $context->id]);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
// Don't remove data from role_capabilities.
// Because this data affects the whole Moodle, there are override capabilities.
// Don't belong to the modifier user.
$context = $userlist->get_context();
$userids = $userlist->get_userids();
if (empty($userids)) {
return;
}
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params = ['contextid' => $context->id] + $userparams;
// Remove data from role_assignments.
$DB->delete_records_select('role_assignments',
"contextid = :contextid AND userid {$usersql}", $params);
}
/**
* Delete all user data for this user only.
*
@@ -332,10 +408,14 @@ class provider implements
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
// Only delete the roles assignments where the user is assigned in all contexts.
$DB->delete_records('role_assignments', ['userid' => $userid, 'contextid' => $context->id]);
}
$contextids = $contextlist->get_contextids();
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$params = ['userid' => $userid] + $contextparams;
// Only delete the roles assignments where the user is assigned in all contexts.
$DB->delete_records_select('role_assignments',
"userid = :userid AND contextid {$contextsql}", $params);
}
/**
* Delete user entries in role_assignments related to the feature
+270
View File
@@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy test for core_role
*
@@ -21,13 +22,16 @@
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \core_role\privacy\provider;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\writer;
use \core_privacy\tests\provider_testcase;
use \core_privacy\local\request\transform;
use \tool_cohortroles\api;
use \core_privacy\local\request\approved_userlist;
/**
* Privacy test for core_role
@@ -54,6 +58,7 @@ class core_role_privacy_testcase extends provider_testcase {
$this->assertEquals(get_string('privacy:metadata:preference:showadvanced', 'core_role'),
$prefs->definerole_showadvanced->description);
}
/**
* Check all contexts are returned if there is any user data for this user.
*/
@@ -231,6 +236,7 @@ class core_role_privacy_testcase extends provider_testcase {
}
}
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
@@ -316,6 +322,7 @@ class core_role_privacy_testcase extends provider_testcase {
$count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
@@ -373,6 +380,7 @@ class core_role_privacy_testcase extends provider_testcase {
$count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
$this->assertEquals(2, $count);
}
/**
* Export for a user with a key against a script where no instance is specified.
*/
@@ -426,6 +434,7 @@ class core_role_privacy_testcase extends provider_testcase {
$exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
$this->assertEquals($user2->id, reset($exported)->userid);
}
/**
* Test for provider::delete_user_role_to_cohort().
*/
@@ -460,6 +469,267 @@ class core_role_privacy_testcase extends provider_testcase {
$count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
$this->assertEquals(0, $count);
}
/**
* Test that only users within a course context are fetched.
*/
public function test_get_users_in_context() {
global $DB;
$this->resetAfterTest();
$component = 'core_role';
$this->setAdminUser();
$admin = \core_user::get_user_by_username('admin');
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
$usercontext1 = \context_user::instance($user1->id);
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
$usercontext2 = \context_user::instance($user2->id);
// Create course1.
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
// Create course category.
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = \context_coursecat::instance($coursecat->id);
// Create chat module.
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course1->id]);
$cmcontext = \context_module::instance($cm->cmid);
$systemcontext = \context_system::instance();
// Create a block.
$block = $this->getDataGenerator()->create_block('online_users');
$blockcontext = \context_block::instance($block->id);
$studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
$managerrole = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
// Role assignments CONTEXT_COURSE.
role_assign($studentrole->id, $user1->id, $coursecontext1->id);
role_assign($studentrole->id, $user2->id, $coursecontext1->id);
// Role assignments CONTEXT_COURSECAT.
role_assign($studentrole->id, $user2->id, $coursecatcontext->id);
// Role assignments CONTEXT_SYSTEM.
role_assign($studentrole->id, $user1->id, $systemcontext->id);
// Role assignments CONTEXT_MODULE.
role_assign($studentrole->id, $user2->id, $cmcontext->id);
// Role assigments CONTEXT_BLOCK.
role_assign($studentrole->id, $user1->id, $blockcontext->id);
// Role assigments CONTEXT_USER.
role_assign($managerrole->id, $user1->id, $usercontext2->id);
// Role capabilities.
$this->setUser($user1);
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $studentrole->id, $cmcontext->id);
// The user list for usercontext1 should not return any users.
$userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
// The user list for usercontext2 should user1 and admin (role creator).
$userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(2, $userlist2);
$expected = [
$user1->id,
$admin->id
];
$this->assertEquals($expected, $userlist2->get_userids(), '', 0.0, 10, true);
// The user list for coursecontext1 should user1, user2 and admin (role creator).
$userlist3 = new \core_privacy\local\request\userlist($coursecontext1, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(3, $userlist3);
$expected = [
$user1->id,
$user2->id,
$admin->id
];
$this->assertEquals($expected, $userlist3->get_userids(), '', 0.0, 10, true);
// The user list for coursecatcontext should user2 and admin (role creator).
$userlist4 = new \core_privacy\local\request\userlist($coursecatcontext, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(2, $userlist4);
$expected = [
$user2->id,
$admin->id
];
$this->assertEquals($expected, $userlist4->get_userids(), '', 0.0, 10, true);
// The user list for systemcontext should user1 and admin (role creator).
$userlist6 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist6);
$this->assertCount(2, $userlist6);
$expected = [
$user1->id,
$admin->id
];
$this->assertEquals($expected, $userlist6->get_userids(), '', 0.0, 10, true);
// The user list for cmcontext should user1, user2 and admin (role creator).
$userlist7 = new \core_privacy\local\request\userlist($cmcontext, $component);
provider::get_users_in_context($userlist7);
$this->assertCount(3, $userlist7);
$expected = [
$user1->id,
$user2->id,
$admin->id
];
$this->assertEquals($expected, $userlist7->get_userids(), '', 0.0, 10, true);
// The user list for blockcontext should user1 and admin (role creator).
$userlist8 = new \core_privacy\local\request\userlist($blockcontext, $component);
provider::get_users_in_context($userlist8);
$this->assertCount(2, $userlist8);
$expected = [
$user1->id,
$admin->id
];
$this->assertEquals($expected, $userlist8->get_userids(), '', 0.0, 10, true);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users() {
global $DB;
$this->resetAfterTest();
$component = 'core_role';
$this->setAdminUser();
$admin = \core_user::get_user_by_username('admin');
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
$usercontext2 = \context_user::instance($user2->id);
// Create course1.
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
// Create course category.
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = \context_coursecat::instance($coursecat->id);
// Create chat module.
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course1->id]);
$cmcontext = \context_module::instance($cm->cmid);
$systemcontext = \context_system::instance();
// Create a block.
$block = $this->getDataGenerator()->create_block('online_users');
$blockcontext = \context_block::instance($block->id);
$studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
$managerrole = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
// Role assignments CONTEXT_COURSE.
role_assign($studentrole->id, $user1->id, $coursecontext1->id);
role_assign($studentrole->id, $user2->id, $coursecontext1->id);
// Role assignments CONTEXT_COURSECAT.
role_assign($studentrole->id, $user2->id, $coursecatcontext->id);
// Role assignments CONTEXT_SYSTEM.
role_assign($studentrole->id, $user1->id, $systemcontext->id);
// Role assignments CONTEXT_MODULE.
role_assign($studentrole->id, $user2->id, $cmcontext->id);
// Role assigments CONTEXT_BLOCK.
role_assign($studentrole->id, $user1->id, $blockcontext->id);
// Role assigments CONTEXT_USER.
role_assign($managerrole->id, $user1->id, $usercontext2->id);
// Role capabilities.
$this->setUser($user1);
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $studentrole->id, $cmcontext->id);
// The user list for usercontext2 should user1 and admin (role creator).
$userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(2, $userlist1);
// The user list for coursecontext1 should user1, user2 and admin (role creator).
$userlist2 = new \core_privacy\local\request\userlist($coursecontext1, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(3, $userlist2);
// The user list for coursecatcontext should user2 and admin (role creator).
$userlist3 = new \core_privacy\local\request\userlist($coursecatcontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(2, $userlist3);
// The user list for systemcontext should user1 and admin (role creator).
$userlist4 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(2, $userlist4);
// The user list for cmcontext should user1, user2 and admin (role creator).
$userlist5 = new \core_privacy\local\request\userlist($cmcontext, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(3, $userlist5);
// The user list for blockcontext should user1 and admin (role creator).
$userlist6 = new \core_privacy\local\request\userlist($blockcontext, $component);
provider::get_users_in_context($userlist6);
$this->assertCount(2, $userlist6);
// Convert $userlist1 into an approved_contextlist.
$approvedlist1 = new approved_userlist($usercontext2, $component, $userlist1->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist1);
// Re-fetch users in usercontext2.
$userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
// Convert $userlist2 into an approved_contextlist.
$approvedlist2 = new approved_userlist($coursecontext1, $component, $userlist2->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist2);
// Re-fetch users in coursecontext1.
$userlist2 = new \core_privacy\local\request\userlist($coursecontext1, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
// Convert $userlist3 into an approved_contextlist.
$approvedlist3 = new approved_userlist($coursecatcontext, $component, $userlist3->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist3);
// Re-fetch users in coursecatcontext.
$userlist3 = new \core_privacy\local\request\userlist($coursecatcontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
// Convert $userlist4 into an approved_contextlist.
$approvedlist4 = new approved_userlist($systemcontext, $component, $userlist4->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist4);
// Re-fetch users in systemcontext.
$userlist4 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist4);
// The data from role_capabilities should still be present. The user list should return the admin user.
$this->assertCount(1, $userlist4);
$expected = [$admin->id];
$this->assertEquals($expected, $userlist4->get_userids());
// Convert $userlist5 into an approved_contextlist.
$approvedlist5 = new approved_userlist($cmcontext, $component, $userlist5->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist5);
// Re-fetch users in cmcontext.
$userlist5 = new \core_privacy\local\request\userlist($cmcontext, $component);
provider::get_users_in_context($userlist5);
// The data from role_capabilities should still be present. The user list should return user1.
$this->assertCount(1, $userlist5);
$expected = [$user1->id];
$this->assertEquals($expected, $userlist5->get_userids());
// Convert $userlist6 into an approved_contextlist.
$approvedlist6 = new approved_userlist($blockcontext, $component, $userlist6->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist6);
// Re-fetch users in blockcontext.
$userlist6 = new \core_privacy\local\request\userlist($blockcontext, $component);
provider::get_users_in_context($userlist6);
$this->assertCount(0, $userlist6);
}
/**
* Supoort function to get all the localised roles name
* in a simple array for testing.
+111 -84
View File
@@ -28,113 +28,139 @@ admin_externalpage_setup('searchareas');
$areaid = optional_param('areaid', null, PARAM_ALPHAEXT);
$action = optional_param('action', null, PARAM_ALPHA);
$indexingenabled = \core_search\manager::is_indexing_enabled(); // This restricts many of the actions on this page.
// Get a search manager instance, which we'll need for display and to handle some actions.
try {
$searchmanager = \core_search\manager::instance();
} catch (core_search\engine_exception $searchmanagererror) {
// Continue, we return an error later depending on the requested action.
// In action cases, we'll throw this exception below. In non-action cases, we produce a lang string error.
}
// Handle all the actions.
if ($action) {
// If dealing with an areaid, we need to check that the area exists.
if ($areaid) {
// We need to check that the area exists.
$area = \core_search\manager::get_search_area($areaid);
if ($area === false) {
throw new moodle_exception('invalidrequest');
}
}
if ($action !== 'enable' && $action !== 'disable') {
// All actions but enable/disable need the search engine to be ready.
if (!empty($searchmanagererror)) {
throw $searchmanagererror;
}
// All the indexing actions.
if (in_array($action, ['delete', 'indexall', 'reindexall', 'deleteall'])) {
// Show confirm prompt for all these actions as they may be inadvisable, or may cause
// an interruption in search functionality, on production systems.
if (!optional_param('confirm', 0, PARAM_INT)) {
// Display confirmation prompt.
$a = null;
if ($areaid) {
$a = html_writer::tag('strong', $area->get_visible_name());
// All of these actions require that indexing is enabled.
if ($indexingenabled) {
// For all of these actions, we strictly need a manager instance.
if (isset($searchmanagererror)) {
throw $searchmanagererror;
}
$actionparams = ['sesskey' => sesskey(), 'action' => $action, 'confirm' => 1];
if ($areaid) {
$actionparams['areaid'] = $areaid;
}
$actionurl = new moodle_url('/admin/searchareas.php', $actionparams);
$cancelurl = new moodle_url('/admin/searchareas.php');
echo $OUTPUT->header();
echo $OUTPUT->confirm(get_string('confirm_' . $action, 'search', $a),
// Show confirm prompt for all these actions as they may be inadvisable, or may cause
// an interruption in search functionality, on production systems.
if (!optional_param('confirm', 0, PARAM_INT)) {
// Display confirmation prompt.
$a = null;
if ($areaid) {
$a = html_writer::tag('strong', $area->get_visible_name());
}
$actionparams = ['sesskey' => sesskey(), 'action' => $action, 'confirm' => 1];
if ($areaid) {
$actionparams['areaid'] = $areaid;
}
$actionurl = new moodle_url('/admin/searchareas.php', $actionparams);
$cancelurl = new moodle_url('/admin/searchareas.php');
echo $OUTPUT->header();
echo $OUTPUT->confirm(get_string('confirm_' . $action, 'search', $a),
new single_button($actionurl, get_string('continue'), 'post', true),
new single_button($cancelurl, get_string('cancel'), 'get'));
echo $OUTPUT->footer();
exit;
echo $OUTPUT->footer();
exit;
} else {
// Confirmed, so run the required action.
require_sesskey();
switch ($action) {
case 'delete':
$searchmanager->delete_index($areaid);
\core\notification::success(get_string('searchindexdeleted', 'admin'));
break;
case 'indexall':
$searchmanager->index();
\core\notification::success(get_string('searchindexupdated', 'admin'));
break;
case 'reindexall':
$searchmanager->index(true);
\core\notification::success(get_string('searchreindexed', 'admin'));
break;
case 'deleteall':
$searchmanager->delete_index();
\core\notification::success(get_string('searchalldeleted', 'admin'));
break;
default:
break;
}
// Redirect back to the main page after taking action.
redirect(new moodle_url('/admin/searchareas.php'));
}
}
} else if (in_array($action, ['enable', 'disable'])) {
// Toggling search areas requires no confirmation.
require_sesskey();
switch ($action) {
case 'enable':
$area->set_enabled(true);
\core\notification::success(get_string('searchareaenabled', 'admin'));
break;
case 'disable':
$area->set_enabled(false);
core\notification::success(get_string('searchareadisabled', 'admin'));
break;
default:
break;
}
redirect(new moodle_url('/admin/searchareas.php'));
} else {
// Invalid action.
throw new moodle_exception('invalidaction');
}
}
// We are now taking an actual action, so require sesskey.
require_sesskey();
switch ($action) {
case 'enable':
$area->set_enabled(true);
\core\notification::add(get_string('searchareaenabled', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
break;
case 'disable':
$area->set_enabled(false);
\core\notification::add(get_string('searchareadisabled', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
break;
case 'delete':
$search = \core_search\manager::instance();
$search->delete_index($areaid);
\core\notification::add(get_string('searchindexdeleted', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
break;
case 'indexall':
$searchmanager->index();
\core\notification::add(get_string('searchindexupdated', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
break;
case 'reindexall':
$searchmanager->index(true);
\core\notification::add(get_string('searchreindexed', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
break;
case 'deleteall':
$searchmanager->delete_index();
\core\notification::add(get_string('searchalldeleted', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
break;
default:
throw new moodle_exception('invalidaction');
break;
}
// Redirect back to the main page after taking action.
redirect(new moodle_url('/admin/searchareas.php'));
// Display.
if (isset($searchmanager) && $indexingenabled) {
\core\notification::info(get_string('indexinginfo', 'admin'));
} else if (isset($searchmanager)) {
$params = (object) [
'url' => (new moodle_url("/admin/settings.php?section=manageglobalsearch#admin-searchindexwhendisabled"))->out(false)
];
\core\notification::error(get_string('indexwhendisabledfullnotice', 'search', $params));
} else {
// In non-action cases, init errors are translated and displayed to the user as error notifications.
$errorstr = get_string($searchmanagererror->errorcode, $searchmanagererror->module, $searchmanagererror->a);
\core\notification::error($errorstr);
}
echo $OUTPUT->header();
$searchareas = \core_search\manager::get_search_areas_list();
if (empty($searchmanagererror)) {
$areasconfig = $searchmanager->get_areas_config($searchareas);
} else {
$areasconfig = false;
}
if (!empty($searchmanagererror)) {
$errorstr = get_string($searchmanagererror->errorcode, $searchmanagererror->module, $searchmanagererror->a);
echo $OUTPUT->notification($errorstr, \core\output\notification::NOTIFY_ERROR);
} else {
echo $OUTPUT->notification(get_string('indexinginfo', 'admin'), \core\output\notification::NOTIFY_INFO);
}
$table = new html_table();
$table->id = 'core-search-areas';
$table->head = [
get_string('searcharea', 'search'),
get_string('enable'),
get_string('newestdocindexed', 'admin'),
get_string('searchlastrun', 'admin'),
get_string('searchindexactions', 'admin')
];
$table->head = array(get_string('searcharea', 'search'), get_string('enable'), get_string('newestdocindexed', 'admin'),
get_string('searchlastrun', 'admin'), get_string('searchindexactions', 'admin'));
$searchareas = \core_search\manager::get_search_areas_list();
$areasconfig = isset($searchmanager) ? $searchmanager->get_areas_config($searchareas) : false;
foreach ($searchareas as $area) {
$areaid = $area->get_area_id();
$columns = array(new html_table_cell($area->get_visible_name()));
@@ -144,7 +170,7 @@ foreach ($searchareas as $area) {
new pix_icon('t/hide', get_string('disable'), 'moodle', array('title' => '', 'class' => 'iconsmall')),
null, array('title' => get_string('disable')));
if ($areasconfig) {
if ($areasconfig && $indexingenabled) {
$columns[] = $areasconfig[$areaid]->lastindexrun;
if ($areasconfig[$areaid]->indexingstart) {
@@ -173,7 +199,11 @@ foreach ($searchareas as $area) {
$columns[] = html_writer::alist($actions, ['class' => 'unstyled list-unstyled']);
} else {
$blankrow = new html_table_cell(get_string('searchnotavailable', 'admin'));
if (!$areasconfig) {
$blankrow = new html_table_cell(get_string('searchnotavailable', 'admin'));
} else {
$blankrow = new html_table_cell(get_string('indexwhendisabledshortnotice', 'search'));
}
$blankrow->colspan = 3;
$columns[] = $blankrow;
}
@@ -192,10 +222,7 @@ foreach ($searchareas as $area) {
}
// Cross-search area tasks.
$options = array();
if (!empty($searchmanagererror)) {
$options['disabled'] = true;
}
$options = (isset($searchmanager) && $indexingenabled) ? [] : ['disabled' => true];
echo $OUTPUT->box_start('search-areas-actions');
echo $OUTPUT->single_button(admin_searcharea_action_url('indexall'), get_string('searchupdateindex', 'admin'), 'get', $options);
echo $OUTPUT->single_button(admin_searcharea_action_url('reindexall'), get_string('searchreindexindex', 'admin'), 'get', $options);
@@ -204,7 +231,7 @@ echo $OUTPUT->box_end();
echo html_writer::table($table);
if (empty($searchmanagererror)) {
if (isset($searchmanager)) {
// Show information about queued index requests for specific contexts.
$searchrenderer = $PAGE->get_renderer('core_search');
echo $searchrenderer->render_index_requests_info($searchmanager->get_index_requests_info());
+2 -1
View File
@@ -38,7 +38,8 @@ if (!($settingspage->check_access())) {
$statusmsg = '';
$errormsg = '';
if ($data = data_submitted() and confirm_sesskey()) {
// Form is submitted with changed settings. Do not want to execute when modifying a block.
if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and $data->action == 'save-settings') {
$count = admin_write_settings($data);
// Regardless of whether any setting change was written (a positive count), check validation errors for those that didn't.
+22 -4
View File
@@ -15,10 +15,28 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$optionalsubsystems->add(new admin_setting_configcheckbox('messaging', new lang_string('messaging', 'admin'), new lang_string('configmessaging','admin'), 1));
$optionalsubsystems->add(new admin_setting_configcheckbox('messaginghidereadnotifications', new lang_string('messaginghidereadnotifications', 'admin'), new lang_string('configmessaginghidereadnotifications','admin'), 0));
$options = array(DAYSECS=>new lang_string('secondstotime86400'), WEEKSECS=>new lang_string('secondstotime604800'), 2620800=>new lang_string('nummonths', 'moodle', 1), 15724800=>new lang_string('nummonths', 'moodle', 6),0=>new lang_string('never'));
$optionalsubsystems->add(new admin_setting_configselect('messagingdeletereadnotificationsdelay', new lang_string('messagingdeletereadnotificationsdelay', 'admin'), new lang_string('configmessagingdeletereadnotificationsdelay', 'admin'), 604800, $options));
$options = array(
DAYSECS => new lang_string('secondstotime86400'),
WEEKSECS => new lang_string('secondstotime604800'),
2620800 => new lang_string('nummonths', 'moodle', 1),
7862400 => new lang_string('nummonths', 'moodle', 3),
15724800 => new lang_string('nummonths', 'moodle', 6),
0 => new lang_string('never')
);
$optionalsubsystems->add(new admin_setting_configselect(
'messagingdeletereadnotificationsdelay',
new lang_string('messagingdeletereadnotificationsdelay', 'admin'),
new lang_string('configmessagingdeletereadnotificationsdelay', 'admin'),
604800,
$options)
);
$optionalsubsystems->add(new admin_setting_configselect(
'messagingdeleteallnotificationsdelay',
new lang_string('messagingdeleteallnotificationsdelay', 'admin'),
new lang_string('configmessagingdeleteallnotificationsdelay', 'admin'),
2620800,
$options)
);
$optionalsubsystems->add(new admin_setting_configcheckbox('messagingallowemailoverride', new lang_string('messagingallowemailoverride', 'admin'), new lang_string('configmessagingallowemailoverride','admin'), 0));
+1
View File
@@ -40,6 +40,7 @@
<div class="settingsform clearfix">
{{#params}}
<input type="hidden" name="{{name}}" value="{{value}}">
<input type="hidden" name="action" value="save-settings">
{{/params}}
<input type="hidden" name="sesskey" value="{{sesskey}}">
<input type="hidden" name="return" value="{{return}}">
+6 -6
View File
@@ -23,29 +23,29 @@
*/
$string['aim'] = 'This administration tool helps developers and test writers to create .feature files describing Moodle\'s functionalities and run them automatically. Step definitions available for use in .feature files are listed below.';
$string['allavailablesteps'] = 'All the available steps definitions';
$string['allavailablesteps'] = 'All available step definitions';
$string['errorbehatcommand'] = 'Error running behat CLI command. Try running "{$a} --help" manually from CLI to find out more about the problem.';
$string['errorcomposer'] = 'Composer dependencies are not installed.';
$string['errordataroot'] = '$CFG->behat_dataroot is not set or is invalid.';
$string['errorsetconfig'] = '$CFG->behat_dataroot, $CFG->behat_prefix and $CFG->behat_wwwroot need to be set in config.php.';
$string['erroruniqueconfig'] = '$CFG->behat_dataroot, $CFG->behat_prefix and $CFG->behat_wwwroot values need to be different than $CFG->dataroot, $CFG->prefix, $CFG->wwwroot, $CFG->phpunit_dataroot and $CFG->phpunit_prefix values.';
$string['fieldvalueargument'] = 'Field value arguments';
$string['fieldvalueargument_help'] = 'This argument should be completed by a field value, there are many field types, simple ones like checkboxes, selects or textareas or complex ones like date selectors. You can check <a href="http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps" target="_blank">Field values</a> to see the expected field value depending on the field type you provide.';
$string['fieldvalueargument_help'] = 'This argument should be completed by a field value. There are many field types, including simple ones like checkboxes, selects or textareas, or complex ones like date selectors. See the dev documentation <a href="http://docs.moodle.org/dev/Acceptance_testing" target="_blank">Acceptance_testing</a> for details of expected field values.';
$string['giveninfo'] = 'Given. Processes to set up the environment';
$string['infoheading'] = 'Info';
$string['installinfo'] = 'Read {$a} for installation and tests execution info';
$string['newstepsinfo'] = 'Read {$a} for info about how to add new steps definitions';
$string['newstepsinfo'] = 'Read {$a} for info about how to add new step definitions';
$string['newtestsinfo'] = 'Read {$a} for info about how to write new tests';
$string['nostepsdefinitions'] = 'There aren\'t steps definitions matching this filters';
$string['nostepsdefinitions'] = 'There aren\'t any step definitions matching this filter';
$string['pluginname'] = 'Acceptance testing';
$string['stepsdefinitionscomponent'] = 'Area';
$string['stepsdefinitionscontains'] = 'Contains';
$string['stepsdefinitionsfilters'] = 'Steps definitions';
$string['stepsdefinitionsfilters'] = 'Step definitions';
$string['stepsdefinitionstype'] = 'Type';
$string['theninfo'] = 'Then. Checkings to ensure the outcomes are the expected ones';
$string['unknownexceptioninfo'] = 'There was a problem with Selenium or your browser. Please ensure you are using the latest version of Selenium. Error:';
$string['viewsteps'] = 'Filter';
$string['wheninfo'] = 'When. Actions that provokes an event';
$string['wheninfo'] = 'When. Action that provokes an event';
$string['wrongbehatsetup'] = 'Something is wrong with the behat setup and so step definitions cannot be listed: <b>{$a->errormsg}</b><br/><br/>Please check:<ul>
<li>$CFG->behat_dataroot, $CFG->behat_prefix and $CFG->behat_wwwroot are set in config.php with different values from $CFG->dataroot, $CFG->prefix and $CFG->wwwroot.</li>
<li>You ran "{$a->behatinit}" from your Moodle root directory.</li>
@@ -11,7 +11,7 @@ Feature: List the system steps definitions
@javascript
Scenario: Accessing the list
Then I should see "Steps definitions"
Then I should see "Step definitions"
And I should not see "There aren't steps definitions matching this filter"
@javascript
@@ -30,6 +30,8 @@ use core_privacy\local\request\context;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
use \core_privacy\local\request\approved_userlist;
defined('MOODLE_INTERNAL') || die();
@@ -39,7 +41,10 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
@@ -75,15 +80,25 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
public static function get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new contextlist();
// Retrieve the User context associated with tool_cohortroles records.
$sql = "SELECT DISTINCT c.id
FROM {context} c
JOIN {tool_cohortroles} cr ON cr.userid = c.instanceid AND c.contextlevel = :contextuser
WHERE cr.userid = :userid";
// When we process user deletions and expiries, we always delete from the user context.
// As a result the cohort role assignments would be deleted, which has a knock-on effect with courses
// as roles may change and data may be removed earlier than it should be.
// Retrieve the context associated with tool_cohortroles records.
$sql = "SELECT DISTINCT c.contextid
FROM {tool_cohortroles} tc
JOIN {cohort} c
ON tc.cohortid = c.id
JOIN {context} ctx
ON ctx.id = c.contextid
WHERE tc.userid = :userid
AND (ctx.contextlevel = :contextlevel1
OR ctx.contextlevel = :contextlevel2)";
$params = [
'contextuser' => CONTEXT_USER,
'userid' => $userid
'userid' => $userid,
'contextlevel1' => CONTEXT_SYSTEM,
'contextlevel2' => CONTEXT_COURSECAT
];
$contextlist->add_from_sql($sql, $params);
@@ -91,6 +106,40 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
// When we process user deletions and expiries, we always delete from the user context.
// As a result the cohort role assignments would be deleted, which has a knock-on effect with courses
// as roles may change and data may be removed earlier than it should be.
$allowedcontextlevels = [
CONTEXT_SYSTEM,
CONTEXT_COURSECAT
];
if (!in_array($context->contextlevel, $allowedcontextlevels)) {
return;
}
$sql = "SELECT tc.userid as userid
FROM {tool_cohortroles} tc
JOIN {cohort} c
ON tc.cohortid = c.id
WHERE c.contextid = :contextid";
$params = [
'contextid' => $context->id
];
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
@@ -99,24 +148,28 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// If the user has tool_cohortroles data, then only the User context should be present so get the first context.
$contexts = $contextlist->get_contexts();
if (count($contexts) == 0) {
return;
}
$context = reset($contexts);
// Remove contexts different from SYSTEM or COURSECAT.
$contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT) {
$carry[] = $context->id;
}
return $carry;
}, []);
// Sanity check that context is at the User context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
if (empty($contextids)) {
return;
}
$userid = $context->instanceid;
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
// Retrieve the tool_cohortroles records created for the user.
$sql = 'SELECT cr.id as cohortroleid,
$sql = "SELECT cr.id as cohortroleid,
c.name as cohortname,
c.idnumber as cohortidnumber,
c.description as cohortdescription,
c.contextid as contextid,
r.shortname as roleshortname,
cr.userid as userid,
cr.timecreated as timecreated,
@@ -124,13 +177,13 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
FROM {tool_cohortroles} cr
JOIN {cohort} c ON c.id = cr.cohortid
JOIN {role} r ON r.id = cr.roleid
WHERE cr.userid = :userid';
WHERE cr.userid = :userid
AND c.contextid {$contextsql}";
$params = [
'userid' => $userid
];
$params = ['userid' => $userid] + $contextparams;
$cohortroles = $DB->get_records_sql($sql, $params);
foreach ($cohortroles as $cohortrole) {
// The tool_cohortroles data export is organised in:
// {User Context}/Cohort roles management/{cohort name}/{role shortname}/data.json.
@@ -150,6 +203,8 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
'timemodified' => transform::datetime($cohortrole->timemodified)
];
$context = \context::instance_by_id($cohortrole->contextid);
writer::with_context($context)->export_data($subcontext, $data);
}
}
@@ -162,14 +217,70 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
// Sanity check that context is at the User context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
// When we process user deletions and expiries, we always delete from the user context.
// As a result the cohort role assignments would be deleted, which has a knock-on effect with courses
// as roles may change and data may be removed earlier than it should be.
$allowedcontextlevels = [
CONTEXT_SYSTEM,
CONTEXT_COURSECAT
];
if (!in_array($context->contextlevel, $allowedcontextlevels)) {
return;
}
$userid = $context->instanceid;
// Delete the tool_cohortroles records created for the userid.
$DB->delete_records('tool_cohortroles', ['userid' => $userid]);
$cohortids = $DB->get_fieldset_select('cohort', 'id', 'contextid = :contextid',
['contextid' => $context->id]);
// Delete the tool_cohortroles records created in the specific context.
$DB->delete_records_list('tool_cohortroles', 'cohortid', $cohortids);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
// When we process user deletions and expiries, we always delete from the user context.
// As a result the cohort role assignments would be deleted, which has a knock-on effect with courses
// as roles may change and data may be removed earlier than it should be.
$userids = $userlist->get_userids();
if (empty($userids)) {
return;
}
$context = $userlist->get_context();
$allowedcontextlevels = [
CONTEXT_SYSTEM,
CONTEXT_COURSECAT
];
if (!in_array($context->contextlevel, $allowedcontextlevels)) {
return;
}
$cohortids = $DB->get_fieldset_select('cohort', 'id', 'contextid = :contextid',
['contextid' => $context->id]);
if (empty($cohortids)) {
return;
}
list($cohortsql, $cohortparams) = $DB->get_in_or_equal($cohortids, SQL_PARAMS_NAMED);
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params = $cohortparams + $userparams;
$select = "cohortid {$cohortsql} AND userid {$usersql}";
// Delete the tool_cohortroles records created in the specific context for an approved list of users.
$DB->delete_records_select('tool_cohortroles', $select, $params);
}
/**
@@ -180,21 +291,38 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
// If the user has tool_cohortroles data, then only the User context should be present so get the first context.
$contexts = $contextlist->get_contexts();
if (count($contexts) == 0) {
return;
}
$context = reset($contexts);
// When we process user deletions and expiries, we always delete from the user context.
// As a result the cohort role assignments would be deleted, which has a knock-on effect with courses
// as roles may change and data may be removed earlier than it should be.
// Sanity check that context is at the User context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
// Remove contexts different from SYSTEM or COURSECAT.
$contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT) {
$carry[] = $context->id;
}
return $carry;
}, []);
if (empty($contextids)) {
return;
}
$userid = $context->instanceid;
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$selectcontext = "contextid {$contextsql}";
// Get the cohorts in the specified contexts.
$cohortids = $DB->get_fieldset_select('cohort', 'id', $selectcontext, $contextparams);
if (empty($cohortids)) {
return;
}
list($cohortsql, $cohortparams) = $DB->get_in_or_equal($cohortids, SQL_PARAMS_NAMED);
$selectcohort = "cohortid {$cohortsql} AND userid = :userid";
$params = ['userid' => $userid] + $cohortparams;
// Delete the tool_cohortroles records created for the userid.
$DB->delete_records('tool_cohortroles', ['userid' => $userid]);
$DB->delete_records_select('tool_cohortroles', $selectcohort, $params);
}
}
@@ -43,10 +43,10 @@ $string['selectusers'] = 'Select users to assign role';
$string['taskname'] = 'Sync cohort role assignments';
$string['thisuserroles'] = 'Roles assigned relative to this user';
$string['privacy:metadata:tool_cohortroles'] = 'The Cohort roles management plugin stores user cohort role mappings.';
$string['privacy:metadata:tool_cohortroles:id'] = 'The ID of the cohort role mapping record.';
$string['privacy:metadata:tool_cohortroles:cohortid'] = 'The ID of the cohort.';
$string['privacy:metadata:tool_cohortroles:roleid'] = 'The ID of the role.';
$string['privacy:metadata:tool_cohortroles:userid'] = 'The ID of the user.';
$string['privacy:metadata:tool_cohortroles:timecreated'] = 'The date/time of when the cohort role mapping was created.';
$string['privacy:metadata:tool_cohortroles:timemodified'] = 'The date/time of when the cohort role mapping was modified.';
$string['privacy:metadata:tool_cohortroles:usermodified'] = 'The ID of the user who last modified the cohort role mapping.';
$string['privacy:metadata:tool_cohortroles:id'] = 'The ID of the cohort role mapping record';
$string['privacy:metadata:tool_cohortroles:cohortid'] = 'The ID of the cohort';
$string['privacy:metadata:tool_cohortroles:roleid'] = 'The ID of the role';
$string['privacy:metadata:tool_cohortroles:userid'] = 'The ID of the user';
$string['privacy:metadata:tool_cohortroles:timecreated'] = 'The time when the cohort role mapping was created';
$string['privacy:metadata:tool_cohortroles:timemodified'] = 'The time when the cohort role mapping was modified';
$string['privacy:metadata:tool_cohortroles:usermodified'] = 'The ID of the user who last modified the cohort role mapping';
+219 -46
View File
@@ -30,6 +30,7 @@ use \core_privacy\local\request\writer;
use \core_privacy\local\request\approved_contextlist;
use \tool_cohortroles\api;
use \tool_cohortroles\privacy\provider;
use core_privacy\local\request\approved_userlist;
/**
* Unit tests for the tool_cohortroles implementation of the privacy API.
@@ -57,22 +58,36 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
$this->setUser($user);
$this->setAdminUser();
$nocohortroles = 3;
$this->setup_test_scenario_data($user->id, $nocohortroles);
// Create course category.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = context_system::instance();
// Create course.
$course = $this->getDataGenerator()->create_course();
$coursectx = \context_course::instance($course->id);
$this->setup_test_scenario_data($user->id, $systemctx, 1);
$this->setup_test_scenario_data($user->id, $coursecategoryctx, 1, 'Sausage roll 2',
'sausageroll2');
$this->setup_test_scenario_data($user->id, $coursectx, 1, 'Sausage roll 3',
'sausageroll3');
// Test the User's assigned cohortroles matches 3.
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
$this->assertCount($nocohortroles, $cohortroles);
$this->assertCount(3, $cohortroles);
// Test the User's retrieved contextlist contains only one context.
// Test the User's retrieved contextlist returns only the system and course category context.
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(1, $contexts);
$this->assertCount(2, $contexts);
// Test the User's contexts equal the User's own context.
$context = reset($contexts);
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
$this->assertEquals($user->id, $context->instanceid);
$contextlevels = array_column($contexts, 'contextlevel');
$expected = [
CONTEXT_SYSTEM,
CONTEXT_COURSECAT
];
// Test the User's contexts equal the system and course category context.
$this->assertEquals($expected, $contextlevels, '', 0, 10, true);
}
/**
@@ -84,26 +99,45 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
$this->setUser($user);
$this->setAdminUser();
$nocohortroles = 3;
$this->setup_test_scenario_data($user->id, $nocohortroles);
// Create course category.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = context_system::instance();
// Create course.
$course = $this->getDataGenerator()->create_course();
$coursectx = \context_course::instance($course->id);
// Test the User's retrieved contextlist contains only one context.
$this->setup_test_scenario_data($user->id, $systemctx, 1);
$this->setup_test_scenario_data($user->id, $coursecategoryctx, 1, 'Sausage roll 2',
'sausageroll2');
$this->setup_test_scenario_data($user->id, $coursectx, 1, 'Sausage roll 3',
'sausageroll3');
// Test the User's retrieved contextlist contains two contexts.
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(1, $contexts);
$this->assertCount(2, $contexts);
// Test the User's contexts equal the User's own context.
$context = reset($contexts);
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
$this->assertEquals($user->id, $context->instanceid);
// Add a system, course category and course context to the approved context list.
$approvedcontextids = [
$systemctx->id,
$coursecategoryctx->id,
$coursectx->id
];
// Retrieve the User's tool_cohortroles data.
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $contextlist->get_contextids());
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $approvedcontextids);
provider::export_user_data($approvedcontextlist);
// Test the tool_cohortroles data is exported at the User context level.
$writer = writer::with_context($context);
// Test the tool_cohortroles data is exported at the system context level.
$writer = writer::with_context($systemctx);
$this->assertTrue($writer->has_any_data());
// Test the tool_cohortroles data is exported at the course category context level.
$writer = writer::with_context($coursecategoryctx);
$this->assertTrue($writer->has_any_data());
// Test the tool_cohortroles data is not exported at the course context level.
$writer = writer::with_context($coursectx);
$this->assertFalse($writer->has_any_data());
}
/**
@@ -117,29 +151,52 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
$this->setUser($user);
$this->setAdminUser();
$nocohortroles = 4;
$this->setup_test_scenario_data($user->id, $nocohortroles);
// Create course category.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = context_system::instance();
// Test the User's assigned cohortroles matches 4.
$this->setup_test_scenario_data($user->id, $systemctx, 1);
$this->setup_test_scenario_data($user->id, $coursecategoryctx, 1, 'Sausage roll 2',
'sausageroll2');
// Test the User's assigned cohortroles matches 2.
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
$this->assertCount($nocohortroles, $cohortroles);
$this->assertCount(2, $cohortroles);
// Test the User's retrieved contextlist contains only one context.
// Test the User's retrieved contextlist contains two contexts.
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(2, $contexts);
// Make sure the user data is only being deleted in within the system and course category context.
$usercontext = context_user::instance($user->id);
// Delete all the User's records in mdl_tool_cohortroles table by the user context.
provider::delete_data_for_all_users_in_context($usercontext);
// Test the cohort roles records in mdl_tool_cohortroles table is still present.
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
$this->assertCount(2, $cohortroles);
// Delete all the User's records in mdl_tool_cohortroles table by the specified system context.
provider::delete_data_for_all_users_in_context($systemctx);
// The user data in the system context should be deleted.
// Test the User's retrieved contextlist contains one context (course category).
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(1, $contexts);
// Test the User's contexts equal the User's own context.
$context = reset($contexts);
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
$this->assertEquals($user->id, $context->instanceid);
// Delete all the User's records in mdl_tool_cohortroles table by the specified User context.
provider::delete_data_for_all_users_in_context($context);
// Delete all the User's records in mdl_tool_cohortroles table by the specified course category context.
provider::delete_data_for_all_users_in_context($coursecategoryctx);
// Test the cohort roles records in mdl_tool_cohortroles table is equals zero.
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
$this->assertCount(0, $cohortroles);
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(0, $contexts);
}
/**
@@ -153,24 +210,35 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
$this->setUser($user);
$this->setAdminUser();
$nocohortroles = 4;
$this->setup_test_scenario_data($user->id, $nocohortroles);
// Create course category.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = context_system::instance();
// Test the User's assigned cohortroles matches 4.
$this->setup_test_scenario_data($user->id, $systemctx, 1);
$this->setup_test_scenario_data($user->id, $coursecategoryctx, 1, 'Sausage roll 2',
'sausageroll2');
// Test the User's assigned cohortroles matches 2.
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
$this->assertCount($nocohortroles, $cohortroles);
$this->assertCount(2, $cohortroles);
// Test the User's retrieved contextlist contains only one context.
// Test the User's retrieved contextlist contains two contexts.
$contextlist = provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();
$this->assertCount(1, $contexts);
$this->assertCount(2, $contexts);
// Test the User's contexts equal the User's own context.
$context = reset($contexts);
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
$this->assertEquals($user->id, $context->instanceid);
// Make sure the user data is only being deleted in within the system and the course category contexts.
$usercontext = context_user::instance($user->id);
// Delete all the User's records in mdl_tool_cohortroles table by the specified approved context list.
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', [$usercontext->id]);
provider::delete_data_for_user($approvedcontextlist);
// Delete all the User's records in mdl_tool_cohortroles table by the specified User approved context list.
// Test the cohort roles records in mdl_tool_cohortroles table are still present.
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
$this->assertCount(2, $cohortroles);
// Delete all the User's records in mdl_tool_cohortroles table by the specified approved context list.
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $contextlist->get_contextids());
provider::delete_data_for_user($approvedcontextlist);
@@ -179,19 +247,124 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
$this->assertCount(0, $cohortroles);
}
/**
* Test that only users within a course context are fetched.
*/
public function test_get_users_in_context() {
$component = 'tool_cohortroles';
// Create a user.
$user = $this->getDataGenerator()->create_user();
$usercontext = context_user::instance($user->id);
// Create course category.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = context_system::instance();
$this->setAdminUser();
$userlist = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
$this->setup_test_scenario_data($user->id, $systemctx, 1);
$this->setup_test_scenario_data($user->id, $coursecategoryctx, 1, 'Sausage roll 2',
'sausageroll2');
// The list of users within the system context should contain user.
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$this->assertTrue(in_array($user->id, $userlist->get_userids()));
// The list of users within the course category context should contain user.
$userlist = new \core_privacy\local\request\userlist($coursecategoryctx, $component);
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$this->assertTrue(in_array($user->id, $userlist->get_userids()));
// The list of users within the user context should be empty.
$userlist2 = new \core_privacy\local\request\userlist($usercontext, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users() {
$component = 'tool_cohortroles';
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
// Create user3.
$user3 = $this->getDataGenerator()->create_user();
$usercontext3 = context_user::instance($user3->id);
// Create course category.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = context_system::instance();
$this->setAdminUser();
$this->setup_test_scenario_data($user1->id, $systemctx, 1);
$this->setup_test_scenario_data($user2->id, $systemctx, 1, 'Sausage roll 2',
'sausageroll2');
$this->setup_test_scenario_data($user3->id, $coursecategoryctx, 1, 'Sausage roll 3',
'sausageroll3');
$userlist1 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(2, $userlist1);
$this->assertTrue(in_array($user1->id, $userlist1->get_userids()));
$this->assertTrue(in_array($user2->id, $userlist1->get_userids()));
// Convert $userlist1 into an approved_contextlist.
$approvedlist1 = new approved_userlist($systemctx, $component, [$user1->id]);
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist1);
// Re-fetch users in systemcontext.
$userlist1 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist1);
// The user data of user1in systemcontext should be deleted.
// The user data of user2 in systemcontext should be still present.
$this->assertCount(1, $userlist1);
$this->assertTrue(in_array($user2->id, $userlist1->get_userids()));
// Convert $userlist1 into an approved_contextlist in the user context.
$approvedlist2 = new approved_userlist($usercontext3, $component, $userlist1->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist2);
// Re-fetch users in systemcontext.
$userlist1 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist1);
// The user data in systemcontext should not be deleted.
$this->assertCount(1, $userlist1);
}
/**
* Helper function to setup tool_cohortroles records for testing a specific user.
*
* @param int $userid The ID of the user used for testing.
* @param int $nocohortroles The number of tool_cohortroles to create for the user.
* @param string $rolename The name of the role to be created.
* @param string $roleshortname The short name of the role to be created.
* @throws \core_competency\invalid_persistent_exception
* @throws coding_exception
*/
protected function setup_test_scenario_data($userid, $nocohortroles) {
$roleid = create_role('Sausage Roll', 'sausageroll', 'mmmm');
protected function setup_test_scenario_data($userid, $context, $nocohortroles, $rolename = 'Sausage Roll',
$roleshortname = 'sausageroll') {
$roleid = create_role($rolename, $roleshortname, 'mmmm');
$result = new \stdClass();
$result->contextid = $context->id;
for ($c = 0; $c < $nocohortroles; $c++) {
$cohort = $this->getDataGenerator()->create_cohort();
$cohort = $this->getDataGenerator()->create_cohort($result);
$params = (object)array(
'userid' => $userid,
+3
View File
@@ -38,5 +38,8 @@ function xmldb_tool_customlang_upgrade($oldversion) {
// Automatically generated Moodle v3.4.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v3.5.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
@@ -30,7 +30,7 @@ $string['checkin'] = 'Save strings to language pack';
$string['checkout'] = 'Open language pack for editing';
$string['checkoutdone'] = 'Language pack loaded';
$string['checkoutinprogress'] = 'Loading language pack';
$string['confirmcheckin'] = 'You are about to save modifications to your local language pack. This will export the customised strings from the translator into you Moodle data directory and Moodle will start using the modified strings. Press \'Continue\' to proceed with saving.';
$string['confirmcheckin'] = 'You are about to save modifications to your local language pack. This will export the customised strings from the translator into your site data directory and your site will start using the modified strings. Press \'Continue\' to proceed with saving.';
$string['customlang:edit'] = 'Edit local translation';
$string['customlang:view'] = 'View local translation';
$string['filter'] = 'Filter strings';
+6 -4
View File
@@ -134,11 +134,13 @@ class tool_customlang_renderer extends plugin_renderer_base {
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'translatorsubmitted', 'value'=>1));
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'p', 'value'=>$translator->currentpage));
$save1 = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savecontinue', 'value'=>get_string('savecontinue', 'tool_customlang')));
$save2 = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savecheckin', 'value'=>get_string('savecheckin', 'tool_customlang')));
$output .= html_writer::tag('fieldset', $save1.$save2, array('class'=>'buttonsbar'));
$save1 = html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'savecontinue',
'value' => get_string('savecontinue', 'tool_customlang'), 'class' => 'btn btn-secondary'));
$save2 = html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'savecheckin',
'value' => get_string('savecheckin', 'tool_customlang'), 'class' => 'btn btn-secondary'));
$output .= html_writer::tag('fieldset', $save1 . ' ' . $save2, array('class' => 'buttonsbar'));
$output .= html_writer::table($table);
$output .= html_writer::tag('fieldset', $save1.$save2, array('class'=>'buttonsbar'));
$output .= html_writer::tag('fieldset', $save1 . ' ' . $save2, array('class' => 'buttonsbar'));
$output .= html_writer::end_tag('div');
$output .= html_writer::end_tag('form');
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletecategory"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletecategory",component:"tool_dataprivacy",param:i},{key:"deletecategorytext",component:"tool_dataprivacy",param:i}];d.get_strings(j).then(function(d){var g=d[0],i=d[1];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(g),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_category",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-categoryid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletecategory"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletecategory",component:"tool_dataprivacy"},{key:"deletecategorytext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_category",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-categoryid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]'},i=function(a){d.call(this,a),this.getFooter().find(h.APPROVE_BUTTON).length||b.exception({message:"No approve button found"}),this.getFooter().find(h.DENY_BUTTON).length||b.exception({message:"No deny button found"})};return i.TYPE="tool_dataprivacy-data_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]',COMPLETE_BUTTON:'[data-action="complete"]'},i=function(a){d.call(this,a)};return i.TYPE="tool_dataprivacy-data_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.COMPLETE_BUTTON,function(b,c){var d=a.Event(f.complete);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
@@ -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}}});
+1 -1
View File
@@ -1 +1 @@
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",deny:"tool_dataprivacy-data_request:deny"}});
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",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"}});
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletepurpose"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletepurpose",component:"tool_dataprivacy",param:i},{key:"deletepurposetext",component:"tool_dataprivacy",param:i}];d.get_strings(j).then(function(d){var g=d[0],i=d[1];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(g),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_purpose",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-purposeid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletepurpose"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletepurpose",component:"tool_dataprivacy"},{key:"deletepurposetext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_purpose",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-purposeid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
@@ -0,0 +1 @@
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={REQUEST_FILTERS:"#request-filters"},f=function(){var f=[{key:"filter",component:"moodle"},{key:"nofiltersapplied",component:"moodle"}];c.get_strings(f).then(function(a){var c=a[0],d=a[1];return b.enhance(e.REQUEST_FILTERS,!1,"",c,!1,!0,d,!0)}).fail(d.exception);var g=a(e.REQUEST_FILTERS).val();a(e.REQUEST_FILTERS).on("change",function(){var b=a(this).val();g.join(",")!==b.join(",")&&(0===b.length&&a("#filters-cleared").val(1),a(this.form).submit())})};return{init:function(){f()}}});
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a,g){var h=[],j="";switch(a){case i.approve:h=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}],j="tool_dataprivacy_approve_data_request";break;case i.deny:h=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}],j="tool_dataprivacy_deny_data_request"}var k="";d.get_strings(h).then(function(a){k=a[0];var b=a[1];return e.create({title:k,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){return a.setSaveButtonText(k),a.getRoot().on(f.save,function(){var a={requestid:g},d={methodname:j,args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a}).done(function(a){a.show()}).fail(c.exception)}var k={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]'},l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(k.VIEW_REQUEST).click(function(d){d.preventDefault();var k=a(this).data("requestid"),l={requestid:k},m={methodname:"tool_dataprivacy_get_data_request",args:l},n=b.call([m]),o="",p=e.types.DEFAULT;a.when(n[0]).then(function(a){return a.result?(2==a.result.status&&(p=h.TYPE),o=a.result.typename,g.render("tool_dataprivacy/request_details",a.result)):(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){return e.create({title:o,body:a,type:p,large:!0}).then(function(a){return a.getRoot().on(i.approve,function(){j(i.approve,k)}),a.getRoot().on(i.deny,function(){j(i.deny,k)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(k.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.approve,c)}),a(k.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.deny,c)})},l});
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a){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=g.render("tool_dataprivacy/request_details",a),c={approvedeny:a.approvedeny,canmarkcomplete:a.canmarkcomplete};return e.create({title:a.typename,body:b,type:h.TYPE,large:!0,templateContext:c})}).then(function(a){a.getRoot().on(i.approve,function(){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});
@@ -58,25 +58,28 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
var stringkeys = [
{
key: 'deletecategory',
component: 'tool_dataprivacy',
param: categoryname
component: 'tool_dataprivacy'
},
{
key: 'deletecategorytext',
component: 'tool_dataprivacy',
param: categoryname
},
{
key: 'delete'
}
];
Str.get_strings(stringkeys).then(function(langStrings) {
var title = langStrings[0];
var confirmMessage = langStrings[1];
var buttonText = langStrings[2];
return ModalFactory.create({
title: title,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
}).then(function(modal) {
modal.setSaveButtonText(title);
modal.setSaveButtonText(buttonText);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
@@ -234,6 +234,7 @@ define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/templates'
},
fail: Notification.exception
}]);
return;
}).catch(Notification.exception);
};
@@ -29,6 +29,7 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
var SELECTORS = {
APPROVE_BUTTON: '[data-action="approve"]',
DENY_BUTTON: '[data-action="deny"]',
COMPLETE_BUTTON: '[data-action="complete"]'
};
/**
@@ -38,14 +39,6 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
*/
var ModalDataRequest = function(root) {
Modal.call(this, root);
if (!this.getFooter().find(SELECTORS.APPROVE_BUTTON).length) {
Notification.exception({message: 'No approve button found'});
}
if (!this.getFooter().find(SELECTORS.DENY_BUTTON).length) {
Notification.exception({message: 'No deny button found'});
}
};
ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
@@ -80,6 +73,16 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
data.originalEvent.preventDefault();
}
}.bind(this));
this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
var completeEvent = $.Event(DataPrivacyEvents.complete);
this.getRoot().trigger(completeEvent, this);
if (!completeEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
}.bind(this));
};
// Automatically register with the modal registry the first time this module is imported so that you can create modals
@@ -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();
}
};
});
+3
View File
@@ -25,6 +25,9 @@
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'
};
});
@@ -34,7 +34,6 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
*
* @param {object} targetnode The node that we want to expand / collapse
* @param {object} thisnode The node that was clicked.
* @return {null}
*/
expandCollapse: function(targetnode, thisnode) {
if (targetnode.hasClass('hide')) {
@@ -58,7 +57,6 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
* Expand or collapse all nodes on this page.
*
* @param {string} nextstate The next state to change to.
* @return {null}
*/
expandCollapseAll: function(nextstate) {
var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
@@ -75,6 +73,7 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
str.get_string(currentstate, 'tool_dataprivacy').then(function(langString) {
$('.tool_dataprivacy-expand-all').html(langString);
return;
}).catch(Notification.exception);
$(':header i.fa').each(function() {
@@ -58,25 +58,28 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
var stringkeys = [
{
key: 'deletepurpose',
component: 'tool_dataprivacy',
param: purposename
component: 'tool_dataprivacy'
},
{
key: 'deletepurposetext',
component: 'tool_dataprivacy',
param: purposename
},
{
key: 'delete'
}
];
Str.get_strings(stringkeys).then(function(langStrings) {
var title = langStrings[0];
var confirmMessage = langStrings[1];
var buttonText = langStrings[2];
return ModalFactory.create({
title: title,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
}).then(function(modal) {
modal.setSaveButtonText(title);
modal.setSaveButtonText(buttonText);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
@@ -0,0 +1,84 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* JS module for the data requests filter.
*
* @module tool_dataprivacy/request_filter
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'], function($, Autocomplete, Str, Notification) {
/**
* Selectors.
*
* @access private
* @type {{REQUEST_FILTERS: string}}
*/
var SELECTORS = {
REQUEST_FILTERS: '#request-filters'
};
/**
* Init function.
*
* @method init
* @private
*/
var init = function() {
var stringkeys = [
{
key: 'filter',
component: 'moodle'
},
{
key: 'nofiltersapplied',
component: 'moodle'
}
];
Str.get_strings(stringkeys).then(function(langstrings) {
var placeholder = langstrings[0];
var noSelectionString = langstrings[1];
return Autocomplete.enhance(SELECTORS.REQUEST_FILTERS, false, '', placeholder, false, true, noSelectionString, true);
}).fail(Notification.exception);
var last = $(SELECTORS.REQUEST_FILTERS).val();
$(SELECTORS.REQUEST_FILTERS).on('change', function() {
var current = $(this).val();
// Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
if (last.join(',') !== current.join(',')) {
// If we're submitting without filters, set the hidden input 'filters-cleared' to 1.
if (current.length === 0) {
$('#filters-cleared').val(1);
}
$(this.form).submit();
}
});
};
return /** @alias module:core/form-autocomplete */ {
/**
* Initialise the unified user filter.
*
* @method init
*/
init: function() {
init();
}
};
});
+277 -63
View File
@@ -39,11 +39,39 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
* @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"]'
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'
};
/**
@@ -73,16 +101,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
};
var promises = Ajax.call([request]);
var modalTitle = '';
var modalType = ModalFactory.types.DEFAULT;
$.when(promises[0]).then(function(data) {
if (data.result) {
// Check if the status is awaiting approval.
if (data.result.status == 2) {
modalType = ModalDataRequest.TYPE;
}
modalTitle = data.result.typename;
return Templates.render('tool_dataprivacy/request_details', data.result);
return data.result;
}
// Fail.
Notification.addNotification({
@@ -91,61 +112,209 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
});
return false;
}).then(function(html) {
}).then(function(data) {
var body = Templates.render('tool_dataprivacy/request_details', data);
var templateContext = {
approvedeny: data.approvedeny,
canmarkcomplete: data.canmarkcomplete
};
return ModalFactory.create({
title: modalTitle,
body: html,
type: modalType,
large: true
}).then(function(modal) {
// Handle approve event.
modal.getRoot().on(DataPrivacyEvents.approve, function() {
showConfirmation(DataPrivacyEvents.approve, requestId);
});
// Handle deny event.
modal.getRoot().on(DataPrivacyEvents.deny, function() {
showConfirmation(DataPrivacyEvents.deny, requestId);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
return modal;
title: data.typename,
body: body,
type: ModalDataRequest.TYPE,
large: true,
templateContext: templateContext
});
}).done(function(modal) {
}).then(function(modal) {
// Handle approve event.
modal.getRoot().on(DataPrivacyEvents.approve, function() {
showConfirmation(DataPrivacyEvents.approve, 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();
}).fail(Notification.exception);
return;
}).catch(Notification.exception);
});
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.approve, requestId);
showConfirmation(DataPrivacyEvents.approve, approveEventWsData(requestId));
});
$(ACTIONS.DENY_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.deny, 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 {Number} requestId The request ID.
* @param {Object} wsdata Object containing ws data.
*/
function showConfirmation(action, requestId) {
function showConfirmation(action, wsdata) {
var keys = [];
var wsfunction = '';
switch (action) {
case DataPrivacyEvents.approve:
keys = [
@@ -158,7 +327,18 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
component: 'tool_dataprivacy'
}
];
wsfunction = 'tool_dataprivacy_approve_data_request';
break;
case DataPrivacyEvents.bulkApprove:
keys = [
{
key: 'bulkapproverequests',
component: 'tool_dataprivacy'
},
{
key: 'confirmbulkapproval',
component: 'tool_dataprivacy'
}
];
break;
case DataPrivacyEvents.deny:
keys = [
@@ -171,7 +351,30 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
component: 'tool_dataprivacy'
}
];
wsfunction = 'tool_dataprivacy_deny_data_request';
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;
}
@@ -189,26 +392,7 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
// Confirm the request.
var params = {
'requestid': requestId
};
var request = {
methodname: wsfunction,
args: params
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
window.location.reload();
} else {
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
handleSave(wsdata.wsfunction, wsdata.wsparams);
});
// Handle hidden event.
@@ -217,9 +401,39 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
modal.destroy();
});
return modal;
}).done(function(modal) {
modal.show();
return;
}).catch(Notification.exception);
}
/**
* Calls a web service function and reloads the page on success and shows a notification.
* Displays an error notification, otherwise.
*
* @param {String} wsfunction The web service function to call.
* @param {Object} params The parameters for the web service functoon.
*/
function handleSave(wsfunction, params) {
// Confirm the request.
var request = {
methodname: wsfunction,
args: params
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
// On success, reload the page so that the data request table will be updated.
// TODO: Probably in the future, better to reload the table or the target data request via AJAX.
window.location.reload();
} else {
// Add the notification.
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
}
+3
View File
@@ -24,6 +24,8 @@
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$url = new moodle_url("/admin/tool/dataprivacy/categories.php");
$title = get_string('editcategories', 'tool_dataprivacy');
@@ -31,6 +33,7 @@ $title = get_string('editcategories', 'tool_dataprivacy');
$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);
+475 -88
View File
@@ -24,6 +24,7 @@
namespace tool_dataprivacy;
use coding_exception;
use context_helper;
use context_system;
use core\invalid_persistent_exception;
use core\message\message;
@@ -40,6 +41,7 @@ use tool_dataprivacy\external\data_request_exporter;
use tool_dataprivacy\local\helper;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
use tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
@@ -75,7 +77,7 @@ class api {
/** The request is now being processed. */
const DATAREQUEST_STATUS_PROCESSING = 4;
/** Data request completed. */
/** Information/other request completed. */
const DATAREQUEST_STATUS_COMPLETE = 5;
/** Data request cancelled by the user. */
@@ -84,6 +86,21 @@ class api {
/** Data request rejected by the DPO. */
const DATAREQUEST_STATUS_REJECTED = 7;
/** Data request download ready. */
const DATAREQUEST_STATUS_DOWNLOAD_READY = 8;
/** Data request expired. */
const DATAREQUEST_STATUS_EXPIRED = 9;
/** Data delete request completed, account is removed. */
const DATAREQUEST_STATUS_DELETED = 10;
/** Approve data request. */
const DATAREQUEST_ACTION_APPROVE = 1;
/** Reject data request. */
const DATAREQUEST_ACTION_REJECT = 2;
/**
* Determines whether the user can contact the site's Data Protection Officer via Moodle.
*
@@ -95,18 +112,14 @@ class api {
}
/**
* Check's whether the current user has the capability to manage data requests.
* Checks whether the current user has the capability to manage data requests.
*
* @param int $userid The user ID.
* @return bool
* @throws coding_exception
* @throws dml_exception
*/
public static function can_manage_data_requests($userid) {
$context = context_system::instance();
// A user can manage data requests if he/she has the site DPO role and has the capability to manage data requests.
return self::is_site_dpo($userid) && has_capability('tool/dataprivacy:managedatarequests', $context, $userid);
// Privacy officers can manage data requests.
return self::is_site_dpo($userid);
}
/**
@@ -127,20 +140,59 @@ class api {
}
/**
* Fetches the list of users with the Data Protection Officer role.
* Fetches the list of configured privacy officer roles.
*
* Every time this function is called, it checks each role if they have the 'managedatarequests' capability and removes
* any role that doesn't have the required capability anymore.
*
* @return int[]
* @throws dml_exception
*/
public static function get_assigned_privacy_officer_roles() {
$roleids = [];
// Get roles from config.
$configroleids = explode(',', str_replace(' ', '', get_config('tool_dataprivacy', 'dporoles')));
if (!empty($configroleids)) {
// Fetch roles that have the capability to manage data requests.
$capableroles = array_keys(get_roles_with_capability('tool/dataprivacy:managedatarequests'));
// Extract the configured roles that have the capability from the list of capable roles.
$roleids = array_intersect($capableroles, $configroleids);
}
return $roleids;
}
/**
* Fetches the role shortnames of Data Protection Officer roles.
*
* @return array An array of the DPO role shortnames
*/
public static function get_dpo_role_names() : array {
global $DB;
$dporoleids = self::get_assigned_privacy_officer_roles();
$dponames = array();
if (!empty($dporoleids)) {
list($insql, $inparams) = $DB->get_in_or_equal($dporoleids);
$dponames = $DB->get_fieldset_select('role', 'shortname', "id {$insql}", $inparams);
}
return $dponames;
}
/**
* Fetches the list of users with the Privacy Officer role.
*/
public static function get_site_dpos() {
// Get role(s) that can manage data requests.
$dporoles = explode(',', get_config('tool_dataprivacy', 'dporoles'));
$dporoles = self::get_assigned_privacy_officer_roles();
$dpos = [];
$context = context_system::instance();
foreach ($dporoles as $roleid) {
if (empty($roleid)) {
continue;
}
$allnames = get_all_user_name_fields(true, 'u');
$fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' .
'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '.
@@ -160,15 +212,14 @@ class api {
}
/**
* Checks whether a given user is a site DPO.
* Checks whether a given user is a site Privacy Officer.
*
* @param int $userid The user ID.
* @return bool
* @throws dml_exception
*/
public static function is_site_dpo($userid) {
$dpos = self::get_site_dpos();
return array_key_exists($userid, $dpos);
return array_key_exists($userid, $dpos) || is_siteadmin();
}
/**
@@ -177,33 +228,24 @@ class api {
* @param int $foruser The user whom the request is being made for.
* @param int $type The request type.
* @param string $comments Request comments.
* @param int $creationmethod The creation method of the data request.
* @return data_request
* @throws invalid_persistent_exception
* @throws coding_exception
*/
public static function create_data_request($foruser, $type, $comments = '') {
public static function create_data_request($foruser, $type, $comments = '',
$creationmethod = data_request::DATAREQUEST_CREATION_MANUAL) {
global $USER;
$datarequest = new data_request();
// The user the request is being made for.
$datarequest->set('userid', $foruser);
$requestinguser = $USER->id;
// Check when the user is making a request on behalf of another.
if ($requestinguser != $foruser) {
if (self::is_site_dpo($requestinguser)) {
// The user making the request is a DPO. Should be fine.
$datarequest->set('dpo', $requestinguser);
} else {
// If not a DPO, only users with the capability to make data requests for the user should be allowed.
// (e.g. users with the Parent role, etc).
if (!api::can_create_data_request_for_user($foruser)) {
$forusercontext = \context_user::instance($foruser);
throw new required_capability_exception($forusercontext,
'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
}
}
}
// The cron is considered to be a guest user when it creates a data request.
// NOTE: This should probably be changed. We should leave the default value for $requestinguser if
// the request is not explicitly created by a specific user.
$requestinguser = (isguestuser() && $creationmethod == data_request::DATAREQUEST_CREATION_AUTO) ?
get_admin()->id : $USER->id;
// The user making the request.
$datarequest->set('requestedby', $requestinguser);
// Set status.
@@ -212,6 +254,8 @@ class api {
$datarequest->set('type', $type);
// Set request comments.
$datarequest->set('comments', $comments);
// Set the creation method.
$datarequest->set('creationmethod', $creationmethod);
// Store subject access request.
$datarequest->create();
@@ -232,16 +276,51 @@ class api {
* (e.g. Users with the Data Protection Officer roles)
*
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param int[] $creationmethods The request creation method filters.
* @param string $sort The order by clause.
* @param int $offset Amount of records to skip.
* @param int $limit Amount of records to fetch.
* @return data_request[]
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests($userid = 0) {
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $creationmethods = [],
$sort = '', $offset = 0, $limit = 0) {
global $DB, $USER;
$results = [];
$sort = 'status ASC, timemodified ASC';
$sqlparams = [];
$sqlconditions = [];
// Set default sort.
if (empty($sort)) {
$sort = 'status ASC, timemodified ASC';
}
// Set status filters.
if (!empty($statuses)) {
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
$sqlconditions[] = "status $statusinsql";
}
// Set request type filter.
if (!empty($types)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
$sqlconditions[] = "type $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
// Set request creation method filter.
if (!empty($creationmethods)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($creationmethods, SQL_PARAMS_NAMED);
$sqlconditions[] = "creationmethod $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$select = "(userid = :userid OR requestedby = :requestedby)";
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
$params = [
'userid' => $userid,
'requestedby' => $userid
@@ -256,20 +335,105 @@ class api {
$alloweduserids = array_merge($alloweduserids, array_keys($children));
}
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
$select .= " AND userid $insql";
$params = array_merge($params, $inparams);
$sqlconditions[] .= "userid $insql";
$select = implode(' AND ', $sqlconditions);
$params = array_merge($params, $inparams, $sqlparams);
$results = data_request::get_records_select($select, $params, $sort);
$results = data_request::get_records_select($select, $params, $sort, '*', $offset, $limit);
} else {
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
if (self::is_site_dpo($USER->id)) {
$results = data_request::get_records(null, $sort, '');
if (!empty($sqlconditions)) {
$select = implode(' AND ', $sqlconditions);
$results = data_request::get_records_select($select, $sqlparams, $sort, '*', $offset, $limit);
} else {
$results = data_request::get_records(null, $sort, '', $offset, $limit);
}
}
}
// If any are due to expire, expire them and re-fetch updated data.
if (empty($statuses)
|| in_array(self::DATAREQUEST_STATUS_DOWNLOAD_READY, $statuses)
|| in_array(self::DATAREQUEST_STATUS_EXPIRED, $statuses)) {
$expiredrequests = data_request::get_expired_requests($userid);
if (!empty($expiredrequests)) {
data_request::expire($expiredrequests);
$results = self::get_data_requests($userid, $statuses, $types, $creationmethods, $sort, $offset, $limit);
}
}
return $results;
}
/**
* Fetches the count of data request records based on the given parameters.
*
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param int[] $creationmethods The request creation method filters.
* @return int
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests_count($userid = 0, $statuses = [], $types = [], $creationmethods = []) {
global $DB, $USER;
$count = 0;
$sqlparams = [];
$sqlconditions = [];
if (!empty($statuses)) {
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
$sqlconditions[] = "status $statusinsql";
}
if (!empty($types)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
$sqlconditions[] = "type $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if (!empty($creationmethods)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($creationmethods, SQL_PARAMS_NAMED);
$sqlconditions[] = "creationmethod $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
$params = [
'userid' => $userid,
'requestedby' => $userid
];
// Build a list of user IDs that the user is allowed to make data requests for.
// Of course, the user should be included in this list.
$alloweduserids = [$userid];
// Get any users that the user can make data requests for.
if ($children = helper::get_children_of_user($userid)) {
// Get the list of user IDs of the children and merge to the allowed user IDs.
$alloweduserids = array_merge($alloweduserids, array_keys($children));
}
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
$sqlconditions[] .= "userid $insql";
$select = implode(' AND ', $sqlconditions);
$params = array_merge($params, $inparams, $sqlparams);
$count = data_request::count_records_select($select, $params);
} else {
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
if (self::is_site_dpo($USER->id)) {
if (!empty($sqlconditions)) {
$select = implode(' AND ', $sqlconditions);
$count = data_request::count_records_select($select, $sqlparams);
} else {
$count = data_request::count_records();
}
}
}
return $count;
}
/**
* Checks whether there is already an existing pending/in-progress data request for a user for a given request type.
*
@@ -287,9 +451,12 @@ class api {
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
self::DATAREQUEST_STATUS_EXPIRED,
self::DATAREQUEST_STATUS_DELETED,
];
list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED);
$select = 'type = :type AND userid = :userid AND status NOT ' . $insql;
list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED, 'st', false);
$select = "type = :type AND userid = :userid AND status {$insql}";
$params = array_merge([
'type' => $type,
'userid' => $userid
@@ -298,6 +465,48 @@ class api {
return data_request::record_exists_select($select, $params);
}
/**
* Find whether any ongoing requests exist for a set of users.
*
* @param array $userids
* @return array
*/
public static function find_ongoing_request_types_for_users(array $userids) : array {
global $DB;
if (empty($userids)) {
return [];
}
// Check if the user already has an incomplete data request of the same type.
$nonpendingstatuses = [
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
self::DATAREQUEST_STATUS_EXPIRED,
self::DATAREQUEST_STATUS_DELETED,
];
list($statusinsql, $statusparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED, 'st', false);
list($userinsql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'us');
$select = "userid {$userinsql} AND status {$statusinsql}";
$params = array_merge($statusparams, $userparams);
$requests = $DB->get_records_select(data_request::TABLE, $select, $params, 'userid', 'id, userid, type');
$returnval = [];
foreach ($userids as $userid) {
$returnval[$userid] = (object) [];
}
foreach ($requests as $request) {
$returnval[$request->userid]->{$request->type} = true;
}
return $returnval;
}
/**
* Determines whether a request is active or not based on its status.
*
@@ -310,6 +519,9 @@ class api {
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
self::DATAREQUEST_STATUS_EXPIRED,
self::DATAREQUEST_STATUS_DELETED,
];
return !in_array($status, $finalstatuses);
@@ -333,7 +545,22 @@ class api {
if ($dpoid) {
$datarequest->set('dpo', $dpoid);
}
$datarequest->set('dpocomment', $comment);
// Update the comment if necessary.
if (!empty(trim($comment))) {
$params = [
'date' => userdate(time()),
'comment' => $comment
];
$commenttosave = get_string('datecomment', 'tool_dataprivacy', $params);
// Check if there's an existing DPO comment.
$currentcomment = trim($datarequest->get('dpocomment'));
if ($currentcomment) {
// Append the new comment to the current comment and give them 1 line space in between.
$commenttosave = $currentcomment . PHP_EOL . PHP_EOL . $commenttosave;
}
$datarequest->set('dpocomment', $commenttosave);
}
return $datarequest->update();
}
@@ -428,7 +655,6 @@ class api {
* @param data_request $request The data request
* @return int|false
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
public static function notify_dpo($dpo, data_request $request) {
@@ -465,6 +691,8 @@ class api {
'requestedby' => $requestedby->fullname,
'requesttype' => $typetext,
'requestdate' => userdate($requestdata->timecreated),
'requestorigin' => $SITE->fullname,
'requestoriginurl' => new moodle_url('/'),
'requestcomments' => $requestdata->messagehtml,
'datarequestsurl' => $datarequestsurl
];
@@ -490,16 +718,79 @@ class api {
/**
* Checks whether a non-DPO user can make a data request for another user.
*
* @param int $user The user ID of the target user.
* @param int $requester The user ID of the user making the request.
* @return bool
* @throws coding_exception
* @param int $user The user ID of the target user.
* @param int $requester The user ID of the user making the request.
* @return bool
*/
public static function can_create_data_request_for_user($user, $requester = null) {
$usercontext = \context_user::instance($user);
return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
}
/**
* Require that the current user can make a data request for the specified other user.
*
* @param int $user The user ID of the target user.
* @param int $requester The user ID of the user making the request.
* @return bool
*/
public static function require_can_create_data_request_for_user($user, $requester = null) {
$usercontext = \context_user::instance($user);
require_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
return true;
}
/**
* Checks whether a user can download a data request.
*
* @param int $userid Target user id (subject of data request)
* @param int $requesterid Requester user id (person who requsted it)
* @param int|null $downloaderid Person who wants to download user id (default current)
* @return bool
* @throws coding_exception
*/
public static function can_download_data_request_for_user($userid, $requesterid, $downloaderid = null) {
global $USER;
if (!$downloaderid) {
$downloaderid = $USER->id;
}
$usercontext = \context_user::instance($userid);
// If it's your own and you have the right capability, you can download it.
if ($userid == $downloaderid && has_capability('tool/dataprivacy:downloadownrequest', $usercontext, $downloaderid)) {
return true;
}
// If you can download anyone's in that context, you can download it.
if (has_capability('tool/dataprivacy:downloadallrequests', $usercontext, $downloaderid)) {
return true;
}
// If you can have the 'child access' ability to request in that context, and you are the one
// who requested it, then you can download it.
if ($requesterid == $downloaderid && self::can_create_data_request_for_user($userid, $requesterid)) {
return true;
}
return false;
}
/**
* Gets an action menu link to download a data request.
*
* @param \context_user $usercontext User context (of user who the data is for)
* @param int $requestid Request id
* @return \action_menu_link_secondary Action menu link
* @throws coding_exception
*/
public static function get_download_link(\context_user $usercontext, $requestid) {
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id,
'tool_dataprivacy', 'export', $requestid, '/', 'export.zip', true);
$downloadtext = get_string('download', 'tool_dataprivacy');
return new \action_menu_link_secondary($downloadurl, null, $downloadtext);
}
/**
* Creates a new data purpose.
*
@@ -507,8 +798,6 @@ class api {
* @return \tool_dataprivacy\purpose.
*/
public static function create_purpose(stdClass $record) {
self::check_can_manage_data_registry();
$purpose = new purpose(0, $record);
$purpose->create();
@@ -522,8 +811,6 @@ class api {
* @return \tool_dataprivacy\purpose.
*/
public static function update_purpose(stdClass $record) {
self::check_can_manage_data_registry();
if (!isset($record->sensitivedatareasons)) {
$record->sensitivedatareasons = '';
}
@@ -543,8 +830,6 @@ class api {
* @return bool
*/
public static function delete_purpose($id) {
self::check_can_manage_data_registry();
$purpose = new purpose($id);
if ($purpose->is_used()) {
throw new \moodle_exception('Purpose with id ' . $id . ' can not be deleted because it is used.');
@@ -558,8 +843,6 @@ class api {
* @return \tool_dataprivacy\purpose[]
*/
public static function get_purposes() {
self::check_can_manage_data_registry();
return purpose::get_records([], 'name', 'ASC');
}
@@ -570,8 +853,6 @@ class api {
* @return \tool_dataprivacy\category.
*/
public static function create_category(stdClass $record) {
self::check_can_manage_data_registry();
$category = new category(0, $record);
$category->create();
@@ -585,8 +866,6 @@ class api {
* @return \tool_dataprivacy\category.
*/
public static function update_category(stdClass $record) {
self::check_can_manage_data_registry();
$category = new category($record->id);
$category->from_record($record);
@@ -602,8 +881,6 @@ class api {
* @return bool
*/
public static function delete_category($id) {
self::check_can_manage_data_registry();
$category = new category($id);
if ($category->is_used()) {
throw new \moodle_exception('Category with id ' . $id . ' can not be deleted because it is used.');
@@ -617,8 +894,6 @@ class api {
* @return \tool_dataprivacy\category[]
*/
public static function get_categories() {
self::check_can_manage_data_registry();
return category::get_records([], 'name', 'ASC');
}
@@ -629,8 +904,6 @@ class api {
* @return \tool_dataprivacy\context_instance
*/
public static function set_context_instance($record) {
self::check_can_manage_data_registry($record->contextid);
if ($instance = context_instance::get_record_by_contextid($record->contextid, false)) {
// Update.
$instance->from_record($record);
@@ -657,7 +930,6 @@ class api {
* @return null
*/
public static function unset_context_instance(context_instance $instance) {
self::check_can_manage_data_registry($instance->get('contextid'));
$instance->delete();
}
@@ -671,9 +943,6 @@ class api {
public static function set_contextlevel($record) {
global $DB;
// Only manager at system level can set this.
self::check_can_manage_data_registry();
if ($record->contextlevel != CONTEXT_SYSTEM && $record->contextlevel != CONTEXT_USER) {
throw new \coding_exception('Only context system and context user can set a contextlevel ' .
'purpose and retention');
@@ -704,8 +973,7 @@ class api {
* @param int $forcedvalue Use this categoryid value as if this was this context instance category.
* @return category|false
*/
public static function get_effective_context_category(\context $context, $forcedvalue=false) {
self::check_can_manage_data_registry($context->id);
public static function get_effective_context_category(\context $context, $forcedvalue = false) {
if (!data_registry::defaults_set()) {
return false;
}
@@ -720,8 +988,7 @@ class api {
* @param int $forcedvalue Use this purposeid value as if this was this context instance purpose.
* @return purpose|false
*/
public static function get_effective_context_purpose(\context $context, $forcedvalue=false) {
self::check_can_manage_data_registry($context->id);
public static function get_effective_context_purpose(\context $context, $forcedvalue = false) {
if (!data_registry::defaults_set()) {
return false;
}
@@ -733,16 +1000,14 @@ class api {
* Returns the effective category given a context level.
*
* @param int $contextlevel
* @param int $forcedvalue Use this categoryid value as if this was this context level category.
* @return category|false
*/
public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
self::check_can_manage_data_registry(\context_system::instance()->id);
public static function get_effective_contextlevel_category($contextlevel) {
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
return data_registry::get_effective_contextlevel_value($contextlevel, 'category');
}
/**
@@ -753,7 +1018,6 @@ class api {
* @return purpose|false
*/
public static function get_effective_contextlevel_purpose($contextlevel, $forcedvalue=false) {
self::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
@@ -768,8 +1032,6 @@ class api {
* @return \tool_dataprivacy\expired_context
*/
public static function create_expired_context($contextid) {
self::check_can_manage_data_registry();
$record = (object)[
'contextid' => $contextid,
'status' => expired_context::STATUS_EXPIRED,
@@ -787,8 +1049,6 @@ class api {
* @return bool True on success.
*/
public static function delete_expired_context($id) {
self::check_can_manage_data_registry();
$expiredcontext = new expired_context($id);
return $expiredcontext->delete();
}
@@ -801,8 +1061,6 @@ class api {
* @return null
*/
public static function set_expired_context_status(expired_context $expiredctx, $status) {
self::check_can_manage_data_registry();
$expiredctx->set('status', $status);
$expiredctx->save();
}
@@ -816,6 +1074,8 @@ class api {
*/
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
$request = new data_request($requestid);
$user = \core_user::get_user($request->get('userid'));
$isconfigured = data_registry::defaults_set();
foreach ($clcollection as $contextlist) {
// Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
$clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
@@ -824,12 +1084,20 @@ class api {
// Store the associated contexts in the contextlist.
foreach ($contextlist->get_contextids() as $contextid) {
if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
if ($isconfigured && self::DATAREQUEST_TYPE_DELETE == $request->get('type')) {
// Data can only be deleted from it if the context is either expired, or unprotected.
// Note: We can only check whether a context is expired or unprotected if the site is configured and
// defaults are set appropriately. If they are not, we treat all contexts as though they are
// unprotected.
$context = \context::instance_by_id($contextid);
if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
$purpose = static::get_effective_context_purpose($context);
// Data can only be deleted from it if the context is either expired, or unprotected.
if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $user)) {
continue;
}
}
$context = new contextlist_context();
$context->set('contextid', $contextid)
->set('contextlistid', $contextlistid)
@@ -900,10 +1168,11 @@ class api {
* @return contextlist_collection the collection of approved_contextlist objects.
*/
public static function get_approved_contextlist_collection_for_request(data_request $request) : contextlist_collection {
global $DB;
$foruser = core_user::get_user($request->get('userid'));
$isconfigured = data_registry::defaults_set();
// Fetch all approved contextlists and create the core_privacy\local\request\contextlist objects here.
global $DB;
$sql = "SELECT cl.component, ctx.contextid
FROM {" . request_contextlist::TABLE . "} rcl
JOIN {" . contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
@@ -927,6 +1196,18 @@ class api {
$contexts = [];
}
if ($isconfigured && $request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
$context = \context::instance_by_id($record->contextid);
$purpose = static::get_effective_context_purpose($context);
// Data can only be deleted from it if the context is either expired, or unprotected.
// Note: We can only check whether a context is expired or unprotected if the site is configured and
// defaults are set appropriately. If they are not, we treat all contexts as though they are
// unprotected.
if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $foruser)) {
continue;
}
}
$contexts[] = $record->contextid;
$lastcomponent = $record->component;
}
@@ -939,4 +1220,110 @@ class api {
return $approvedcollection;
}
/**
* Updates the default category and purpose for a given context level (and optionally, a plugin).
*
* @param int $contextlevel The context level.
* @param int $categoryid The ID matching the category.
* @param int $purposeid The ID matching the purpose record.
* @param int $activity The name of the activity that we're making a defaults configuration for.
* @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
* @return boolean True if set/unset config succeeds. Otherwise, it throws an exception.
*/
public static function set_context_defaults($contextlevel, $categoryid, $purposeid, $activity = null, $override = false) {
global $DB;
// Get the class name associated with this context level.
$classname = context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname, $activity);
// Check the default category to be set.
if ($categoryid == context_instance::INHERIT) {
unset_config($categoryvar, 'tool_dataprivacy');
} else {
// Make sure the given category ID exists first.
$categorypersistent = new category($categoryid);
$categorypersistent->read();
// Then set the new default value.
set_config($categoryvar, $categoryid, 'tool_dataprivacy');
}
// Check the default purpose to be set.
if ($purposeid == context_instance::INHERIT) {
// If the defaults is set to inherit, just unset the config value.
unset_config($purposevar, 'tool_dataprivacy');
} else {
// Make sure the given purpose ID exists first.
$purposepersistent = new purpose($purposeid);
$purposepersistent->read();
// Then set the new default value.
set_config($purposevar, $purposeid, 'tool_dataprivacy');
}
// Unset instances that have been assigned with custom purpose and category, if override was specified.
if ($override) {
// We'd like to find context IDs that we want to unset.
$statements = ["SELECT c.id as contextid FROM {context} c"];
// Based on this context level.
$params = ['contextlevel' => $contextlevel];
if ($contextlevel == CONTEXT_MODULE) {
// If we're deleting module context instances, we need to make sure the instance ID is in the course modules table.
$statements[] = "JOIN {course_modules} cm ON cm.id = c.instanceid";
// And that the module is listed on the modules table.
$statements[] = "JOIN {modules} m ON m.id = cm.module";
if ($activity) {
// If we're overriding for an activity module, make sure that the context instance matches that activity.
$statements[] = "AND m.name = :modname";
$params['modname'] = $activity;
}
}
// Make sure this context instance exists in the tool_dataprivacy_ctxinstance table.
$statements[] = "JOIN {tool_dataprivacy_ctxinstance} tdc ON tdc.contextid = c.id";
// And that the context level of this instance matches the given context level.
$statements[] = "WHERE c.contextlevel = :contextlevel";
// Build our SQL query by gluing the statements.
$sql = implode("\n", $statements);
// Get the context records matching our query.
$contextids = $DB->get_fieldset_sql($sql, $params);
// Delete the matching context instances.
foreach ($contextids as $contextid) {
if ($instance = context_instance::get_record_by_contextid($contextid, false)) {
self::unset_context_instance($instance);
}
}
}
return true;
}
/**
* Format the supplied date interval as a retention period.
*
* @param \DateInterval $interval
* @return string
*/
public static function format_retention_period(\DateInterval $interval) : string {
// 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');
}
return $formattedtime;
}
}
+127 -125
View File
@@ -28,10 +28,7 @@
namespace tool_dataprivacy;
use coding_exception;
use tool_dataprivacy\purpose;
use tool_dataprivacy\category;
use tool_dataprivacy\contextlevel;
use tool_dataprivacy\context_instance;
use core\persistent;
defined('MOODLE_INTERNAL') || die();
@@ -44,28 +41,25 @@ require_once($CFG->libdir . '/coursecatlib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_registry {
/**
* @var array Inheritance between context levels.
*/
private static $contextlevelinheritance = [
CONTEXT_USER => [CONTEXT_SYSTEM],
CONTEXT_COURSECAT => [CONTEXT_SYSTEM],
CONTEXT_COURSE => [CONTEXT_COURSECAT, CONTEXT_SYSTEM],
CONTEXT_MODULE => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
CONTEXT_BLOCK => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
];
/**
* Returns purpose and category var names from a context class name
*
* @param string $classname
* @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) {
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 [
$classname . '_purpose',
$classname . '_category',
$purposevar,
$categoryvar
];
}
@@ -74,22 +68,35 @@ class data_registry {
*
* The caller code is responsible of checking that $contextlevel is an integer.
*
* @param int $contextlevel
* @return int|false[]
* @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) {
public static function get_defaults($contextlevel, $pluginname = '') {
$classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname);
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 = false;
$purposeid = context_instance::NOTSET;
}
if (empty($categoryid)) {
$categoryid = false;
$categoryid = context_instance::NOTSET;
}
return [$purposeid, $categoryid];
@@ -171,55 +178,93 @@ class data_registry {
* @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) {
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 ($forcedvalue === false) {
$instance = context_instance::get_record_by_contextid($context->id, false);
if (!$instance) {
// If the instance does not have a value defaults to not set, so we grab the context level default as its value.
$instancevalue = context_instance::NOTSET;
} else {
$instancevalue = $instance->get($fieldname);
}
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 {
$instancevalue = $forcedvalue;
// 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;
// Not set.
if ($instancevalue == context_instance::NOTSET) {
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]);
// The effective value varies depending on the context level.
if ($context->contextlevel == CONTEXT_USER) {
// Use the context level value as we don't allow people to set specific instances values.
return self::get_effective_contextlevel_value($context->contextlevel, $element);
} else {
// Use the default context level value.
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
$context->contextlevel
);
return self::get_element_instance($element, $$fieldname);
$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);
}
}
// Specific value for this context instance.
if ($instancevalue != context_instance::INHERIT) {
return self::get_element_instance($element, $instancevalue);
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;
}
}
// This context is using inherited so let's return the parent effective value.
$parentcontext = $context->get_parent_context();
if (!$parentcontext) {
return false;
}
// The forced value should not be transmitted to parent contexts.
return self::get_effective_context_value($parentcontext, $element);
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
/**
@@ -231,11 +276,9 @@ class data_registry {
*
* @param int $contextlevel
* @param string $element 'category' or 'purpose'
* @param int $forcedvalue Use this value as if this was this context level purpose.
* @return \tool_dataprivacy\purpose|false
*/
public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = 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.');
}
@@ -246,52 +289,30 @@ class data_registry {
'have a purpose or a category.');
}
if ($forcedvalue === false) {
$instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
if (!$instance) {
// If the context level does not have a value defaults to not set, so we grab the context level default as
// its value.
$instancevalue = context_instance::NOTSET;
} else {
$instancevalue = $instance->get($fieldname);
}
} else {
$instancevalue = $forcedvalue;
}
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
// Not set -> Use the default context level value.
if ($instancevalue == context_instance::NOTSET) {
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);
}
// Specific value for this context instance.
if ($instancevalue != context_instance::INHERIT) {
return self::get_element_instance($element, $instancevalue);
}
if ($contextlevel == CONTEXT_SYSTEM) {
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
// If we reach this point is that we are inheriting so get the parent context level and repeat.
$parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
// Forced value are intentionally not passed as the force value should only affect the immediate context level.
return self::get_effective_contextlevel_value($parentcontextlevel, $element);
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 $forcedpurposevalue Use this value as if this was this context level purpose.
* @param int $forcedcategoryvalue Use this value as if this was this context level category.
* @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) {
list($purposeid, $categoryid) = self::get_defaults($contextlevel);
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) {
@@ -301,37 +322,19 @@ class data_registry {
$categoryid = $forcedcategoryvalue;
}
// Not set == INHERIT for defaults.
if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
$purposeid = false;
}
if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
$categoryid = false;
}
if ($contextlevel == CONTEXT_USER) {
// Only user context levels inherit from a parent context level.
list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
$purposeid = (int)$parentpurposeid;
}
list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
// Not set == INHERIT for defaults.
if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
$parentpurposeid = false;
}
if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
$parentcategoryid = false;
}
if ($purposeid === false && $parentpurposeid) {
$purposeid = $parentpurposeid;
}
if ($categoryid === false && $parentcategoryid) {
$categoryid = $parentcategoryid;
}
if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
$categoryid = $parentcategoryid;
}
}
// They may still be false, but we return anyway.
return [$purposeid, $categoryid];
}
@@ -344,7 +347,6 @@ class data_registry {
* @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');
}
@@ -21,7 +21,9 @@
* @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;
@@ -37,6 +39,12 @@ 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.
*
@@ -85,6 +93,9 @@ class data_request extends persistent {
api::DATAREQUEST_STATUS_COMPLETE,
api::DATAREQUEST_STATUS_CANCELLED,
api::DATAREQUEST_STATUS_REJECTED,
api::DATAREQUEST_STATUS_DOWNLOAD_READY,
api::DATAREQUEST_STATUS_EXPIRED,
api::DATAREQUEST_STATUS_DELETED,
],
'type' => PARAM_INT
],
@@ -108,6 +119,173 @@ class data_request extends persistent {
'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);
}
}
}
}
/**
* Whether this request is in a state appropriate for reset/resubmission.
*
* Note: This does not check whether any other completed requests exist for this user.
*
* @return bool
*/
public function is_resettable() : bool {
if (api::DATAREQUEST_TYPE_OTHERS == $this->get('type')) {
// It is not possible to reset 'other' reqeusts.
return false;
}
$resettable = [
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => true,
api::DATAREQUEST_STATUS_APPROVED => true,
api::DATAREQUEST_STATUS_REJECTED => true,
];
return isset($resettable[$this->get('status')]);
}
/**
* Whether this request is 'active'.
*
* @return bool
*/
public function is_active() : bool {
$active = [
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => true,
api::DATAREQUEST_STATUS_APPROVED => true,
];
return isset($active[$this->get('status')]);
}
/**
* Reject this request and resubmit it as a fresh request.
*
* Note: This does not check whether any other completed requests exist for this user.
*
* @return self
*/
public function resubmit_request() : data_request {
if ($this->is_active()) {
$this->set('status', api::DATAREQUEST_STATUS_REJECTED)->save();
}
if (!$this->is_resettable()) {
throw new \moodle_exception('cannotreset', 'tool_dataprivacy');
}
$currentdata = $this->to_record();
unset($currentdata->id);
$clone = api::create_data_request($this->get('userid'), $this->get('type'));
$clone->set('comments', $this->get('comments'));
$clone->set('dpo', $this->get('dpo'));
$clone->set('requestedby', $this->get('requestedby'));
$clone->save();
return $clone;
}
}
@@ -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);
}
}
}
}
@@ -60,12 +60,27 @@ class expired_context extends \core\persistent {
* @return array
*/
protected static function define_properties() {
return array(
'contextid' => array(
return [
'contextid' => [
'type' => PARAM_INT,
'description' => 'The context id.',
),
'status' => array(
],
'defaultexpired' => [
'type' => PARAM_INT,
'description' => 'Whether to default retention period for the purpose has been reached',
'default' => 1,
],
'expiredroles' => [
'type' => PARAM_TEXT,
'description' => 'This list of roles to include during deletion',
'default' => '',
],
'unexpiredroles' => [
'type' => PARAM_TEXT,
'description' => 'This list of roles to exclude during deletion',
'default' => '',
],
'status' => [
'choices' => [
self::STATUS_EXPIRED,
self::STATUS_APPROVED,
@@ -73,8 +88,8 @@ class expired_context extends \core\persistent {
],
'type' => PARAM_INT,
'description' => 'The deletion status of the context.',
),
);
],
];
}
/**
@@ -159,4 +174,205 @@ class expired_context extends \core\persistent {
return $DB->count_records_sql($sql, $params);
}
/**
* Set the list of role IDs for either expiredroles, or unexpiredroles.
*
* @param string $field
* @param int[] $roleids
* @return expired_context
*/
protected function set_roleids_for(string $field, array $roleids) : expired_context {
$roledata = json_encode($roleids);
$this->raw_set($field, $roledata);
return $this;
}
/**
* Get the list of role IDs for either expiredroles, or unexpiredroles.
*
* @param string $field
* @return int[]
*/
protected function get_roleids_for(string $field) {
$value = $this->raw_get($field);
if (empty($value)) {
return [];
}
return json_decode($value);
}
/**
* Set the list of unexpired role IDs.
*
* @param int[] $roleids
* @return expired_context
*/
protected function set_unexpiredroles(array $roleids) : expired_context {
$this->set_roleids_for('unexpiredroles', $roleids);
return $this;
}
/**
* Add a set of role IDs to the list of expired role IDs.
*
* @param int[] $roleids
* @return expired_context
*/
public function add_expiredroles(array $roleids) : expired_context {
$existing = $this->get('expiredroles');
$newvalue = array_merge($existing, $roleids);
$this->set('expiredroles', $newvalue);
return $this;
}
/**
* Add a set of role IDs to the list of unexpired role IDs.
*
* @param int[] $roleids
* @return unexpired_context
*/
public function add_unexpiredroles(array $roleids) : expired_context {
$existing = $this->get('unexpiredroles');
$newvalue = array_merge($existing, $roleids);
$this->set('unexpiredroles', $newvalue);
return $this;
}
/**
* Set the list of expired role IDs.
*
* @param int[] $roleids
* @return expired_context
*/
protected function set_expiredroles(array $roleids) : expired_context {
$this->set_roleids_for('expiredroles', $roleids);
return $this;
}
/**
* Get the list of expired role IDs.
*
* @return int[]
*/
protected function get_expiredroles() {
return $this->get_roleids_for('expiredroles');
}
/**
* Get the list of unexpired role IDs.
*
* @return int[]
*/
protected function get_unexpiredroles() {
return $this->get_roleids_for('unexpiredroles');
}
/**
* Create a new expired_context based on the context, and expiry_info object.
*
* @param \context $context
* @param expiry_info $info
* @param boolean $save
* @return expired_context
*/
public static function create_from_expiry_info(\context $context, expiry_info $info, bool $save = true) : expired_context {
$record = (object) [
'contextid' => $context->id,
'status' => self::STATUS_EXPIRED,
'defaultexpired' => (int) $info->is_default_expired(),
];
$expiredcontext = new static(0, $record);
$expiredcontext->set('expiredroles', $info->get_expired_roles());
$expiredcontext->set('unexpiredroles', $info->get_unexpired_roles());
if ($save) {
$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) : expired_context {
$save = false;
// Compare the expiredroles.
$thisexpired = $this->get('expiredroles');
$infoexpired = $info->get_expired_roles();
sort($thisexpired);
sort($infoexpired);
if ($infoexpired != $thisexpired) {
$this->set('expiredroles', $infoexpired);
$save = true;
}
// Compare the unexpiredroles.
$thisunexpired = $this->get('unexpiredroles');
$infounexpired = $info->get_unexpired_roles();
sort($thisunexpired);
sort($infounexpired);
if ($infounexpired != $thisunexpired) {
$this->set('unexpiredroles', $infounexpired);
$save = true;
}
if (empty($this->get('defaultexpired')) == $info->is_default_expired()) {
$this->set('defaultexpired', (int) $info->is_default_expired());
$save = true;
}
if ($save) {
$this->set('status', self::STATUS_EXPIRED);
$this->save();
}
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() : bool {
return ($this->get('status') == self::STATUS_APPROVED);
}
/**
* Check whether this expired_context record has already been cleaned.
*
* @return bool
*/
public function is_complete() : bool {
return ($this->get('status') == self::STATUS_CLEANED);
}
/**
* 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() : bool {
return $this->get('defaultexpired') && empty($this->get('unexpiredroles'));
}
}
File diff suppressed because it is too large Load Diff
@@ -1,135 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Expired contexts manager for CONTEXT_COURSE, CONTEXT_MODULE and CONTEXT_BLOCK.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use tool_dataprivacy\purpose;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager for CONTEXT_COURSE, CONTEXT_MODULE and CONTEXT_BLOCK.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_course_related_contexts extends \tool_dataprivacy\expired_contexts_manager {
/**
* Course-related context levels.
*
* @return int[]
*/
protected function get_context_levels() {
return [CONTEXT_MODULE, CONTEXT_BLOCK, CONTEXT_COURSE];
}
/**
* Returns a recordset with user context instances that are possibly expired (to be confirmed by get_recordset_callback).
*
* @return \stdClass[]
*/
protected function get_expired_contexts() {
global $DB;
// Including context info + course end date + purposeid (this last one only if defined).
$fields = 'ctx.id AS id, ctxcourse.enddate AS courseenddate, dpctx.purposeid AS purposeid, ' .
\context_helper::get_preload_record_columns_sql('ctx');
// 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.
$sql = "SELECT $fields
FROM {context} ctx
JOIN (
SELECT c.enddate, subctx.path
FROM {context} subctx
JOIN {course} c
ON subctx.contextlevel = ? AND subctx.instanceid = c.id
WHERE c.enddate < ? AND c.enddate > 0
) 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 expiredctx.id IS NULL
ORDER BY ctx.contextlevel DESC, ctx.path";
$possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_COURSE, time()]);
$expiredcontexts = [];
$excludedcontextids = [];
foreach ($possiblyexpired as $record) {
\context_helper::preload_from_record($record);
// No strict checking as the context may already be deleted (e.g. we just deleted a course,
// module contexts below it will not exist).
$context = \context::instance_by_id($record->id, false);
if (!$context) {
continue;
}
// We pass the value we just got from SQL so get_effective_context_purpose don't need to query
// the db again to retrieve it. If there is no tool_dataprovider_ctxinstance record
// $record->purposeid will be null which is ok as it would force get_effective_context_purpose
// to return the default purpose for the context context level (no db queries involved).
$purposevalue = $record->purposeid !== null ? $record->purposeid : context_instance::NOTSET;
// It should be cheap as system purposes and context level purposes will be retrieved from a cache most of the time.
$purpose = api::get_effective_context_purpose($context, $purposevalue);
$dt = new \DateTime();
$dt->setTimestamp($record->courseenddate);
$di = new \DateInterval($purpose->get('retentionperiod'));
$dt->add($di);
if (time() < $dt->getTimestamp()) {
// Exclude this context ID as it has not reached the retention period yet.
$excludedcontextids[] = $context->id;
continue;
}
// Check if this context has children that have not yet expired.
$hasunexpiredchildren = false;
$children = $context->get_child_contexts();
foreach ($children as $child) {
if (in_array($child->id, $excludedcontextids)) {
$hasunexpiredchildren = true;
break;
}
}
if ($hasunexpiredchildren) {
// Exclude this context ID as it has children that have not yet expired.
$excludedcontextids[] = $context->id;
continue;
}
$expiredcontexts[$context->id] = $context;
}
return $expiredcontexts;
}
}
@@ -1,151 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Expired contexts manager for CONTEXT_USER.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use tool_dataprivacy\purpose;
use tool_dataprivacy\context_instance;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager for CONTEXT_USER.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
/**
* Only user level.
*
* @return int[]
*/
protected function get_context_levels() {
return [CONTEXT_USER];
}
/**
* Returns the user context instances that are expired.
*
* @return \stdClass[]
*/
protected function get_expired_contexts() {
global $DB;
// Including context info + last login timestamp.
$fields = 'ctx.id AS id, ' . \context_helper::get_preload_record_columns_sql('ctx');
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
// Calculate what is considered expired according to the context level effective purpose (= now + retention period).
$expiredtime = new \DateTime();
$retention = new \DateInterval($purpose->get('retentionperiod'));
$expiredtime->sub($retention);
$sql = "SELECT $fields FROM {context} ctx
JOIN {user} u ON ctx.contextlevel = ? AND ctx.instanceid = u.id
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx ON ctx.id = expiredctx.contextid
WHERE u.lastaccess <= ? AND u.lastaccess > 0 AND expiredctx.id IS NULL
ORDER BY ctx.path, ctx.contextlevel ASC";
$possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_USER, $expiredtime->getTimestamp()]);
$expiredcontexts = [];
foreach ($possiblyexpired as $record) {
\context_helper::preload_from_record($record);
// No strict checking as the context may already be deleted (e.g. we just deleted a course,
// module contexts below it will not exist).
$context = \context::instance_by_id($record->id, false);
if (!$context) {
continue;
}
if (is_siteadmin($context->instanceid)) {
continue;
}
$courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
foreach ($courses as $course) {
if (!$course->enddate) {
// We can not know it what is going on here, so we prefer to be conservative.
continue 2;
}
if ($course->enddate >= time()) {
// Future or ongoing course.
continue 2;
}
}
$expiredcontexts[$context->id] = $context;
}
return $expiredcontexts;
}
/**
* Deletes user data from the provided context.
*
* Overwritten to delete the user.
*
* @param \core_privacy\manager $privacymanager
* @param \tool_dataprivacy\expired_context $expiredctx
* @return \context|false
*/
protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
if (!$context) {
api::delete_expired_context($expiredctx->get('contextid'));
return false;
}
if (!PHPUNIT_TEST) {
mtrace('Deleting context ' . $context->id . ' - ' .
shorten_text($context->get_context_name(true, true)));
}
// To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
// contexts for the user that may still exist and call delete_data_for_user().
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
foreach ($contextlistcollection as $contextlist) {
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
$user,
$contextlist->get_component(),
$contextlist->get_contextids()
));
}
$privacymanager->delete_data_for_user($approvedlistcollection);
api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
// Delete the user.
delete_user($user);
return $context;
}
}
@@ -0,0 +1,207 @@
<?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 $fullyexpired = false;
/** @var bool Whether the default expiry value of this purpose has been reached */
protected $defaultexpiryreached = false;
/** @var bool Whether the default purpose is protected */
protected $defaultprotected = false;
/** @var int[] List of expires roles */
protected $expired = [];
/** @var int[] List of unexpires roles */
protected $unexpired = [];
/** @var int[] List of unexpired roles which are also protected */
protected $protectedroles = [];
/**
* Constructor for the expiry_info class.
*
* @param bool $default Whether the default expiry period for this context has been reached.
* @param bool $defaultprotected Whether the default expiry is protected.
* @param int[] $expired A list of roles in this context which have explicitly expired.
* @param int[] $unexpired A list of roles in this context which have not yet expired.
* @param int[] $protectedroles A list of unexpired roles in this context which are protected.
*/
public function __construct(bool $default, bool $defaultprotected, array $expired, array $unexpired, array $protectedroles) {
$this->defaultexpiryreached = $default;
$this->defaultprotected = $defaultprotected;
$this->expired = $expired;
$this->unexpired = $unexpired;
$this->protectedroles = $protectedroles;
}
/**
* 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() : bool {
return $this->defaultexpiryreached && empty($this->unexpired);
}
/**
* Whether any part of this context has expired.
*
* @return bool
*/
public function is_any_expired() : bool {
if ($this->is_fully_expired()) {
return true;
}
if (!empty($this->get_expired_roles())) {
return true;
}
if ($this->is_default_expired()) {
return true;
}
return false;
}
/**
* Get the list of explicitly expired role IDs.
* Note: This does not list roles which have been expired via the default retention policy being reached.
*
* @return int[]
*/
public function get_expired_roles() : array {
if ($this->is_default_expired()) {
return [];
}
return $this->expired;
}
/**
* Check whether the specified role is explicitly expired.
* Note: This does not list roles which have been expired via the default retention policy being reached.
*
* @param int $roleid
* @return bool
*/
public function is_role_expired(int $roleid) : bool {
return false !== array_search($roleid, $this->expired);
}
/**
* Whether the default retention policy has been reached.
*
* @return bool
*/
public function is_default_expired() : bool {
return $this->defaultexpiryreached;
}
/**
* Whether the default purpose is protected.
*
* @return bool
*/
public function is_default_protected() : bool {
return $this->defaultprotected;
}
/**
* Get the list of unexpired role IDs.
*
* @return int[]
*/
public function get_unexpired_roles() : array {
return $this->unexpired;
}
/**
* Get the list of unexpired protected roles.
*
* @return int[]
*/
public function get_unexpired_protected_roles() : array {
return array_keys(array_filter($this->protectedroles));
}
/**
* Get a list of all overridden roles which are unprotected.
* @return int[]
*/
public function get_unprotected_overridden_roles() : array {
$allroles = array_merge($this->expired, $this->unexpired);
return array_diff($allroles, $this->protectedroles);
}
/**
* 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) : expiry_info {
if ($child->is_fully_expired()) {
return $this;
}
// If the child is not fully expired, then none of the parents can be either.
$this->fullyexpired = false;
// Remove any role in this node which is not expired in the child.
foreach ($this->expired as $key => $roleid) {
if (!$child->is_role_expired($roleid)) {
unset($this->expired[$key]);
}
}
array_merge($this->unexpired, $child->get_unexpired_roles());
if (!$child->is_default_expired()) {
$this->defaultexpiryreached = false;
}
return $this;
}
}
+572 -29
View File
@@ -31,6 +31,7 @@ use context_helper;
use context_system;
use context_user;
use core\invalid_persistent_exception;
use core\notification;
use core_user;
use dml_exception;
use external_api;
@@ -91,17 +92,30 @@ class external extends external_api {
]);
$requestid = $params['requestid'];
// Validate context.
// Validate context and access to manage the registry.
$context = context_user::instance($USER->id);
self::validate_context($context);
// Ensure the request exists.
$select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
$params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
$requestexists = data_request::record_exists_select($select, $params);
$requests = data_request::get_records_select($select, $params);
$requestexists = count($requests) === 1;
$result = false;
if ($requestexists) {
$request = reset($requests);
$datasubject = $request->get('userid');
if ($datasubject !== $USER->id) {
// The user is not the subject. Check that they can cancel this request.
if (!api::can_create_data_request_for_user($datasubject)) {
$forusercontext = \context_user::instance($datasubject);
throw new required_capability_exception($forusercontext,
'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
}
}
// TODO: Do we want a request to be non-cancellable past a certain point? E.g. When it's already approved/processing.
$result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
} else {
@@ -144,7 +158,7 @@ class external extends external_api {
}
/**
* Deny a data request.
* Make a general enquiry to a DPO.
*
* @since Moodle 3.5
* @param string $message The message to be sent to the DPO.
@@ -210,7 +224,7 @@ class external extends external_api {
}
/**
* Parameter description for deny_data_request().
* Parameter description for contact_dpo().
*
* @since Moodle 3.5
* @return external_description
@@ -222,6 +236,71 @@ class external extends external_api {
]);
}
/**
* Parameter description for mark_complete().
*
* @since Moodle 3.5.2
* @return external_function_parameters
*/
public static function mark_complete_parameters() {
return new external_function_parameters([
'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
]);
}
/**
* Mark a user's general enquiry's status as complete.
*
* @since Moodle 3.5.2
* @param int $requestid The request ID of the general enquiry.
* @return array
* @throws coding_exception
* @throws invalid_parameter_exception
* @throws invalid_persistent_exception
* @throws restricted_context_exception
* @throws dml_exception
* @throws moodle_exception
*/
public static function mark_complete($requestid) {
global $USER;
$warnings = [];
$params = external_api::validate_parameters(self::mark_complete_parameters(), [
'requestid' => $requestid,
]);
$requestid = $params['requestid'];
// Validate context and access to manage the registry.
$context = context_system::instance();
self::validate_context($context);
api::check_can_manage_data_registry();
$message = get_string('markedcomplete', 'tool_dataprivacy');
// Update the data request record.
if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
// Add notification in the session to be shown when the page is reloaded on the JS side.
notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Parameter description for mark_complete().
*
* @since Moodle 3.5.2
* @return external_description
*/
public static function mark_complete_returns() {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'The processing result'),
'warnings' => new external_warnings()
]);
}
/**
* Parameter description for get_data_request().
*
@@ -258,9 +337,9 @@ class external extends external_api {
// Validate context.
$context = context_system::instance();
self::validate_context($context);
$requestpersistent = new data_request($requestid);
require_capability('tool/dataprivacy:managedatarequests', $context);
$requestpersistent = new data_request($requestid);
$exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
$renderer = $PAGE->get_renderer('tool_dataprivacy');
$result = $exporter->export($renderer);
@@ -326,6 +405,9 @@ class external extends external_api {
$result = false;
if ($requestexists) {
$result = api::approve_data_request($requestid);
// Add notification in the session to be shown when the page is reloaded on the JS side.
notification::success(get_string('requestapproved', 'tool_dataprivacy'));
} else {
$warnings[] = [
'item' => $requestid,
@@ -353,6 +435,85 @@ class external extends external_api {
]);
}
/**
* Parameter description for bulk_approve_data_requests().
*
* @since Moodle 3.5
* @return external_function_parameters
*/
public static function bulk_approve_data_requests_parameters() {
return new external_function_parameters([
'requestids' => new external_multiple_structure(
new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
)
]);
}
/**
* Bulk approve bulk data request.
*
* @since Moodle 3.5
* @param array $requestids Array consisting the request ID's.
* @return array
* @throws coding_exception
* @throws dml_exception
* @throws invalid_parameter_exception
* @throws restricted_context_exception
* @throws moodle_exception
*/
public static function bulk_approve_data_requests($requestids) {
$warnings = [];
$result = false;
$params = external_api::validate_parameters(self::bulk_approve_data_requests_parameters(), [
'requestids' => $requestids
]);
$requestids = $params['requestids'];
// Validate context.
$context = context_system::instance();
self::validate_context($context);
require_capability('tool/dataprivacy:managedatarequests', $context);
foreach ($requestids as $requestid) {
// Ensure the request exists.
$requestexists = data_request::record_exists($requestid);
if ($requestexists) {
api::approve_data_request($requestid);
} else {
$warnings[] = [
'item' => $requestid,
'warningcode' => 'errorrequestnotfound',
'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
];
}
}
if (empty($warnings)) {
$result = true;
// Add notification in the session to be shown when the page is reloaded on the JS side.
notification::success(get_string('requestsapproved', 'tool_dataprivacy'));
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Parameter description for bulk_approve_data_requests().
*
* @since Moodle 3.5
* @return external_description
*/
public static function bulk_approve_data_requests_returns() {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'The processing result'),
'warnings' => new external_warnings()
]);
}
/**
* Parameter description for deny_data_request().
*
@@ -395,6 +556,9 @@ class external extends external_api {
$result = false;
if ($requestexists) {
$result = api::deny_data_request($requestid);
// Add notification in the session to be shown when the page is reloaded on the JS side.
notification::success(get_string('requestdenied', 'tool_dataprivacy'));
} else {
$warnings[] = [
'item' => $requestid,
@@ -422,6 +586,85 @@ class external extends external_api {
]);
}
/**
* Parameter description for bulk_deny_data_requests().
*
* @since Moodle 3.5
* @return external_function_parameters
*/
public static function bulk_deny_data_requests_parameters() {
return new external_function_parameters([
'requestids' => new external_multiple_structure(
new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
)
]);
}
/**
* Bulk deny data requests.
*
* @since Moodle 3.5
* @param array $requestids Array consisting of request ID's.
* @return array
* @throws coding_exception
* @throws dml_exception
* @throws invalid_parameter_exception
* @throws restricted_context_exception
* @throws moodle_exception
*/
public static function bulk_deny_data_requests($requestids) {
$warnings = [];
$result = false;
$params = external_api::validate_parameters(self::bulk_deny_data_requests_parameters(), [
'requestids' => $requestids
]);
$requestids = $params['requestids'];
// Validate context.
$context = context_system::instance();
self::validate_context($context);
require_capability('tool/dataprivacy:managedatarequests', $context);
foreach ($requestids as $requestid) {
// Ensure the request exists.
$requestexists = data_request::record_exists($requestid);
if ($requestexists) {
api::deny_data_request($requestid);
} else {
$warnings[] = [
'item' => $requestid,
'warningcode' => 'errorrequestnotfound',
'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
];
}
}
if (empty($warnings)) {
$result = true;
// Add notification in the session to be shown when the page is reloaded on the JS side.
notification::success(get_string('requestsdenied', 'tool_dataprivacy'));
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Parameter description for bulk_deny_data_requests().
*
* @since Moodle 3.5
* @return external_description
*/
public static function bulk_deny_data_requests_returns() {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'The processing result'),
'warnings' => new external_warnings()
]);
}
/**
* Parameter description for get_data_request().
*
@@ -519,7 +762,9 @@ class external extends external_api {
'jsonformdata' => $jsonformdata
]);
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$serialiseddata = json_decode($params['jsonformdata']);
$data = array();
@@ -587,6 +832,10 @@ class external extends external_api {
'id' => $id
]);
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$result = api::delete_purpose($params['id']);
return [
@@ -636,7 +885,9 @@ class external extends external_api {
'jsonformdata' => $jsonformdata
]);
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$serialiseddata = json_decode($params['jsonformdata']);
$data = array();
@@ -704,6 +955,10 @@ class external extends external_api {
'id' => $id
]);
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$result = api::delete_category($params['id']);
return [
@@ -753,8 +1008,9 @@ class external extends external_api {
'jsonformdata' => $jsonformdata
]);
// Extra permission checkings are delegated to api::set_contextlevel.
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$serialiseddata = json_decode($params['jsonformdata']);
$data = array();
@@ -768,7 +1024,6 @@ class external extends external_api {
$contextlevel = api::set_contextlevel($validateddata);
} else if ($errors = $mform->is_validated()) {
$warnings[] = json_encode($errors);
throw new moodle_exception('generalerror');
}
if ($contextlevel) {
@@ -823,8 +1078,9 @@ class external extends external_api {
'jsonformdata' => $jsonformdata
]);
// Extra permission checkings are delegated to api::set_context_instance.
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$serialiseddata = json_decode($params['jsonformdata']);
$data = array();
@@ -834,6 +1090,7 @@ class external extends external_api {
$customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context);
$mform = new \tool_dataprivacy\form\context_instance(null, $customdata, 'post', '', null, true, $data);
if ($validateddata = $mform->get_data()) {
api::check_can_manage_data_registry($validateddata->contextid);
$context = api::set_context_instance($validateddata);
} else if ($errors = $mform->is_validated()) {
$warnings[] = json_encode($errors);
@@ -963,9 +1220,9 @@ class external extends external_api {
]);
$ids = $params['ids'];
// Validate context.
$context = context_system::instance();
self::validate_context($context);
// Validate context and access to manage the registry.
self::validate_context(\context_system::instance());
api::check_can_manage_data_registry();
$result = true;
if (!empty($ids)) {
@@ -975,24 +1232,27 @@ class external extends external_api {
$expiredcontext = new expired_context($id);
$targetcontext = context_helper::instance_by_id($expiredcontext->get('contextid'));
// Fetch this context's child contexts. Make sure that all of the child contexts are flagged for deletion.
$childcontexts = $targetcontext->get_child_contexts();
foreach ($childcontexts as $child) {
if ($expiredchildcontext = expired_context::get_record(['contextid' => $child->id])) {
// Add this child context to the list for approval.
$expiredcontextstoapprove[] = $expiredchildcontext;
} else {
// This context has not yet been flagged for deletion.
$result = false;
$message = get_string('errorcontexthasunexpiredchildren', 'tool_dataprivacy',
$targetcontext->get_context_name(false));
$warnings[] = [
'item' => 'tool_dataprivacy_ctxexpired',
'warningcode' => 'errorcontexthasunexpiredchildren',
'message' => $message
];
// Exit the process.
break 2;
if (!$targetcontext instanceof \context_user) {
// Fetch this context's child contexts. Make sure that all of the child contexts are flagged for deletion.
// User context children do not need to be considered.
$childcontexts = $targetcontext->get_child_contexts();
foreach ($childcontexts as $child) {
if ($expiredchildcontext = expired_context::get_record(['contextid' => $child->id])) {
// Add this child context to the list for approval.
$expiredcontextstoapprove[] = $expiredchildcontext;
} else {
// This context has not yet been flagged for deletion.
$result = false;
$message = get_string('errorcontexthasunexpiredchildren', 'tool_dataprivacy',
$targetcontext->get_context_name(false));
$warnings[] = [
'item' => 'tool_dataprivacy_ctxexpired',
'warningcode' => 'errorcontexthasunexpiredchildren',
'message' => $message
];
// Exit the process.
break 2;
}
}
}
@@ -1039,6 +1299,289 @@ class external extends external_api {
]);
}
/**
* Parameters for set_context_defaults().
*
* @return external_function_parameters
*/
public static function set_context_defaults_parameters() {
return new external_function_parameters([
'contextlevel' => new external_value(PARAM_INT, 'The context level', VALUE_REQUIRED),
'category' => new external_value(PARAM_INT, 'The default category for the given context level', VALUE_REQUIRED),
'purpose' => new external_value(PARAM_INT, 'The default purpose for the given context level', VALUE_REQUIRED),
'activity' => new external_value(PARAM_PLUGIN, 'The plugin name of the activity', VALUE_DEFAULT, null),
'override' => new external_value(PARAM_BOOL, 'Whether to override existing instances with the defaults', VALUE_DEFAULT,
false),
]);
}
/**
* Updates the default category and purpose for a given context level (and optionally, a plugin).
*
* @param int $contextlevel The context level.
* @param int $category The ID matching the category.
* @param int $purpose The ID matching the purpose record.
* @param int $activity The name of the activity that we're making a defaults configuration for.
* @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
* @return array
*/
public static function set_context_defaults($contextlevel, $category, $purpose, $activity, $override) {
$warnings = [];
$params = external_api::validate_parameters(self::set_context_defaults_parameters(), [
'contextlevel' => $contextlevel,
'category' => $category,
'purpose' => $purpose,
'activity' => $activity,
'override' => $override,
]);
$contextlevel = $params['contextlevel'];
$category = $params['category'];
$purpose = $params['purpose'];
$activity = $params['activity'];
$override = $params['override'];
// Validate context.
$context = context_system::instance();
self::validate_context($context);
api::check_can_manage_data_registry();
// Set the context defaults.
$result = api::set_context_defaults($contextlevel, $category, $purpose, $activity, $override);
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Returns for set_context_defaults().
*
* @return external_single_structure
*/
public static function set_context_defaults_returns() {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'Whether the context defaults were successfully set or not'),
'warnings' => new external_warnings()
]);
}
/**
* Parameters for get_category_options().
*
* @return external_function_parameters
*/
public static function get_category_options_parameters() {
return new external_function_parameters([
'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
]);
}
/**
* Fetches a list of data category options containing category IDs as keys and the category name for the value.
*
* @param bool $includeinherit Whether to include the "Inherit" option.
* @param bool $includenotset Whether to include the "Not set" option.
* @return array
*/
public static function get_category_options($includeinherit, $includenotset) {
$warnings = [];
$params = self::validate_parameters(self::get_category_options_parameters(), [
'includeinherit' => $includeinherit,
'includenotset' => $includenotset
]);
$includeinherit = $params['includeinherit'];
$includenotset = $params['includenotset'];
$context = context_system::instance();
self::validate_context($context);
api::check_can_manage_data_registry();
$categories = api::get_categories();
$options = data_registry_page::category_options($categories, $includenotset, $includeinherit);
$categoryoptions = [];
foreach ($options as $id => $name) {
$categoryoptions[] = [
'id' => $id,
'name' => $name,
];
}
return [
'options' => $categoryoptions,
'warnings' => $warnings
];
}
/**
* Returns for get_category_options().
*
* @return external_single_structure
*/
public static function get_category_options_returns() {
$optiondefinition = new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'The category ID'),
'name' => new external_value(PARAM_TEXT, 'The category name'),
]
);
return new external_single_structure([
'options' => new external_multiple_structure($optiondefinition),
'warnings' => new external_warnings()
]);
}
/**
* Parameters for get_purpose_options().
*
* @return external_function_parameters
*/
public static function get_purpose_options_parameters() {
return new external_function_parameters([
'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
]);
}
/**
* Fetches a list of data storage purposes containing purpose IDs as keys and the purpose name for the value.
*
* @param bool $includeinherit Whether to include the "Inherit" option.
* @param bool $includenotset Whether to include the "Not set" option.
* @return array
*/
public static function get_purpose_options($includeinherit, $includenotset) {
$warnings = [];
$params = self::validate_parameters(self::get_category_options_parameters(), [
'includeinherit' => $includeinherit,
'includenotset' => $includenotset
]);
$includeinherit = $params['includeinherit'];
$includenotset = $params['includenotset'];
$context = context_system::instance();
self::validate_context($context);
$purposes = api::get_purposes();
$options = data_registry_page::purpose_options($purposes, $includenotset, $includeinherit);
$purposeoptions = [];
foreach ($options as $id => $name) {
$purposeoptions[] = [
'id' => $id,
'name' => $name,
];
}
return [
'options' => $purposeoptions,
'warnings' => $warnings
];
}
/**
* Returns for get_purpose_options().
*
* @return external_single_structure
*/
public static function get_purpose_options_returns() {
$optiondefinition = new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'The purpose ID'),
'name' => new external_value(PARAM_TEXT, 'The purpose name'),
]
);
return new external_single_structure([
'options' => new external_multiple_structure($optiondefinition),
'warnings' => new external_warnings()
]);
}
/**
* Parameters for get_activity_options().
*
* @return external_function_parameters
*/
public static function get_activity_options_parameters() {
return new external_function_parameters([
'nodefaults' => new external_value(PARAM_BOOL, 'Whether to fetch all activities or only those without defaults',
VALUE_DEFAULT, false),
]);
}
/**
* Fetches a list of activity options for setting data registry defaults.
*
* @param boolean $nodefaults If false, it will fetch all of the activities. Otherwise, it will only fetch the activities
* that don't have defaults yet (e.g. when adding a new activity module defaults).
* @return array
*/
public static function get_activity_options($nodefaults) {
$warnings = [];
$params = self::validate_parameters(self::get_activity_options_parameters(), [
'nodefaults' => $nodefaults,
]);
$nodefaults = $params['nodefaults'];
$context = context_system::instance();
self::validate_context($context);
// Get activity module plugin info.
$pluginmanager = \core_plugin_manager::instance();
$modplugins = $pluginmanager->get_enabled_plugins('mod');
$modoptions = [];
// Get the module-level defaults. data_registry::get_defaults falls back to this when there are no activity defaults.
list($levelpurpose, $levelcategory) = data_registry::get_defaults(CONTEXT_MODULE);
foreach ($modplugins as $name) {
// Check if we have default purpose and category for this module if we want don't want to fetch everything.
if ($nodefaults) {
list($purpose, $category) = data_registry::get_defaults(CONTEXT_MODULE, $name);
// Compare this with the module-level defaults.
if ($purpose !== $levelpurpose || $category !== $levelcategory) {
// If the defaults for this activity has been already set, there's no need to add this in the list of options.
continue;
}
}
$displayname = $pluginmanager->plugin_name('mod_' . $name);
$modoptions[] = (object)[
'name' => $name,
'displayname' => $displayname
];
}
return [
'options' => $modoptions,
'warnings' => $warnings
];
}
/**
* Returns for get_category_options().
*
* @return external_single_structure
*/
public static function get_activity_options_returns() {
$optionsdefinition = new external_single_structure(
[
'name' => new external_value(PARAM_TEXT, 'The plugin name of the activity'),
'displayname' => new external_value(PARAM_TEXT, 'The display name of the activity'),
]
);
return new external_single_structure([
'options' => new external_multiple_structure($optionsdefinition),
'warnings' => new external_warnings()
]);
}
/**
* Gets the structure of a tree node (link + child branches).
*
@@ -25,6 +25,8 @@ 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.
@@ -53,4 +55,25 @@ class category_exporter extends persistent_exporter {
'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;
}
}
}
@@ -102,6 +102,16 @@ class data_request_exporter extends persistent_exporter {
'optional' => true,
'default' => false
],
'approvedeny' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false
],
'canmarkcomplete' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false
],
];
}
@@ -140,14 +150,19 @@ class data_request_exporter extends persistent_exporter {
$values['messagehtml'] = text_to_html($this->persistent->get('comments'));
$values['typename'] = helper::get_request_type_string($this->persistent->get('type'));
$values['typenameshort'] = helper::get_shortened_request_type_string($this->persistent->get('type'));
$requesttype = $this->persistent->get('type');
$values['typename'] = helper::get_request_type_string($requesttype);
$values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
$values['canreview'] = false;
$values['approvedeny'] = false;
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
switch ($this->persistent->get('status')) {
case api::DATAREQUEST_STATUS_PENDING:
$values['statuslabelclass'] = 'label-default';
$values['statuslabelclass'] = 'label-info';
// Request can be manually completed for general enquiry requests.
$values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
break;
case api::DATAREQUEST_STATUS_PREPROCESSING:
$values['statuslabelclass'] = 'label-default';
@@ -156,6 +171,8 @@ class data_request_exporter extends persistent_exporter {
$values['statuslabelclass'] = 'label-info';
// DPO can review the request once it's ready.
$values['canreview'] = true;
// Whether the DPO can approve or deny the request.
$values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
break;
case api::DATAREQUEST_STATUS_APPROVED:
$values['statuslabelclass'] = 'label-info';
@@ -164,6 +181,8 @@ class data_request_exporter extends persistent_exporter {
$values['statuslabelclass'] = 'label-info';
break;
case api::DATAREQUEST_STATUS_COMPLETE:
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
case api::DATAREQUEST_STATUS_DELETED:
$values['statuslabelclass'] = 'label-success';
break;
case api::DATAREQUEST_STATUS_CANCELLED:
@@ -172,6 +191,9 @@ class data_request_exporter extends persistent_exporter {
case api::DATAREQUEST_STATUS_REJECTED:
$values['statuslabelclass'] = 'label-important';
break;
case api::DATAREQUEST_STATUS_EXPIRED:
$values['statuslabelclass'] = 'label-default';
break;
}
return $values;
+28 -13
View File
@@ -26,9 +26,9 @@ 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;
/**
@@ -78,6 +78,9 @@ class purpose_exporter extends persistent_exporter {
'multiple' => true,
'optional' => true
],
'roleoverrides' => [
'type' => PARAM_TEXT
],
];
}
@@ -124,23 +127,35 @@ class purpose_exporter extends persistent_exporter {
$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');
}
$formattedtime = \tool_dataprivacy\api::format_retention_period(new \DateInterval($retentionperiod));
} else {
$formattedtime = get_string('retentionperiodnotdefined', 'tool_dataprivacy');
}
$values['formattedretentionperiod'] = $formattedtime;
$values['roleoverrides'] = !empty($this->persistent->get_purpose_overrides());
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,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/>.
/**
* An implementation of a userlist which has been filtered and approved.
*
* @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;
defined('MOODLE_INTERNAL') || die();
/**
* An implementation of a userlist which can be filtered by role.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filtered_userlist extends \core_privacy\local\request\approved_userlist {
/**
* Apply filters to only remove users in the expireduserids list, and to remove any who are in the unexpired list.
* The unexpired list wins where a user is in both lists.
*
* @param int[] $expireduserids The list of userids for users who should be expired.
* @param int[] $unexpireduserids The list of userids for those users who should not be expired.
* @return $this
*/
public function apply_expired_context_filters(array $expireduserids, array $unexpireduserids) : filtered_userlist {
// The current userlist content.
$userids = $this->get_userids();
if (!empty($expireduserids)) {
// Now remove any not on the list of expired users.
$userids = array_intersect($userids, $expireduserids);
}
if (!empty($unexpireduserids)) {
// Remove any on the list of unexpiredusers users.
$userids = array_diff($userids, $unexpireduserids);
}
$this->set_userids($userids);
return $this;
}
}
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
use tool_dataprivacy\api;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\purpose;
/**
* Context instance data form.
@@ -144,12 +145,12 @@ class context_instance extends \core\form\persistent {
$persistent->set('contextid', $context->id);
}
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options(
api::get_purposes()
);
$categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options(
api::get_categories()
);
$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,
@@ -167,9 +168,14 @@ class context_instance extends \core\form\persistent {
$context);
$customdata['purposeretentionperiods'] = [];
foreach ($purposeoptions as $optionvalue => $unused) {
// Get the effective purpose if $optionvalue would be the selected value.
$purpose = api::get_effective_context_purpose($context, $optionvalue);
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,
@@ -186,12 +192,12 @@ class context_instance extends \core\form\persistent {
/**
* Returns the purpose display text.
*
* @param \tool_dataprivacy\purpose $effectivepurpose
* @param purpose $effectivepurpose
* @param int $retentioncontextlevel
* @param \context $context The context, just for displaying (filters) purposes.
* @return string
*/
protected static function get_retention_display_text(\tool_dataprivacy\purpose $effectivepurpose, $retentioncontextlevel, \context $context) {
protected static function get_retention_display_text(purpose $effectivepurpose, $retentioncontextlevel, \context $context) {
global $PAGE;
$renderer = $PAGE->get_renderer('tool_dataprivacy');
@@ -1,81 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the defaults form.
*
* @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\output\data_registry_page;
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Context levels defaults form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class defaults extends \moodleform {
/**
* Define the form.
*/
public function definition() {
global $OUTPUT;
$mform = $this->_form;
$mform->setDisableShortforms();
$notification = $OUTPUT->notification(get_string('defaultsinfo', 'tool_dataprivacy'),
\core\output\notification::NOTIFY_INFO);
$mform->addElement('html', $notification);
foreach ($this->_customdata['levels'] as $level => $classname) {
$mform->addElement('header', $classname . '-header',
get_string('contextlevelname' . $level, 'tool_dataprivacy'));
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$includeinherit = true;
if ($level == CONTEXT_SYSTEM) {
$includeinherit = false;
}
$categoryoptions = data_registry_page::category_options($this->_customdata['categories'], false, $includeinherit);
$purposeoptions = data_registry_page::category_options($this->_customdata['purposes'], false, $includeinherit);
$mform->addElement('select', $categoryvar, get_string('category', 'tool_dataprivacy'), $categoryoptions);
$mform->addHelpButton($categoryvar, 'categorydefault', 'tool_dataprivacy');
$mform->setType($categoryvar, PARAM_INT);
$mform->addElement('select', $purposevar, get_string('purpose', 'tool_dataprivacy'), $purposeoptions);
$mform->addHelpButton($purposevar, 'purposedefault', 'tool_dataprivacy');
$mform->setType($purposevar, PARAM_INT);
}
$this->add_action_buttons(true, get_string('savechanges'));
}
}
+456 -31
View File
@@ -41,6 +41,11 @@ class purpose extends persistent {
*/
protected static $persistentclass = 'tool_dataprivacy\\purpose';
/**
* @var array The list of current overrides.
*/
protected $existingoverrides = [];
/**
* Define the form - called by parent constructor
*/
@@ -56,41 +61,16 @@ class purpose extends persistent {
$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);
$this->add_field($this->get_lawful_base_field());
$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');
$this->add_field($this->get_sensitive_base_field());
$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->add_field($this->get_retention_period_fields());
$this->add_field($this->get_protected_field());
$this->_form->addElement('advcheckbox', 'protected', get_string('protected', 'tool_dataprivacy'),
get_string('protectedlabel', 'tool_dataprivacy'));
$this->add_override_fields();
if (!empty($this->_customdata['showbuttons'])) {
if (!$this->get_persistent()->get('id')) {
@@ -102,16 +82,360 @@ class purpose extends persistent {
}
}
/**
* Add a fieldset to the current form.
*
* @param \stdClass $data
*/
protected function add_field(\stdClass $data) {
foreach ($data->fields as $field) {
$this->_form->addElement($field);
}
if (!empty($data->helps)) {
foreach ($data->helps as $fieldname => $helpdata) {
$help = array_merge([$fieldname], $helpdata);
call_user_func_array([$this->_form, 'addHelpButton'], $help);
}
}
if (!empty($data->types)) {
foreach ($data->types as $fieldname => $type) {
$this->_form->setType($fieldname, $type);
}
}
if (!empty($data->rules)) {
foreach ($data->rules as $fieldname => $ruledata) {
$rule = array_merge([$fieldname], $ruledata);
call_user_func_array([$this->_form, 'addRule'], $rule);
}
}
if (!empty($data->defaults)) {
foreach ($data->defaults as $fieldname => $default) {
$this->_form($fieldname, $default);
}
}
}
/**
* Handle addition of relevant repeated element fields for role overrides.
*/
protected function add_override_fields() {
$purpose = $this->get_persistent();
if (empty($purpose->get('id'))) {
// It is not possible to use repeated elements in a modal form yet.
return;
}
$fields = [
$this->get_role_override_id('roleoverride_'),
$this->get_role_field('roleoverride_'),
$this->get_retention_period_fields('roleoverride_'),
$this->get_protected_field('roleoverride_'),
$this->get_lawful_base_field('roleoverride_'),
$this->get_sensitive_base_field('roleoverride_'),
];
$options = [
'type' => [],
'helpbutton' => [],
];
// Start by adding the title.
$overrideelements = [
$this->_form->createElement('header', 'roleoverride', get_string('roleoverride', 'tool_dataprivacy')),
$this->_form->createElement(
'static',
'roleoverrideoverview',
'',
get_string('roleoverrideoverview', 'tool_dataprivacy')
),
];
foreach ($fields as $fielddata) {
foreach ($fielddata->fields as $field) {
$overrideelements[] = $field;
}
if (!empty($fielddata->helps)) {
foreach ($fielddata->helps as $name => $help) {
if (!isset($options[$name])) {
$options[$name] = [];
}
$options[$name]['helpbutton'] = $help;
}
}
if (!empty($fielddata->types)) {
foreach ($fielddata->types as $name => $type) {
if (!isset($options[$name])) {
$options[$name] = [];
}
$options[$name]['type'] = $type;
}
}
if (!empty($fielddata->rules)) {
foreach ($fielddata->rules as $name => $rule) {
if (!isset($options[$name])) {
$options[$name] = [];
}
$options[$name]['rule'] = $rule;
}
}
if (!empty($fielddata->defaults)) {
foreach ($fielddata->defaults as $name => $default) {
if (!isset($options[$name])) {
$options[$name] = [];
}
$options[$name]['default'] = $default;
}
}
if (!empty($fielddata->advanceds)) {
foreach ($fielddata->advanceds as $name => $advanced) {
if (!isset($options[$name])) {
$options[$name] = [];
}
$options[$name]['advanced'] = $advanced;
}
}
}
$this->existingoverrides = $purpose->get_purpose_overrides();
$existingoverridecount = count($this->existingoverrides);
$this->repeat_elements(
$overrideelements,
$existingoverridecount,
$options,
'overrides',
'addoverride',
1,
get_string('addroleoverride', 'tool_dataprivacy')
);
}
/**
* Converts fields.
*
* @param \stdClass $data
* @return \stdClass
*/
public function filter_data_for_persistent($data) {
$data = parent::filter_data_for_persistent($data);
$classname = static::$persistentclass;
$properties = $classname::properties_definition();
$data = (object) array_filter((array) $data, function($value, $key) use ($properties) {
return isset($properties[$key]);
}, ARRAY_FILTER_USE_BOTH);
return $data;
}
/**
* Get the field for the role name.
*
* @param string $prefix The prefix to apply to the field
* @return \stdClass
*/
protected function get_role_override_id(string $prefix = '') : \stdClass {
$fieldname = "{$prefix}id";
$fielddata = (object) [
'fields' => [],
];
$fielddata->fields[] = $this->_form->createElement('hidden', $fieldname);
$fielddata->types[$fieldname] = PARAM_INT;
return $fielddata;
}
/**
* Get the field for the role name.
*
* @param string $prefix The prefix to apply to the field
* @return \stdClass
*/
protected function get_role_field(string $prefix = '') : \stdClass {
$fieldname = "{$prefix}roleid";
$fielddata = (object) [
'fields' => [],
'helps' => [],
];
$roles = [
'' => get_string('none'),
];
foreach (role_get_names() as $roleid => $role) {
$roles[$roleid] = $role->localname;
}
$fielddata->fields[] = $this->_form->createElement('select', $fieldname, get_string('role'),
$roles,
[
'multiple' => false,
]
);
$fielddata->helps[$fieldname] = ['role', 'tool_dataprivacy'];
$fielddata->defaults[$fieldname] = null;
return $fielddata;
}
/**
* Get the mform field for lawful bases.
*
* @param string $prefix The prefix to apply to the field
* @return \stdClass
*/
protected function get_lawful_base_field(string $prefix = '') : \stdClass {
$fieldname = "{$prefix}lawfulbases";
$data = (object) [
'fields' => [],
];
$bases = [];
foreach (\tool_dataprivacy\purpose::GDPR_ART_6_1_ITEMS as $article) {
$key = 'gdpr_art_6_1_' . $article;
$bases[$key] = get_string("{$key}_name", 'tool_dataprivacy');
}
$data->fields[] = $this->_form->createElement('autocomplete', $fieldname, get_string('lawfulbases', 'tool_dataprivacy'),
$bases,
[
'multiple' => true,
]
);
$data->helps = [
$fieldname => ['lawfulbases', 'tool_dataprivacy'],
];
$data->advanceds = [
$fieldname => true,
];
return $data;
}
/**
* Get the mform field for sensitive bases.
*
* @param string $prefix The prefix to apply to the field
* @return \stdClass
*/
protected function get_sensitive_base_field(string $prefix = '') : \stdClass {
$fieldname = "{$prefix}sensitivedatareasons";
$data = (object) [
'fields' => [],
];
$bases = [];
foreach (\tool_dataprivacy\purpose::GDPR_ART_9_2_ITEMS as $article) {
$key = 'gdpr_art_9_2_' . $article;
$bases[$key] = get_string("{$key}_name", 'tool_dataprivacy');
}
$data->fields[] = $this->_form->createElement(
'autocomplete',
$fieldname,
get_string('sensitivedatareasons', 'tool_dataprivacy'),
$bases,
[
'multiple' => true,
]
);
$data->helps = [
$fieldname => ['sensitivedatareasons', 'tool_dataprivacy'],
];
$data->advanceds = [
$fieldname => true,
];
return $data;
}
/**
* Get the retention period fields.
*
* @param string $prefix The name of the main field, and prefix for the subfields.
* @return \stdClass
*/
protected function get_retention_period_fields(string $prefix = '') : \stdClass {
$prefix = "{$prefix}retentionperiod";
$data = (object) [
'fields' => [],
'types' => [],
];
$number = $this->_form->createElement('text', "{$prefix}number", null, ['size' => 8]);
$data->types["{$prefix}number"] = PARAM_INT;
$unitoptions = [
'Y' => get_string('years'),
'M' => strtolower(get_string('months')),
'D' => strtolower(get_string('days'))
];
$unit = $this->_form->createElement('select', "{$prefix}unit", '', $unitoptions);
$data->fields[] = $this->_form->createElement(
'group',
$prefix,
get_string('retentionperiod', 'tool_dataprivacy'),
[
'number' => $number,
'unit' => $unit,
],
null,
false
);
return $data;
}
/**
* Get the mform field for the protected flag.
*
* @param string $prefix The prefix to apply to the field
* @return \stdClass
*/
protected function get_protected_field(string $prefix = '') : \stdClass {
$fieldname = "{$prefix}protected";
return (object) [
'fields' => [
$this->_form->createElement(
'advcheckbox',
$fieldname,
get_string('protected', 'tool_dataprivacy'),
get_string('protectedlabel', 'tool_dataprivacy')
),
],
];
}
/**
* Converts data to data suitable for storage.
*
* @param \stdClass $data
* @return \stdClass
*/
protected static function convert_fields(\stdClass $data) {
$data = parent::convert_fields($data);
if (is_array($data->lawfulbases)) {
if (!empty($data->lawfulbases) && is_array($data->lawfulbases)) {
$data->lawfulbases = implode(',', $data->lawfulbases);
}
if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
@@ -122,6 +446,7 @@ class purpose extends persistent {
$data->retentionperiod = 'P' . $data->retentionperiodnumber . $data->retentionperiodunit;
unset($data->retentionperiodnumber);
unset($data->retentionperiodunit);
return $data;
}
@@ -133,6 +458,16 @@ class purpose extends persistent {
protected function get_default_data() {
$data = parent::get_default_data();
return $this->convert_existing_data_to_values($data);
}
/**
* Normalise any values stored in existing data.
*
* @param \stdClass $data
* @return \stdClass
*/
protected function convert_existing_data_to_values(\stdClass $data) : \stdClass {
$data->lawfulbases = explode(',', $data->lawfulbases);
if (!empty($data->sensitivedatareasons)) {
$data->sensitivedatareasons = explode(',', $data->sensitivedatareasons);
@@ -146,4 +481,94 @@ class purpose extends persistent {
return $data;
}
/**
* Fetch the role override data from the list of submitted data.
*
* @param \stdClass $data The complete set of processed data
* @return \stdClass[] The list of overrides
*/
public function get_role_overrides_from_data(\stdClass $data) {
$overrides = [];
if (!empty($data->overrides)) {
$searchkey = 'roleoverride_';
for ($i = 0; $i < $data->overrides; $i++) {
$overridedata = (object) [];
foreach ((array) $data as $fieldname => $value) {
if (strpos($fieldname, $searchkey) !== 0) {
continue;
}
$overridefieldname = substr($fieldname, strlen($searchkey));
$overridedata->$overridefieldname = $value[$i];
}
if (empty($overridedata->roleid) || empty($overridedata->retentionperiodnumber)) {
// Skip this one.
// There is no value and it will be delete.
continue;
}
$override = static::convert_fields($overridedata);
$overrides[$i] = $override;
}
}
return $overrides;
}
/**
* Define extra validation mechanims.
*
* @param stdClass $data Data to validate.
* @param array $files Array of files.
* @param array $errors Currently reported errors.
* @return array of additional errors, or overridden errors.
*/
protected function extra_validation($data, $files, array &$errors) {
$overrides = $this->get_role_overrides_from_data($data);
// Check role overrides to ensure that:
// - roles are unique; and
// - specifeid retention periods are numeric.
$seenroleids = [];
foreach ($overrides as $id => $override) {
$override->purposeid = 0;
$persistent = new \tool_dataprivacy\purpose_override($override->id, $override);
if (isset($seenroleids[$persistent->get('roleid')])) {
$errors["roleoverride_roleid[{$id}]"] = get_string('duplicaterole');
}
$seenroleids[$persistent->get('roleid')] = true;
$errors = array_merge($errors, $persistent->get_errors());
}
return $errors;
}
/**
* Load in existing data as form defaults. Usually new entry defaults are stored directly in
* form definition (new entry form); this function is used to load in data where values
* already exist and data is being edited (edit entry form).
*
* @param stdClass $data
*/
public function set_data($data) {
$purpose = $this->get_persistent();
$count = 0;
foreach ($this->existingoverrides as $override) {
$overridedata = $this->convert_existing_data_to_values($override->to_record());
foreach ($overridedata as $key => $value) {
$keyname = "roleoverride_{$key}[{$count}]";
$data->$keyname = $value;
}
$count++;
}
parent::set_data($data);
}
}
+141 -39
View File
@@ -27,6 +27,7 @@ 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.
@@ -35,6 +36,23 @@ use tool_dataprivacy\api;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/** The default number of results to be shown per page. */
const DEFAULT_PAGE_SIZE = 20;
/** Filter constant associated with the request type filter. */
const FILTER_TYPE = 1;
/** Filter constant associated with the request status filter. */
const FILTER_STATUS = 2;
/** 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.
@@ -45,16 +63,11 @@ class helper {
* @throws moodle_exception
*/
public static function get_request_type_string($requesttype) {
switch ($requesttype) {
case api::DATAREQUEST_TYPE_EXPORT:
return get_string('requesttypeexport', 'tool_dataprivacy');
case api::DATAREQUEST_TYPE_DELETE:
return get_string('requesttypedelete', 'tool_dataprivacy');
case api::DATAREQUEST_TYPE_OTHERS:
return get_string('requesttypeothers', 'tool_dataprivacy');
default:
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
$types = self::get_request_types();
if (!isset($types[$requesttype])) {
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
return $types[$requesttype];
}
/**
@@ -66,16 +79,37 @@ class helper {
* @throws moodle_exception
*/
public static function get_shortened_request_type_string($requesttype) {
switch ($requesttype) {
case api::DATAREQUEST_TYPE_EXPORT:
return get_string('requesttypeexportshort', 'tool_dataprivacy');
case api::DATAREQUEST_TYPE_DELETE:
return get_string('requesttypedeleteshort', 'tool_dataprivacy');
case api::DATAREQUEST_TYPE_OTHERS:
return get_string('requesttypeothersshort', 'tool_dataprivacy');
default:
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
$types = self::get_request_types_short();
if (!isset($types[$requesttype])) {
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
return $types[$requesttype];
}
/**
* Returns the key value-pairs of request type code and their string value.
*
* @return array
*/
public static function get_request_types() {
return [
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothers', 'tool_dataprivacy'),
];
}
/**
* Returns the key value-pairs of request type code and their shortened string value.
*
* @return array
*/
public static function get_request_types_short() {
return [
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexportshort', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedeleteshort', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothersshort', 'tool_dataprivacy'),
];
}
/**
@@ -83,30 +117,64 @@ class helper {
*
* @param int $status The request status.
* @return string
* @throws coding_exception
* @throws moodle_exception
*/
public static function get_request_status_string($status) {
switch ($status) {
case api::DATAREQUEST_STATUS_PENDING:
return get_string('statuspending', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_PREPROCESSING:
return get_string('statuspreprocessing', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
return get_string('statusawaitingapproval', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_APPROVED:
return get_string('statusapproved', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_PROCESSING:
return get_string('statusprocessing', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_COMPLETE:
return get_string('statuscomplete', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_CANCELLED:
return get_string('statuscancelled', 'tool_dataprivacy');
case api::DATAREQUEST_STATUS_REJECTED:
return get_string('statusrejected', 'tool_dataprivacy');
default:
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
$statuses = self::get_request_statuses();
if (!isset($statuses[$status])) {
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
}
return $statuses[$status];
}
/**
* Returns the key value-pairs of request status code and string value.
*
* @return array
*/
public static function get_request_statuses() {
return [
api::DATAREQUEST_STATUS_PENDING => get_string('statuspending', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_PREPROCESSING => get_string('statuspreprocessing', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => get_string('statusawaitingapproval', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_COMPLETE => get_string('statuscomplete', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_DOWNLOAD_READY => get_string('statusready', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_EXPIRED => get_string('statusexpired', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_CANCELLED => get_string('statuscancelled', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_REJECTED => get_string('statusrejected', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_DELETED => get_string('statusdeleted', 'tool_dataprivacy'),
];
}
/**
* 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'),
];
}
/**
@@ -132,7 +200,7 @@ class helper {
'contextlevel' => CONTEXT_USER
];
// The final list of users that we will return;
// The final list of users that we will return.
$finalresults = [];
// Our prospective list of users.
@@ -146,4 +214,38 @@ class helper {
}
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;
}
}
@@ -70,11 +70,37 @@ class metadata_registry {
$internaldata['compliant'] = false;
}
// Check to see if we are an external plugin.
$componentshortname = explode('_', $component);
// Plugin names can contain _ characters, limit to 2 to just remove initial plugintype.
$componentshortname = explode('_', $component, 2);
$shortname = array_pop($componentshortname);
if (isset($contributedplugins[$plugintype][$shortname])) {
$internaldata['external'] = true;
}
// Additional interface checks.
if (!$manager->is_empty_subsystem($component)) {
$classname = $manager->get_provider_classname_for_component($component);
if (class_exists($classname)) {
$componentclass = new $classname();
// Check if the interface is deprecated.
if ($componentclass instanceof \core_privacy\local\deprecated) {
$internaldata['deprecated'] = true;
}
// Check that the core_userlist_provider is implemented for all user data providers.
if ($componentclass instanceof \core_privacy\local\request\core_user_data_provider
&& !$componentclass instanceof \core_privacy\local\request\core_userlist_provider) {
$internaldata['userlistnoncompliance'] = true;
}
// Check that any type of userlist_provider is implemented for all shared data providers.
if ($componentclass instanceof \core_privacy\local\request\shared_data_provider
&& !$componentclass instanceof \core_privacy\local\request\userlist_provider) {
$internaldata['userlistnoncompliance'] = true;
}
}
}
return $internaldata;
}, $leaves['plugins']);
$fullyrichtree[$branch]['plugin_type_raw'] = $plugintype;
@@ -25,7 +25,6 @@ namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use dml_exception;
use moodle_exception;
use moodle_url;
use renderable;
@@ -34,7 +33,7 @@ use single_select;
use stdClass;
use templatable;
use tool_dataprivacy\data_request;
use tool_dataprivacy\output\expired_contexts_table;
use tool_dataprivacy\local\helper;
/**
* Class containing data for a user's data requests.
@@ -44,9 +43,6 @@ use tool_dataprivacy\output\expired_contexts_table;
*/
class data_deletion_page implements renderable, templatable {
/** The default number of results to be shown per page. */
const DEFAULT_PAGE_SIZE = 20;
/** @var data_request[] $requests List of data requests. */
protected $filter = null;
@@ -57,7 +53,7 @@ class data_deletion_page implements renderable, templatable {
* Construct this renderable.
*
* @param \tool_dataprivacy\data_request[] $filter
* @param \tool_dataprivacy\expired_contexts_table $expiredcontextstable
* @param expired_contexts_table $expiredcontextstable
*/
public function __construct($filter, expired_contexts_table $expiredcontextstable) {
$this->filter = $filter;
@@ -70,7 +66,6 @@ class data_deletion_page implements renderable, templatable {
* @param renderer_base $output
* @return stdClass
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
public function export_for_template(renderer_base $output) {
@@ -87,7 +82,7 @@ class data_deletion_page implements renderable, templatable {
$data->filter = $filterselector->export_for_template($output);
ob_start();
$this->expiredcontextstable->out(self::DEFAULT_PAGE_SIZE, true);
$this->expiredcontextstable->out(helper::DEFAULT_PAGE_SIZE, true);
$expiredcontexts = ob_get_contents();
ob_end_clean();
$data->expiredcontexts = $expiredcontexts;
@@ -426,7 +426,7 @@ class data_registry_page implements renderable, templatable {
/**
* From a list of purpose persistents to a list of id => name purposes.
*
* @param \tool_dataprivacy\purpose $purposes
* @param \tool_dataprivacy\purpose[] $purposes
* @param bool $includenotset
* @param bool $includeinherit
* @return string[]
@@ -443,7 +443,7 @@ class data_registry_page implements renderable, templatable {
/**
* From a list of category persistents to a list of id => name categories.
*
* @param \tool_dataprivacy\category $categories
* @param \tool_dataprivacy\category[] $categories
* @param bool $includenotset
* @param bool $includeinherit
* @return string[]
@@ -24,20 +24,17 @@
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use action_menu;
use action_menu_link_secondary;
use coding_exception;
use context_system;
use dml_exception;
use moodle_exception;
use moodle_url;
use renderable;
use renderer_base;
use single_select;
use stdClass;
use templatable;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
use tool_dataprivacy\external\data_request_exporter;
use tool_dataprivacy\local\helper;
/**
* Class containing data for a user's data requests.
@@ -47,16 +44,21 @@ use tool_dataprivacy\external\data_request_exporter;
*/
class data_requests_page implements renderable, templatable {
/** @var data_request[] $requests List of data requests. */
protected $requests = [];
/** @var data_requests_table $table The data requests table. */
protected $table;
/** @var int[] $filters The applied filters. */
protected $filters = [];
/**
* Construct this renderable.
*
* @param data_request[] $requests
* @param data_requests_table $table The data requests table.
* @param int[] $filters The applied filters.
*/
public function __construct($requests) {
$this->requests = $requests;
public function __construct($table, $filters) {
$this->table = $table;
$this->filters = $filters;
}
/**
@@ -78,43 +80,17 @@ class data_requests_page implements renderable, templatable {
$data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
}
$requests = [];
foreach ($this->requests as $request) {
$requestid = $request->get('id');
$status = $request->get('status');
$requestexporter = new data_request_exporter($request, ['context' => context_system::instance()]);
$item = $requestexporter->export($output);
$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);
// Prepare actions.
$actions = [];
ob_start();
$this->table->out($this->table->get_requests_per_page(), true);
$requests = ob_get_contents();
ob_end_clean();
// View action.
$actionurl = new moodle_url('#');
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
// Approve.
$actiondata['data-action'] = 'approve';
$actiontext = get_string('approverequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
// Deny.
$actiondata['data-action'] = 'deny';
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
}
$actionsmenu = new action_menu($actions);
$actionsmenu->set_menu_trigger(get_string('actions'));
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
$item->actions = $actionsmenu->export_for_template($output);
$requests[] = $item;
}
$data->requests = $requests;
$data->datarequests = $requests;
return $data;
}
}
@@ -0,0 +1,419 @@
<?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 \stdClass[] List of userids and whether they have any ongoing active requests. */
protected $ongoingrequests = [];
/** @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;
}
if ($this->manage) {
$persistent = $this->datarequests[$requestid];
$canreset = $persistent->is_active() || empty($this->ongoingrequests[$data->foruser->id]->{$data->type});
$canreset = $canreset && $persistent->is_resettable();
if ($canreset) {
$reseturl = new moodle_url('/admin/tool/dataprivacy/resubmitrequest.php', [
'requestid' => $requestid,
]);
$actiondata = ['data-action' => 'reset', 'data-requestid' => $requestid];
$actiontext = get_string('resubmitrequestasnew', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($reseturl, null, $actiontext, $actiondata);
}
}
$actionsmenu = new action_menu($actions);
$actionsmenu->set_menu_trigger(get_string('actions'));
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
$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');
$forusers = [];
foreach ($datarequests as $persistent) {
$this->datarequests[$persistent->get('id')] = $persistent;
$exporter = new data_request_exporter($persistent, ['context' => $context]);
$this->rawdata[] = $exporter->export($renderer);
$forusers[] = $persistent->get('userid');
}
// Fetch the list of all ongoing requests for the users currently shown.
// This is used to determine whether any non-active request can be resubmitted.
// There can only be one ongoing request of a type for each user.
$this->ongoingrequests = api::find_ongoing_request_types_for_users($forusers);
// 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(int $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() : int {
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(array $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() : array {
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;
}
}
@@ -57,9 +57,15 @@ class expired_contexts_table extends table_sql {
*/
protected $selectall = true;
/** @var purpose[] Array of purposes mapped to the contexts. */
/** @var purpose[] Array of purposes by their id. */
protected $purposes = [];
/** @var purpose[] Map of context => purpose. */
protected $purposemap = [];
/** @var array List of roles. */
protected $roles = [];
/**
* expired_contexts_table constructor.
*
@@ -77,6 +83,7 @@ class expired_contexts_table extends table_sql {
'purpose' => get_string('purpose', 'tool_dataprivacy'),
'category' => get_string('category', 'tool_dataprivacy'),
'retentionperiod' => get_string('retentionperiod', 'tool_dataprivacy'),
'tobedeleted' => get_string('tobedeleted', 'tool_dataprivacy'),
'timecreated' => get_string('expiry', 'tool_dataprivacy'),
];
$checkboxattrs = [
@@ -93,21 +100,25 @@ class expired_contexts_table extends table_sql {
$this->no_sorting('purpose');
$this->no_sorting('category');
$this->no_sorting('retentionperiod');
$this->no_sorting('tobedeleted');
// Make this table sorted by first name by default.
$this->sortable(true, 'timecreated');
// We use roles in several places.
$this->roles = role_get_names();
}
/**
* The context name column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return string
* @throws coding_exception
*/
public function col_name($data) {
public function col_name($expiredctx) {
global $OUTPUT;
$context = context_helper::instance_by_id($data->contextid);
$context = context_helper::instance_by_id($expiredctx->get('contextid'));
$parent = $context->get_parent_context();
$contextdata = (object)[
'name' => $context->get_context_name(false, true),
@@ -128,14 +139,14 @@ class expired_contexts_table extends table_sql {
/**
* The context information column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return string
* @throws coding_exception
*/
public function col_info($data) {
public function col_info($expiredctx) {
global $OUTPUT;
$context = context_helper::instance_by_id($data->contextid);
$context = context_helper::instance_by_id($expiredctx->get('contextid'));
$children = $context->get_child_contexts();
if (empty($children)) {
@@ -156,13 +167,13 @@ class expired_contexts_table extends table_sql {
/**
* The category name column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return mixed
* @throws coding_exception
* @throws dml_exception
*/
public function col_category($data) {
$context = context_helper::instance_by_id($data->contextid);
public function col_category($expiredctx) {
$context = context_helper::instance_by_id($expiredctx->get('contextid'));
$category = api::get_effective_context_category($context);
return s($category->get('name'));
@@ -171,12 +182,12 @@ class expired_contexts_table extends table_sql {
/**
* The purpose column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return string
* @throws coding_exception
*/
public function col_purpose($data) {
$purpose = $this->purposes[$data->contextid];
public function col_purpose($expiredctx) {
$purpose = $this->get_purpose_for_expiry($expiredctx);
return s($purpose->get('name'));
}
@@ -184,42 +195,114 @@ class expired_contexts_table extends table_sql {
/**
* The retention period column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return string
* @throws Exception
*/
public function col_retentionperiod($data) {
global $PAGE;
public function col_retentionperiod($expiredctx) {
$purpose = $this->get_purpose_for_expiry($expiredctx);
$purpose = $this->purposes[$data->contextid];
$expiries = [];
$exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
$exportedpurpose = $exporter->export($PAGE->get_renderer('core'));
$expiry = html_writer::tag('dt', get_string('default'), ['class' => 'col-sm-3']);
if ($expiredctx->get('defaultexpired')) {
$expiries[get_string('default')] = get_string('expiredrolewithretention', 'tool_dataprivacy', (object) [
'retention' => api::format_retention_period(new \DateInterval($purpose->get('retentionperiod'))),
]);
} else {
$expiries[get_string('default')] = get_string('unexpiredrolewithretention', 'tool_dataprivacy', (object) [
'retention' => api::format_retention_period(new \DateInterval($purpose->get('retentionperiod'))),
]);
}
return $exportedpurpose->formattedretentionperiod;
if (!$expiredctx->is_fully_expired()) {
$purposeoverrides = $purpose->get_purpose_overrides();
foreach ($expiredctx->get('unexpiredroles') as $roleid) {
$role = $this->roles[$roleid];
$override = $purposeoverrides[$roleid];
$expiries[$role->localname] = get_string('unexpiredrolewithretention', 'tool_dataprivacy', (object) [
'retention' => api::format_retention_period(new \DateInterval($override->get('retentionperiod'))),
]);
}
foreach ($expiredctx->get('expiredroles') as $roleid) {
$role = $this->roles[$roleid];
$override = $purposeoverrides[$roleid];
$expiries[$role->localname] = get_string('expiredrolewithretention', 'tool_dataprivacy', (object) [
'retention' => api::format_retention_period(new \DateInterval($override->get('retentionperiod'))),
]);
}
}
$output = array_map(function($rolename, $expiry) {
$return = html_writer::tag('dt', $rolename, ['class' => 'col-sm-3']);
$return .= html_writer::tag('dd', $expiry, ['class' => 'col-sm-9']);
return $return;
}, array_keys($expiries), $expiries);
return html_writer::tag('dl', implode($output), ['class' => 'row']);
}
/**
* The timecreated a.k.a. the context expiry date column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return string
*/
public function col_timecreated($data) {
return userdate($data->timecreated);
public function col_timecreated($expiredctx) {
return userdate($expiredctx->get('timecreated'));
}
/**
* Generate the select column.
*
* @param stdClass $data The row data.
* @param stdClass $expiredctx The row data.
* @return string
*/
public function col_select($data) {
$id = $data->id;
public function col_select($expiredctx) {
$id = $expiredctx->get('id');
return html_writer::checkbox('expiredcontext_' . $id, $id, $this->selectall, '', ['class' => 'selectcontext']);
}
/**
* Formatting for the 'tobedeleted' column which indicates in a friendlier fashion whose data will be removed.
*
* @param stdClass $expiredctx The row data.
* @return string
*/
public function col_tobedeleted($expiredctx) {
if ($expiredctx->is_fully_expired()) {
return get_string('defaultexpired', 'tool_dataprivacy');
}
$purpose = $this->get_purpose_for_expiry($expiredctx);
$a = (object) [];
$expiredroles = [];
foreach ($expiredctx->get('expiredroles') as $roleid) {
$expiredroles[] = html_writer::tag('li', $this->roles[$roleid]->localname);
}
$a->expired = html_writer::tag('ul', implode($expiredroles));
$unexpiredroles = [];
foreach ($expiredctx->get('unexpiredroles') as $roleid) {
$unexpiredroles[] = html_writer::tag('li', $this->roles[$roleid]->localname);
}
$a->unexpired = html_writer::tag('ul', implode($unexpiredroles));
if ($expiredctx->get('defaultexpired')) {
return get_string('defaultexpiredexcept', 'tool_dataprivacy', $a);
} else if (empty($unexpiredroles)) {
return get_string('defaultunexpired', 'tool_dataprivacy', $a);
} else {
return get_string('defaultunexpiredwithexceptions', 'tool_dataprivacy', $a);
}
}
/**
* Query the database for results to display in the table.
*
@@ -241,17 +324,16 @@ class expired_contexts_table extends table_sql {
// 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 = [];
$contextids = [];
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;
$this->rawdata[] = $persistent;
$contextids[] = $persistent->get('contextid');
}
$this->preload_contexts($contextids);
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
@@ -281,4 +363,48 @@ class expired_contexts_table extends table_sql {
}
return '';
}
/**
* Get the purpose for the specified expired context.
*
* @param expired_context $expiredcontext
* @return purpose
*/
protected function get_purpose_for_expiry(expired_context $expiredcontext) : purpose {
$context = context_helper::instance_by_id($expiredcontext->get('contextid'));
if (empty($this->purposemap[$context->id])) {
$purpose = api::get_effective_context_purpose($context);
$this->purposemap[$context->id] = $purpose->get('id');
if (empty($this->purposes[$purpose->get('id')])) {
$this->purposes[$purpose->get('id')] = $purpose;
}
}
return $this->purposes[$this->purposemap[$context->id]];
}
/**
* Preload context records given a set of contextids.
*
* @param array $contextids
*/
protected function preload_contexts(array $contextids) {
global $DB;
if (empty($contextids)) {
return;
}
$ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$sql = "SELECT {$ctxfields} FROM {context} ctx WHERE ctx.id {$insql}";
$contextlist = $DB->get_recordset_sql($sql, $inparams);
foreach ($contextlist as $contextdata) {
\context_helper::preload_from_record($contextdata);
}
$contextlist->close();
}
}
@@ -82,6 +82,7 @@ class my_data_requests_page implements renderable, templatable {
$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) {
@@ -94,7 +95,8 @@ class my_data_requests_page implements renderable, templatable {
$requestexporter = new data_request_exporter($request, ['context' => $outputcontext]);
$item = $requestexporter->export($output);
if ($request->get('userid') != $USER->id) {
$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);
@@ -107,7 +109,28 @@ class my_data_requests_page implements renderable, templatable {
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
$cancancel = false;
break;
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statusready', 'tool_dataprivacy');
$cancancel = false;
$candownload = true;
if ($usercontext) {
$candownload = api::can_download_data_request_for_user(
$request->get('userid'), $request->get('requestedby'));
}
break;
case api::DATAREQUEST_STATUS_DELETED:
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statusdeleted', 'tool_dataprivacy');
$cancancel = false;
break;
case api::DATAREQUEST_STATUS_EXPIRED:
$item->statuslabelclass = 'label-default';
$item->statuslabel = get_string('statusexpired', 'tool_dataprivacy');
$item->statuslabeltitle = get_string('downloadexpireduser', 'tool_dataprivacy');
$cancancel = false;
break;
case api::DATAREQUEST_STATUS_CANCELLED:
case api::DATAREQUEST_STATUS_REJECTED:
@@ -124,10 +147,7 @@ class my_data_requests_page implements renderable, templatable {
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
}
if ($candownload && $usercontext) {
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
'export.zip', true);
$downloadtext = get_string('download', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($downloadurl, null, $downloadtext);
$actions[] = api::get_download_link($usercontext, $requestid);
}
if (!empty($actions)) {
$actionsmenu = new action_menu($actions);
@@ -139,4 +139,15 @@ class renderer extends plugin_renderer_base {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/data_deletion', $data);
}
/**
* Render the user data retention summary page.
*
* @param summary_page $page
* @return string html for the page.
*/
public function render_summary_page(summary_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/summary', $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,132 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Summary page 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 summary page renderable.
*
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class summary_page implements renderable, templatable {
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output) {
$contextlevels = [
'contextlevelname10' => CONTEXT_SYSTEM,
'contextlevelname30' => CONTEXT_USER,
'contextlevelname40' => CONTEXT_COURSECAT,
'contextlevelname50' => CONTEXT_COURSE,
'contextlevelname70' => CONTEXT_MODULE,
'contextlevelname80' => CONTEXT_BLOCK
];
$data = [];
$context = \context_system::instance();
foreach ($contextlevels as $levelname => $level) {
$classname = \context_helper::get_class_for_level($level);
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$purposeid = get_config('tool_dataprivacy', $purposevar);
$categoryid = get_config('tool_dataprivacy', $categoryvar);
$section = [];
$section['contextname'] = get_string($levelname, 'tool_dataprivacy');
if (empty($purposeid)) {
list($purposeid, $categoryid) =
\tool_dataprivacy\data_registry::get_effective_default_contextlevel_purpose_and_category($level);
}
if ($purposeid == -1) {
$purposeid = 0;
}
$purpose = new \tool_dataprivacy\purpose($purposeid);
$export = new \tool_dataprivacy\external\purpose_exporter($purpose, ['context' => $context]);
$purposedata = $export->export($output);
$section['purpose'] = $purposedata;
if (empty($categoryid)) {
list($purposeid, $categoryid) =
\tool_dataprivacy\data_registry::get_effective_default_contextlevel_purpose_and_category($level);
}
if ($categoryid == -1) {
$categoryid = 0;
}
$category = new \tool_dataprivacy\category($categoryid);
$export = new \tool_dataprivacy\external\category_exporter($category, ['context' => $context]);
$categorydata = $export->export($output);
$section['category'] = $categorydata;
$data['contexts'][] = $section;
}
// Get activity module plugin info.
$pluginmanager = \core_plugin_manager::instance();
$modplugins = $pluginmanager->get_enabled_plugins('mod');
foreach ($modplugins as $name) {
$classname = \context_helper::get_class_for_level($contextlevels['contextlevelname70']);
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname, $name);
$categoryid = get_config('tool_dataprivacy', $categoryvar);
$purposeid = get_config('tool_dataprivacy', $purposevar);
if ($categoryid === false && $purposeid === false) {
// If no purpose and category has been set for this plugin, then there's no need to show this on the list.
continue;
}
$section = [];
$section['contextname'] = $pluginmanager->plugin_name('mod_' . $name);
if ($purposeid == -1) {
$purposeid = 0;
}
$purpose = new \tool_dataprivacy\purpose($purposeid);
$export = new \tool_dataprivacy\external\purpose_exporter($purpose, ['context' => $context]);
$purposedata = $export->export($output);
$section['purpose'] = $purposedata;
if ($categoryid == -1) {
$categoryid = 0;
}
$category = new \tool_dataprivacy\category($categoryid);
$export = new \tool_dataprivacy\external\category_exporter($category, ['context' => $context]);
$categorydata = $export->export($output);
$section['category'] = $categorydata;
$data['contexts'][] = $section;
}
return $data;
}
}
@@ -47,7 +47,7 @@ class page_helper {
*/
public static function setup(moodle_url $url, $title, $attachtoparentnode = '',
$requiredcapability = 'tool/dataprivacy:managedataregistry') {
global $PAGE;
global $PAGE, $SITE;
$context = context_system::instance();
@@ -65,7 +65,7 @@ class page_helper {
$PAGE->set_context($context);
$PAGE->set_pagelayout('admin');
$PAGE->set_title($title);
$PAGE->set_heading($title);
$PAGE->set_heading($SITE->fullname);
// If necessary, override the settings navigation to add this page into the breadcrumb navigation.
if ($attachtoparentnode) {
@@ -30,9 +30,11 @@ use context;
use context_user;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use \core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use dml_exception;
use stdClass;
@@ -50,8 +52,14 @@ class provider implements
// This tool stores user data.
\core_privacy\local\metadata\provider,
// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider,
// This tool may provide access to and deletion of user data.
\core_privacy\local\request\plugin\provider {
\core_privacy\local\request\plugin\provider,
// This plugin has some sitewide user preferences to export.
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
@@ -70,6 +78,12 @@ class provider implements
],
'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;
}
@@ -91,6 +105,32 @@ class provider implements
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!is_a($context, \context_user::class)) {
return;
}
$params = [
'contextlevel' => CONTEXT_USER,
'contextid' => $context->id,
];
$sql = "SELECT instanceid AS userid
FROM {context}
WHERE id = :contextid
AND contextlevel = :contextlevel";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
@@ -127,6 +167,8 @@ class provider implements
$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.
@@ -162,4 +204,55 @@ class provider implements
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*
*/
public static function delete_data_for_users(approved_userlist $userlist) {
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preffilter = get_user_preferences(tool_helper::PREF_REQUEST_FILTERS, null, $userid);
if ($preffilter !== null) {
$filters = json_decode($preffilter);
$descriptions = [];
foreach ($filters as $filter) {
list($category, $value) = explode(':', $filter);
$option = new stdClass();
switch($category) {
case tool_helper::FILTER_TYPE:
$option->category = get_string('requesttype', 'tool_dataprivacy');
$option->name = tool_helper::get_shortened_request_type_string($value);
break;
case tool_helper::FILTER_STATUS:
$option->category = get_string('requeststatus', 'tool_dataprivacy');
$option->name = tool_helper::get_request_status_string($value);
break;
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'));
}
}
}
+10 -2
View File
@@ -64,7 +64,7 @@ class purpose extends \core\persistent {
// Replicate self::read.
$this->from_record($data);
// Using validate() as self::$validated is private.
// Validate the purpose record.
$this->validate();
// Now replicate the parent constructor.
@@ -162,7 +162,6 @@ class purpose extends \core\persistent {
* @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;
@@ -180,4 +179,13 @@ class purpose extends \core\persistent {
return false;
}
/**
* Get a list of the role purpose overrides for this purpose.
*
* @return array
*/
public function get_purpose_overrides() : array {
return purpose_override::get_overrides_for_purpose($this);
}
}
@@ -0,0 +1,143 @@
<?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 purpose overrides from the DB.
*
* @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 stdClass;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Class for loading/storing data purpose overrides from the DB.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class purpose_override extends \core\persistent {
/**
* Database table.
*/
const TABLE = 'tool_dataprivacy_purposerole';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'purposeid' => array(
'type' => PARAM_INT,
'description' => 'The purpose that that this override relates to',
),
'roleid' => array(
'type' => PARAM_INT,
'description' => 'The role that that this override relates to',
),
'lawfulbases' => array(
'type' => PARAM_TEXT,
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis.',
'null' => NULL_ALLOWED,
'default' => null,
),
'sensitivedatareasons' => array(
'type' => PARAM_TEXT,
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_sensitive',
'null' => NULL_ALLOWED,
'default' => null,
),
'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',
),
);
}
/**
* Get all role overrides for the purpose.
*
* @param purpose $purpose
* @return array
*/
public static function get_overrides_for_purpose(purpose $purpose) : array {
$cache = \cache::make('tool_dataprivacy', 'purpose_overrides');
$overrides = [];
$alldata = $cache->get($purpose->get('id'));
if (false === $alldata) {
$tocache = [];
foreach (self::get_records(['purposeid' => $purpose->get('id')]) as $override) {
$tocache[] = $override->to_record();
$overrides[$override->get('roleid')] = $override;
}
$cache->set($purpose->get('id'), $tocache);
} else {
foreach ($alldata as $data) {
$override = new self(0, $data);
$overrides[$override->get('roleid')] = $override;
}
}
return $overrides;
}
/**
* Adds the new record to the cache.
*
* @return null
*/
protected function after_create() {
$cache = \cache::make('tool_dataprivacy', 'purpose_overrides');
$cache->delete($this->get('purposeid'));
}
/**
* Updates the cache record.
*
* @param bool $result
* @return null
*/
protected function after_update($result) {
$cache = \cache::make('tool_dataprivacy', 'purpose_overrides');
$cache->delete($this->get('purposeid'));
}
/**
* Removes unnecessary stuff from db.
*
* @return null
*/
protected function before_delete() {
$cache = \cache::make('tool_dataprivacy', 'purpose_overrides');
$cache->delete($this->get('purposeid'));
}
}
@@ -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');
}
}
}
}
@@ -52,18 +52,10 @@ class delete_expired_contexts extends scheduled_task {
/**
* Run the task to delete context instances based on their retention periods.
*
*/
public function execute() {
$manager = new \tool_dataprivacy\expired_course_related_contexts();
$deleted = $manager->delete();
if ($deleted > 0) {
mtrace($deleted . ' course-related contexts have been deleted');
}
$manager = new \tool_dataprivacy\expired_user_contexts();
$deleted = $manager->delete();
if ($deleted > 0) {
mtrace($deleted . ' user contexts have been deleted');
}
$manager = new \tool_dataprivacy\expired_contexts_manager(new \text_progress_trace());
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');
}
}
}
@@ -54,15 +54,8 @@ class expired_retention_period extends scheduled_task {
* Run the task to flag context instances as expired.
*/
public function execute() {
$manager = new \tool_dataprivacy\expired_course_related_contexts();
$flagged = $manager->flag_expired();
if ($flagged > 0) {
mtrace($flagged . ' course-related contexts have been flagged as expired');
}
$manager = new \tool_dataprivacy\expired_user_contexts();
$flagged = $manager->flag_expired();
if ($flagged > 0) {
mtrace($flagged . ' user contexts have been flagged as expired');
}
$manager = new \tool_dataprivacy\expired_contexts_manager(new \text_progress_trace());
list($courses, $users) = $manager->flag_expired_contexts();
mtrace("Flagged {$courses} course contexts, and {$users} user contexts as expired");
}
}
@@ -54,7 +54,7 @@ class initiate_data_request_task extends adhoc_task {
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/admin/tool/dataprivacy/lib.php');
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
if (!isset($this->get_custom_data()->requestid)) {
throw new coding_exception('The custom data \'requestid\' is required.');
@@ -70,27 +70,6 @@ class initiate_data_request_task extends adhoc_task {
return;
}
$requestedby = $datarequest->get('requestedby');
$valid = true;
$comment = '';
$foruser = $datarequest->get('userid');
if ($foruser != $requestedby) {
if (!$valid = api::can_create_data_request_for_user($foruser, $requestedby)) {
$params = (object)[
'requestedby' => $requestedby,
'userid' => $foruser
];
$comment = get_string('errornocapabilitytorequestforothers', 'tool_dataprivacy', $params);
mtrace($comment);
}
}
// Reject the request outright if it's invalid.
if (!$valid) {
$dpo = $datarequest->get('dpo');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_REJECTED, $dpo, $comment);
return;
}
// Update the status of this request as pre-processing.
mtrace('Generating the contexts containing personal data for the user...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
@@ -109,13 +88,19 @@ class initiate_data_request_task extends adhoc_task {
// Get the list of the site Data Protection Officers.
$dpos = api::get_site_dpos();
// 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);
// 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);
}
}
}
}
@@ -57,7 +57,7 @@ class process_data_request_task extends adhoc_task {
public function execute() {
global $CFG, $PAGE, $SITE;
require_once($CFG->dirroot . '/admin/tool/dataprivacy/lib.php');
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
if (!isset($this->get_custom_data()->requestid)) {
throw new coding_exception('The custom data \'requestid\' is required.');
@@ -74,15 +74,29 @@ class process_data_request_task extends adhoc_task {
return;
}
if (!\tool_dataprivacy\data_registry::defaults_set()) {
// Warn if no site purpose is defined.
mtrace('Warning: No purpose is defined at the system level. Deletion will delete all.');
}
// 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);
$usercontext = \context_user::instance($foruser->id);
// 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) {
// 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);
@@ -105,7 +119,7 @@ class process_data_request_task extends adhoc_task {
$filerecord->author = fullname($foruser);
// Save somewhere.
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
// Get the collection of approved_contextlist objects needed for core_privacy data deletion.
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
@@ -115,10 +129,12 @@ class process_data_request_task extends adhoc_task {
$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, api::DATAREQUEST_STATUS_COMPLETE);
api::update_request_status($requestid, $completestatus);
mtrace('The processing of the user data request has been completed...');
// Create message to notify the user regarding the processing results.
@@ -139,8 +155,17 @@ class process_data_request_task extends adhoc_task {
$output = $PAGE->get_renderer('tool_dataprivacy');
$emailonly = false;
$notifyuser = true;
switch ($request->type) {
case api::DATAREQUEST_TYPE_EXPORT:
// Check if the user is allowed to download their own export. (This is for
// institutions which centrally co-ordinate subject access request across many
// systems, not just one Moodle instance, so we don't want every instance emailing
// the user.)
if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) {
$notifyuser = false;
}
$typetext = get_string('requesttypeexport', 'tool_dataprivacy');
// We want to notify the user in Moodle about the processing results.
$message->notification = 1;
@@ -179,18 +204,47 @@ class process_data_request_task extends adhoc_task {
$message->fullmessagehtml = $messagehtml;
// Send message to the user involved.
if ($emailonly) {
email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
} else {
message_send($message);
}
mtrace('Message sent to user: ' . $messagetextdata['username']);
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');
}
// Send to requester as well if this request was made on behalf of another user who's not a DPO,
// and has the capability to make data requests for the user (e.g. Parent).
if (!api::is_site_dpo($request->requestedby) && $foruser->id != $request->requestedby) {
// Ensure the requester has the capability to make data requests for this user.
if (api::can_create_data_request_for_user($request->userid, $request->requestedby)) {
if ($sendtorequester) {
$requestedby = core_user::get_user($request->requestedby);
$message->userto = $requestedby;
$messagetextdata['username'] = fullname($requestedby);
@@ -209,7 +263,7 @@ class process_data_request_task extends adhoc_task {
}
}
if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
if ($deleteuser) {
// Delete the user.
delete_user($foruser);
}
+16 -6
View File
@@ -24,12 +24,12 @@
require_once('../../../config.php');
require_once('lib.php');
require_once('classes/api.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]);
$url = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', ['manage' => $manage, 'type' => $requesttype]);
$PAGE->set_url($url);
@@ -53,11 +53,13 @@ if ($manage) {
$PAGE->set_context($context);
// If contactdataprotectionofficer is disabled, send the user back to the profile page, or the privacy policy page.
if (!\tool_dataprivacy\api::can_contact_dpo()) {
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), \core\output\notification::NOTIFY_ERROR);
// That is, unless you have sufficient capabilities to perform this on behalf of a user.
if (!$manage && !\tool_dataprivacy\api::can_contact_dpo()) {
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), 0, \core\output\notification::NOTIFY_ERROR);
}
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
$mform->set_data(['type' => $requesttype]);
// Data request cancelled.
if ($mform->is_cancelled()) {
@@ -66,6 +68,14 @@ if ($mform->is_cancelled()) {
// 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) {
@@ -77,8 +87,8 @@ if ($data = $mform->get_data()) {
redirect($returnurl, $redirectmessage);
}
$title = get_string('contactdataprotectionofficer', 'tool_dataprivacy');
$PAGE->set_heading($title);
$title = get_string('createnewdatarequest', 'tool_dataprivacy');
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title($title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
@@ -44,19 +44,28 @@ class tool_dataprivacy_data_request_form extends moodleform {
/**
* Form definition.
*
* @throws HTML_QuickForm_Error
* @throws coding_exception
* @throws dml_exception
*/
public function definition() {
global $DB, $USER;
global $USER;
$mform =& $this->_form;
$this->manage = $this->_customdata['manage'];
if ($this->manage) {
$options = [
'ajax' => 'tool_dataprivacy/form-user-selector',
'multiple' => false
'valuehtmlcallback' => function($value) {
global $OUTPUT;
$allusernames = get_all_user_name_fields(true);
$fields = 'id, email, ' . $allusernames;
$user = \core_user::get_user($value, $fields);
$useroptiondata = [
'fullname' => fullname($user),
'email' => $user->email
];
return $OUTPUT->render_from_template('tool_dataprivacy/form-user-selector-suggestion', $useroptiondata);
}
];
$mform->addElement('autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), [], $options);
$mform->addRule('userid', null, 'required', null, 'client');
+15 -6
View File
@@ -25,6 +25,8 @@
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_login(null, false);
$filter = optional_param('filter', CONTEXT_COURSE, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/datadeletion.php');
@@ -34,14 +36,21 @@ $title = get_string('datadeletion', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$table = new \tool_dataprivacy\output\expired_contexts_table($filter);
$table->baseurl = $url;
$table->baseurl->param('filter', $filter);
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
$table = new \tool_dataprivacy\output\expired_contexts_table($filter);
$table->baseurl = $url;
$table->baseurl->param('filter', $filter);
$datadeletionpage = new \tool_dataprivacy\output\data_deletion_page($filter, $table);
$datadeletionpage = new \tool_dataprivacy\output\data_deletion_page($filter, $table);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->render($datadeletionpage);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->render($datadeletionpage);
} else {
$dponamestring = implode (',', tool_dataprivacy\api::get_dpo_role_names());
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
echo $OUTPUT->notification($message, 'error');
}
echo $OUTPUT->footer();
+11 -3
View File
@@ -25,6 +25,8 @@
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_login(null, false);
$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
$contextid = optional_param('contextid', 0, PARAM_INT);
@@ -35,8 +37,14 @@ $title = get_string('dataregistry', 'tool_dataprivacy');
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
echo $OUTPUT->heading($title);
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
echo $output->render($dataregistry);
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
echo $output->render($dataregistry);
} else {
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
echo $OUTPUT->notification($message, 'error');
}
echo $OUTPUT->footer();
+59 -4
View File
@@ -25,6 +25,10 @@
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');
@@ -34,9 +38,60 @@ $title = get_string('datarequests', 'tool_dataprivacy');
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$requests = tool_dataprivacy\api::get_data_requests();
$requestlist = new tool_dataprivacy\output\data_requests_page($requests);
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
echo $requestlistoutput->render($requestlist);
if (!\tool_dataprivacy\data_registry::defaults_set()) {
\core\notification::error(get_string('systemconfignotsetwarning', 'tool_dataprivacy'));
}
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();
+18
View File
@@ -49,4 +49,22 @@ $capabilities = [
'contextlevel' => CONTEXT_USER,
'archetypes' => []
],
// Capability for users to download the results of their own data request.
'tool/dataprivacy:downloadownrequest' => [
'riskbitmask' => 0,
'captype' => 'read',
'contextlevel' => CONTEXT_USER,
'archetypes' => [
'user' => CAP_ALLOW
]
],
// Capability for administrators to download other people's data requests.
'tool/dataprivacy:downloadallrequests' => [
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_USER,
'archetypes' => []
],
];
+7
View File
@@ -33,6 +33,13 @@ $definitions = array(
'staticacceleration' => true,
'staticaccelerationsize' => 30,
),
'purpose_overrides' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => false,
'staticacceleration' => true,
'staticaccelerationsize' => 50,
),
'contextlevel' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
+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',
],
];
+30 -4
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180313" COMMENT="XMLDB file for Moodle tool/dataprivacy"
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180904" COMMENT="XMLDB file for Moodle tool/dataprivacy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
@@ -12,13 +12,14 @@
<FIELD NAME="commentsformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID the request is being made for"/>
<FIELD NAME="requestedby" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the one making the request"/>
<FIELD NAME="status" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The current status of the data request"/>
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The current status of the data request"/>
<FIELD NAME="dpo" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the Data Protection Officer who is reviewing th request"/>
<FIELD NAME="dpocomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="DPO's comments (e.g. reason for rejecting the request, etc.)"/>
<FIELD NAME="dpocommentformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<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"/>
@@ -98,7 +99,10 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="status" TYPE="int" LENGTH="2" DEFAULT="0" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="unexpiredroles" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Roles which have explicitly not expired yet."/>
<FIELD NAME="expiredroles" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Explicitly expires roles"/>
<FIELD NAME="defaultexpired" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false" COMMENT="The default retention period has passed."/>
<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"/>
@@ -146,5 +150,27 @@
<KEY NAME="request_contextlist" TYPE="unique" FIELDS="requestid, contextlistid" COMMENT="Uniqueness constraint on request and contextlist"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_purposerole" COMMENT="Data purpose overrides for a specific role">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="purposeid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="roleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="lawfulbases" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="sensitivedatareasons" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<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"/>
<KEY NAME="purposepurposeid" TYPE="foreign" FIELDS="purposeid" REFTABLE="tool_dataprivacy_purpose" REFFIELDS="id"/>
<KEY NAME="puproseroleid" TYPE="foreign" FIELDS="roleid" REFTABLE="role" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="purposerole" UNIQUE="true" FIELDS="purposeid, roleid"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
</XMLDB>
+70
View File
@@ -43,6 +43,16 @@ $functions = [
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_mark_complete' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'mark_complete',
'classpath' => '',
'description' => 'Mark a user\'s general enquiry as complete',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_data_request',
@@ -63,6 +73,16 @@ $functions = [
'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',
@@ -73,6 +93,16 @@ $functions = [
'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',
@@ -163,4 +193,44 @@ $functions = [
'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,
],
];
+17
View File
@@ -42,5 +42,22 @@ $tasks = array(
'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,
),
);
+280
View File
@@ -0,0 +1,280 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* tool_dataprivacy plugin upgrade code
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Function to upgrade tool_dataprivacy.
*
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_tool_dataprivacy_upgrade($oldversion) {
global $CFG, $DB;
$dbman = $DB->get_manager();
if ($oldversion < 2018051405) {
// Define table tool_dataprivacy_ctxexpired to be created.
$table = new xmldb_table('tool_dataprivacy_ctxexpired');
// Adding fields to table tool_dataprivacy_ctxexpired.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table tool_dataprivacy_ctxexpired.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
// Conditionally launch create table for tool_dataprivacy_ctxexpired.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table tool_dataprivacy_contextlist to be created.
$table = new xmldb_table('tool_dataprivacy_contextlist');
// Adding fields to table tool_dataprivacy_contextlist.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table tool_dataprivacy_contextlist.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
// Conditionally launch create table for tool_dataprivacy_contextlist.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table tool_dataprivacy_ctxlst_ctx to be created.
$table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
// Adding fields to table tool_dataprivacy_ctxlst_ctx.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table tool_dataprivacy_ctxlst_ctx.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
// Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table tool_dataprivacy_rqst_ctxlst to be created.
$table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
// Adding fields to table tool_dataprivacy_rqst_ctxlst.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table tool_dataprivacy_rqst_ctxlst.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
$table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
// Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define field lawfulbases to be added to tool_dataprivacy_purpose.
$table = new xmldb_table('tool_dataprivacy_purpose');
// It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
// Conditionally launch add field lawfulbases.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
// Set a kind-of-random value to lawfulbasis field.
$DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
// We redefine it now as not null.
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
// Launch change of nullability for field lawfulbases.
$dbman->change_field_notnull($table, $field);
}
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
$table = new xmldb_table('tool_dataprivacy_purpose');
$field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
// Conditionally launch add field sensitivedatareasons.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2018051405, 'tool', 'dataprivacy');
}
if ($oldversion < 2018051406) {
// Update completed delete requests to new delete status.
$query = "UPDATE {tool_dataprivacy_request}
SET status = :setstatus
WHERE type = :type
AND status = :wherestatus";
$params = array(
'setstatus' => 10, // Request deleted.
'type' => 2, // Delete type.
'wherestatus' => 5, // Request completed.
);
$DB->execute($query, $params);
// Update completed data export requests to new download ready status.
$params = array(
'setstatus' => 8, // Request download ready.
'type' => 1, // export type.
'wherestatus' => 5, // Request completed.
);
$DB->execute($query, $params);
upgrade_plugin_savepoint(true, 2018051406, 'tool', 'dataprivacy');
}
if ($oldversion < 2018051407) {
// Changing precision of field status on table tool_dataprivacy_request to (2).
$table = new xmldb_table('tool_dataprivacy_request');
$field = new xmldb_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'requestedby');
// Launch change of precision for field status.
$dbman->change_field_precision($table, $field);
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2018051407, 'tool', 'dataprivacy');
}
if ($oldversion < 2018051411) {
// Define table tool_dataprivacy_purposerole to be created.
$table = new xmldb_table('tool_dataprivacy_purposerole');
// Adding fields to table tool_dataprivacy_purposerole.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('purposeid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('roleid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('retentionperiod', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('protected', XMLDB_TYPE_INTEGER, '1', null, null, null, null);
$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_purposerole.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('purposepurposeid', XMLDB_KEY_FOREIGN, ['purposeid'], 'tool_dataprivacy_purpose', ['id']);
$table->add_key('puproseroleid', XMLDB_KEY_FOREIGN, ['roleid'], 'role', ['id']);
// Adding indexes to table tool_dataprivacy_purposerole.
$table->add_index('purposerole', XMLDB_INDEX_UNIQUE, ['purposeid', 'roleid']);
// Conditionally launch create table for tool_dataprivacy_purposerole.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Update the ctxexpired table.
$table = new xmldb_table('tool_dataprivacy_ctxexpired');
// Add the unexpiredroles field.
$field = new xmldb_field('unexpiredroles', XMLDB_TYPE_TEXT, null, null, null, null, null, 'contextid');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$DB->set_field('tool_dataprivacy_ctxexpired', 'unexpiredroles', '');
// Add the expiredroles field.
$field = new xmldb_field('expiredroles', XMLDB_TYPE_TEXT, null, null, null, null, null, 'unexpiredroles');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$DB->set_field('tool_dataprivacy_ctxexpired', 'expiredroles', '');
// Add the defaultexpired field.
$field = new xmldb_field('defaultexpired', XMLDB_TYPE_INTEGER, '1', null, null, null, '1', 'expiredroles');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Change the default for the expired field to be empty.
$field = new xmldb_field('defaultexpired', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'expiredroles');
$dbman->change_field_default($table, $field);
// Prevent hte field from being nullable.
$field = new xmldb_field('defaultexpired', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null, 'expiredroles');
$dbman->change_field_notnull($table, $field);
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2018051411, 'tool', 'dataprivacy');
}
if ($oldversion < 2018051413) {
// 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, 2018051413, 'tool', 'dataprivacy');
}
if ($oldversion < 2018051415) {
// 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, 2018051415, 'tool', 'dataprivacy');
}
return true;
}
+30 -35
View File
@@ -25,52 +25,47 @@
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');
$levels = \context_helper::get_all_levels();
// They are set through the context level site and user.
unset($levels[CONTEXT_SYSTEM]);
unset($levels[CONTEXT_USER]);
$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);
$customdata = [
'levels' => $levels,
'purposes' => \tool_dataprivacy\api::get_purposes(),
'categories' => \tool_dataprivacy\api::get_categories(),
];
$form = new \tool_dataprivacy\form\defaults($PAGE->url->out(false), $customdata);
$otherdefaults = [];
if ($mode == CONTEXT_MODULE) {
// Get activity module plugin info.
$pluginmanager = core_plugin_manager::instance();
$modplugins = $pluginmanager->get_enabled_plugins('mod');
$toform = new stdClass();
foreach ($levels as $level => $classname) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$toform->{$purposevar} = get_config('tool_dataprivacy', $purposevar);
$toform->{$categoryvar} = get_config('tool_dataprivacy', $categoryvar);
}
$form->set_data($toform);
$returnurl = new \moodle_url('/admin/tool/dataprivacy/dataregistry.php');
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
foreach ($levels as $level => $classname) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
if (isset($data->{$purposevar})) {
set_config($purposevar, $data->{$purposevar}, 'tool_dataprivacy');
}
if (isset($data->{$categoryvar})) {
set_config($categoryvar, $data->{$categoryvar}, 'tool_dataprivacy');
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,
];
}
redirect($returnurl, get_string('defaultssaved', 'tool_dataprivacy'),
0, \core\output\notification::NOTIFY_SUCCESS);
}
$defaultspage = new \tool_dataprivacy\output\defaults_page($mode, $category, $purpose, $otherdefaults, true);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$form->display();
echo $output->heading($title);
echo $output->render_from_template('tool_dataprivacy/defaults_page', $defaultspage->export_for_template($output));
echo $output->footer();
+2
View File
@@ -24,6 +24,8 @@
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$id = optional_param('id', 0, PARAM_INT);
$url = new \moodle_url('/admin/tool/dataprivacy/editcategory.php', array('id' => $id));
+38 -3
View File
@@ -24,6 +24,8 @@
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$id = optional_param('id', 0, PARAM_INT);
$url = new \moodle_url('/admin/tool/dataprivacy/editpurpose.php', array('id' => $id));
@@ -42,14 +44,47 @@ $form = new \tool_dataprivacy\form\purpose($PAGE->url->out(false),
$returnurl = new \moodle_url('/admin/tool/dataprivacy/purposes.php');
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
} else if ($alldata = $form->get_data()) {
$data = $form->filter_data_for_persistent($alldata);
if (empty($data->id)) {
\tool_dataprivacy\api::create_purpose($data);
$purpose = \tool_dataprivacy\api::create_purpose($data);
$messagesuccess = get_string('purposecreated', 'tool_dataprivacy');
} else {
\tool_dataprivacy\api::update_purpose($data);
$purpose = \tool_dataprivacy\api::update_purpose($data);
$messagesuccess = get_string('purposeupdated', 'tool_dataprivacy');
}
$currentoverrides = [];
foreach ($purpose->get_purpose_overrides() as $override) {
$currentoverrides[$override->get('id')] = $override;
}
$overrides = $form->get_role_overrides_from_data($alldata);
$submittedoverrides = [];
$tosave = [];
foreach ($overrides as $overridedata) {
$overridedata->purposeid = $purpose->get('id');
$override = new \tool_dataprivacy\purpose_override($overridedata->id, $overridedata);
$tosave[] = $override;
if (!empty($overridedata->id)) {
$submittedoverrides[$overridedata->id] = true;
}
}
foreach ($currentoverrides as $id => $override) {
if (!isset($submittedoverrides[$id])) {
$override->delete();
}
}
foreach ($tosave as $override) {
$override->save();
}
redirect($returnurl, $messagesuccess, 0, \core\output\notification::NOTIFY_SUCCESS);
}
@@ -28,13 +28,20 @@ $string['pluginname'] = 'Data privacy';
$string['pluginname_help'] = 'Data privacy plugin';
$string['activitiesandresources'] = 'Activities and resources';
$string['addcategory'] = 'Add category';
$string['addnewdefaults'] = 'Add a new module default';
$string['addpurpose'] = 'Add purpose';
$string['approve'] = 'Approve';
$string['approverequest'] = 'Approve request';
$string['automaticdeletionrequests'] = 'Create automatic data deletion requests';
$string['automaticdeletionrequests_desc'] = 'If enabled, a data deletion request will be created automatically for any user accounts deleted manually.';
$string['bulkapproverequests'] = 'Approve requests';
$string['bulkdenyrequests'] = 'Deny requests';
$string['cachedef_purpose'] = 'Data purposes';
$string['cachedef_purpose_overrides'] = 'Purpose overrides in the Data privacy tool';
$string['cachedef_contextlevel'] = 'Context levels purpose and category';
$string['cancelrequest'] = 'Cancel request';
$string['cancelrequestconfirmation'] = 'Do you really want cancel this data request?';
$string['cannotreset'] = 'Unable to reset this request. Only rejected requests can be reset.';
$string['categories'] = 'Categories';
$string['category'] = 'Category';
$string['category_help'] = 'A category in the data registry describes a type of data. A new category may be added, or if Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > Site.';
@@ -46,10 +53,14 @@ $string['categoryupdated'] = 'Category updated';
$string['close'] = 'Close';
$string['compliant'] = 'Compliant';
$string['confirmapproval'] = 'Do you really want to approve this data request?';
$string['confirmbulkapproval'] = 'Do you really want to bulk approve the selected data requests?';
$string['confirmcompletion'] = 'Do you really want to mark this user enquiry as complete?';
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
$string['confirmdenial'] = 'Do you really want deny this data request?';
$string['contactdataprotectionofficer'] = 'Contact Data Protection Officer';
$string['contactdataprotectionofficer_desc'] = 'If enabled, users will be able to contact the Data Protection Officer and make a data request via a link on their profile page.';
$string['confirmbulkdenial'] = 'Do you really want to bulk deny the selected data requests?';
$string['confirmrequestresubmit'] = 'Are you sure you wish to cancel the current {$a->type} request for {$a->username} and resubmit it?';
$string['contactdataprotectionofficer'] = 'Contact the privacy officer';
$string['contactdataprotectionofficer_desc'] = 'If enabled, users will be able to contact the privacy officer and make a data request via a link on their profile page.';
$string['contextlevelname10'] = 'Site';
$string['contextlevelname30'] = 'Users';
$string['contextlevelname40'] = 'Course categories';
@@ -57,41 +68,63 @@ $string['contextlevelname50'] = 'Courses';
$string['contextlevelname70'] = 'Activity modules';
$string['contextlevelname80'] = 'Blocks';
$string['contextpurposecategorysaved'] = 'Purpose and category saved.';
$string['contactdpoviaprivacypolicy'] = 'Please contact the Data Protection Officer as described in the privacy policy.';
$string['contactdpoviaprivacypolicy'] = 'Please contact the privacy officer as described in the privacy policy.';
$string['createcategory'] = 'Create data category';
$string['createnewdatarequest'] = 'Create a new data request';
$string['createpurpose'] = 'Create data purpose';
$string['creationauto'] = 'Automatically';
$string['creationmanual'] = 'Manually';
$string['datadeletion'] = 'Data deletion';
$string['datadeletionpagehelp'] = 'Data for which the retention period has expired are listed here. Please review and confirm data deletion, which will then be executed by the "Delete expired contexts" scheduled task.';
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
$string['dataprivacy:managedatarequests'] = 'Manage data requests';
$string['dataprivacy:managedataregistry'] = 'Manage data registry';
$string['dataprivacy:downloadownrequest'] = 'Download your own exported data';
$string['dataprivacy:downloadallrequests'] = 'Download exported data for everyone';
$string['dataregistry'] = 'Data registry';
$string['dataregistryinfo'] = 'The data registry enables categories (types of data) and purposes (the reasons for processing data) to be set for all content on the site - from users and courses down to activities and blocks. For each purpose, a retention period may be set. When a retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
$string['dataretentionexplanation'] = 'This summary shows the default categories and purposes for retaining user data. Certain areas may have more specific categories and purposes than those listed here.';
$string['dataretentionsummary'] = 'Data retention summary';
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
$string['datarequestcreatedfromscheduledtask'] = 'Automatically created from a scheduled task (pre-existing deleted user).';
$string['datarequestemailsubject'] = 'Data request: {$a}';
$string['datarequestcreatedupondelete'] = 'Automatically created upon user deletion.';
$string['datarequests'] = 'Data requests';
$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
$string['daterequested'] = 'Date requested';
$string['daterequesteddetail'] = 'Date requested:';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
$string['deletecategory'] = 'Delete "{$a}" category';
$string['deletecategorytext'] = 'Are you sure you want to delete "{$a}" category?';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all new and existing instances where a value is not set.';
$string['defaultswarninginfo'] = 'Warning: Changing these defaults may affect the retention period of existing instances.';
$string['deletecategory'] = 'Delete category';
$string['deletecategorytext'] = 'Are you sure you want to delete the category \'{$a}\'?';
$string['deletedefaults'] = 'Delete defaults: {$a}';
$string['deletedefaultsconfirmation'] = 'Are you sure you want to delete the default category and purpose for {$a} modules?';
$string['deleteexpiredcontextstask'] = 'Delete expired contexts';
$string['deletepurpose'] = 'Delete "{$a}" purpose';
$string['deletepurposetext'] = 'Are you sure you want to delete "{$a}" purpose?';
$string['deleteexpireddatarequeststask'] = 'Delete expired data request export files';
$string['deleteexistingdeleteduserstask'] = 'Create delete data request for pre-existing deleted users';
$string['deletemyaccount'] = 'Delete my account';
$string['deletepurpose'] = 'Delete purpose';
$string['deletepurposetext'] = 'Are you sure you want to delete the purpose \'{$a}\'?';
$string['defaultssaved'] = 'Defaults saved';
$string['deny'] = 'Deny';
$string['denyrequest'] = 'Deny request';
$string['deprecated'] = 'Deprecated';
$string['deprecatedexplanation'] = 'This plugin is using an old version of one of the privacy interfaces and should be updated.';
$string['download'] = 'Download';
$string['dporolemapping'] = 'Data Protection Officer role mapping';
$string['dporolemapping_desc'] = 'The Data Protection Officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a Data Protection Officer role mapping option.';
$string['downloadexpireduser'] = 'Download has expired. Submit a new request if you wish to export your personal data.';
$string['dporolemapping'] = 'Privacy officer role mapping';
$string['dporolemapping_desc'] = 'The privacy officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a privacy officer role mapping option.';
$string['editcategories'] = 'Edit categories';
$string['editcategory'] = 'Edit category';
$string['editcategories'] = 'Edit categories';
$string['editdefaults'] = 'Edit defaults: {$a}';
$string['editmoduledefaults'] = 'Edit module defaults';
$string['editpurpose'] = 'Edit purpose';
$string['editpurposes'] = 'Edit purposes';
$string['effectiveretentionperiodcourse'] = '{$a} (after the course end date)';
$string['effectiveretentionperioduser'] = '{$a} (since the last time the user accessed the site)';
$string['emailsalutation'] = 'Dear {$a},';
$string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!';
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
$string['errorinvalidrequesttype'] = 'Invalid request type!';
$string['errornocapabilitytorequestforothers'] = 'User {$a->requestedby} doesn\'t have the capability to make a data request on behalf of user {$a->userid}';
@@ -101,8 +134,10 @@ $string['errorrequestalreadyexists'] = 'You already have an ongoing request.';
$string['errorrequestnotfound'] = 'Request not found';
$string['errorrequestnotwaitingforapproval'] = 'The request is not awaiting approval. Either it is not yet ready or it has already been processed.';
$string['errorsendingmessagetodpo'] = 'An error was encountered while trying to send a message to {$a}.';
$string['exceptionnotificationsubject'] = "Exception occured while processing privacy data";
$string['exceptionnotificationbody'] = "<p>Exception occured while calling <b>{\$a->fullmethodname}</b>.<br>This means that plugin <b>{\$a->component}</b> did not complete processing data. Below you can find exception information that can be passed to the plugin developer.</p><pre>{\$a->message}<br>\n\n{\$a->backtrace}</pre>";
$string['exceptionnotificationsubject'] = 'Exception occurred while processing privacy data';
$string['exceptionnotificationbody'] = '<p>Exception occurred while calling <b>{$a->fullmethodname}</b>.<br>This means that plugin <b>{$a->component}</b> did not complete the processing of data. The following exception information may be passed on to the plugin developer:</p><pre>{$a->message}<br>
{$a->backtrace}</pre>';
$string['expiredretentionperiodtask'] = 'Expired retention period';
$string['expiry'] = 'Expiry';
$string['expandplugin'] = 'Expand and collapse plugin.';
@@ -110,6 +145,7 @@ $string['expandplugintype'] = 'Expand and collapse plugin type.';
$string['explanationtitle'] = 'Icons used on this page and what they mean.';
$string['external'] = 'Additional';
$string['externalexplanation'] = 'An additional plugin installed on this site.';
$string['filteroption'] = '{$a->category}: {$a->name}';
$string['frontpagecourse'] = 'Front page course';
$string['gdpr_art_6_1_a_description'] = 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes';
$string['gdpr_art_6_1_a_name'] = 'Consent (GDPR Art. 6.1(a))';
@@ -148,6 +184,8 @@ $string['httpwarning'] = 'Any data downloaded from this site may not be encrypte
$string['inherit'] = 'Inherit';
$string['lawfulbases'] = 'Lawful bases';
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
$string['markcomplete'] = 'Mark as complete';
$string['markedcomplete'] = 'Your enquiry has been marked as complete by the privacy officer.';
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
$string['messageprovider:notifyexceptions'] = 'Data requests exceptions notifications';
@@ -160,6 +198,7 @@ $string['nameemail'] = '{$a->name} ({$a->email})';
$string['nchildren'] = '{$a} children';
$string['newrequest'] = 'New request';
$string['nodatarequests'] = 'There are no data requests';
$string['nodatarequestsmatchingfilter'] = 'There are no data requests matching the given filter';
$string['noactivitiestoload'] = 'No activities';
$string['noassignedroles'] = 'No assigned roles in this context';
$string['noblockstoload'] = 'No blocks';
@@ -171,15 +210,22 @@ $string['nopurposes'] = 'There are no purposes yet';
$string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
$string['nosystemdefaults'] = 'Site purpose and category have not yet been defined.';
$string['notset'] = 'Not set (use the default value)';
$string['notyetexpired'] = '{$a} (not yet expired)';
$string['overrideinstances'] = 'Reset instances with custom values';
$string['pluginregistry'] = 'Plugin privacy registry';
$string['pluginregistrytitle'] = 'Plugin privacy compliance registry';
$string['privacy'] = 'Privacy';
$string['privacyofficeronly'] = 'Only users who are assigned a privacy officer role ({$a}) have access to this content';
$string['privacy:metadata:preference:tool_dataprivacy_request-filters'] = 'The filters currently applied to the data requests page.';
$string['privacy:metadata:preference:tool_dataprivacy_request-perpage'] = 'The number of data requests the user prefers to see on one page';
$string['privacy:metadata:request'] = 'Information from personal data requests (subject access and deletion requests) made for this site.';
$string['privacy:metadata:request:comments'] = 'Any user comments accompanying the request.';
$string['privacy:metadata:request:userid'] = 'The ID of the user to whom the request belongs';
$string['privacy:metadata:request:requestedby'] = 'The ID of the user making the request, if made on behalf of another user.';
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s Data Protection Officer regarding the request.';
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s privacy officer regarding the request.';
$string['privacy:metadata:request:timecreated'] = 'The timestamp indicating when the request was made by the user.';
$string['privacyrequestexpiry'] = 'Data request expiry';
$string['privacyrequestexpiry_desc'] = 'The time that approved data requests will be available for download before expiring. If set to zero, then there is no time limit.';
$string['protected'] = 'Protected';
$string['protectedlabel'] = 'The retention of this data has a higher legal precedent over a user\'s request to be forgotten. This data will only be deleted after the retention period has expired.';
$string['purpose'] = 'Purpose';
@@ -192,24 +238,44 @@ $string['purposeslist'] = 'List of data purposes';
$string['purposeupdated'] = 'Purpose updated';
$string['replyto'] = 'Reply to';
$string['requestactions'] = 'Actions';
$string['requestapproved'] = 'The request has been approved';
$string['requestby'] = 'Requested by';
$string['requestbydetail'] = 'Requested by:';
$string['requestcomments'] = 'Comments';
$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
$string['requestcreation'] = 'Creation';
$string['requestdenied'] = 'The request has been denied';
$string['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'Requesting for';
$string['requestfor'] = 'User';
$string['requestmarkedcomplete'] = 'The request has been marked as complete';
$string['requestorigin'] = 'Site';
$string['requestsapproved'] = 'The requests have been approved';
$string['requestsdenied'] = 'The requests have been denied';
$string['requeststatus'] = 'Status';
$string['requestsubmitted'] = 'Your request has been submitted to the site\'s Data Protection Officer';
$string['requestsubmitted'] = 'Your request has been submitted to the privacy officer';
$string['requesttype'] = 'Type';
$string['requesttypeuser'] = '{$a->typename} ({$a->user})';
$string['requesttype_help'] = 'Select the reason why you would like to contact the site\'s Data Protection Officer';
$string['requesttype_help'] = 'Select the reason for contacting the privacy officer. Be aware that deletion of all personal data will result in you no longer being able to log in to the site.';
$string['requesttypedelete'] = 'Delete all of my personal data';
$string['requesttypedeleteshort'] = 'Delete';
$string['requesttypeexport'] = 'Export all of my personal data';
$string['requesttypeexportshort'] = 'Export';
$string['requesttypeothers'] = 'General inquiry';
$string['requesttypeothersshort'] = 'Others';
$string['requesttypeothersshort'] = 'Message';
$string['requireallenddatesforuserdeletion'] = 'Consider courses without end date as active';
$string['requireallenddatesforuserdeletion_desc'] = 'When calculating user expiry, several factors are considered:
* the user\'s last login time is compared against the retention period for users; and
* whether the user is actively enrolled in any courses.
When checking the active enrolment in a course, if the course has no end date then this setting is used to determine whether that course is considered active or not.
If the course has no end date, and this setting is enabled, then the user cannot be deleted.';
$string['requiresattention'] = 'Requires attention.';
$string['requiresattentionexplanation'] = 'This plugin does not implement the Moodle privacy API. If this plugin stores any personal data it will not be able to be exported or deleted through Moodle\'s privacy system.';
$string['resubmitrequestasnew'] = 'Resubmit as new request';
$string['resubmitrequest'] = 'Resubmit {$a->type} request for {$a->username}';
$string['resubmittedrequest'] = 'The existing {$a->type} request for {$a->username} was cancelled and resubmitted';
$string['resultdeleted'] = 'You recently requested to have your account and personal data in {$a} to be deleted. This process has been completed and you will no longer be able to log in.';
$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
$string['reviewdata'] = 'Review data';
@@ -217,21 +283,53 @@ $string['retentionperiod'] = 'Retention period';
$string['retentionperiod_help'] = 'The retention period specifies the length of time that data should be kept for. When the retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
$string['retentionperiodnotdefined'] = 'No retention period was defined';
$string['retentionperiodzero'] = 'No retention period';
$string['roleoverrides'] = 'Role overrides';
$string['selectbulkaction'] = 'Please select a bulk action.';
$string['selectdatarequests'] = 'Please select data requests.';
$string['selectuserdatarequest'] = 'Select {$a->username}\'s {$a->requesttype} data request.';
$string['send'] = 'Send';
$string['sensitivedatareasons'] = 'Sensitive personal data processing reasons';
$string['sensitivedatareasons_help'] = 'Select one or more applicable reasons that exempts the prohibition of processing sensitive personal data tied to this purpose. For more information, please see <a href="https://gdpr-info.eu/art-9-gdpr/" target="_blank">GDPR Art. 9.2</a>';
$string['setdefaults'] = 'Set defaults';
$string['showdataretentionsummary'] = 'Show data retention summary';
$string['showdataretentionsummary_desc'] = 'If enabled, a link to the data retention summary is shown in the page footer and in the user profile page.';
$string['statusapproved'] = 'Approved';
$string['statusawaitingapproval'] = 'Awaiting approval';
$string['statuscancelled'] = 'Cancelled';
$string['statuscomplete'] = 'Complete';
$string['statusready'] = 'Download ready';
$string['statusdeleted'] = 'Deleted';
$string['statusdetail'] = 'Status:';
$string['statusexpired'] = 'Expired';
$string['statuspreprocessing'] = 'Pre-processing';
$string['statusprocessing'] = 'Processing';
$string['statuspending'] = 'Pending';
$string['statusrejected'] = 'Rejected';
$string['subjectscope'] = 'Subject scope';
$string['subjectscope_help'] = 'The subject scope lists the roles which may be assigned in this context.';
$string['summary'] = 'Registry configuration summary';
$string['systemconfignotsetwarning'] = 'A site purpose and category have not been defined. When these are not defined, all data will be removed when processing deletion requests.';
$string['user'] = 'User';
$string['userlistnoncompliant'] = 'Userlist provider missing';
$string['userlistexplanation'] = 'This plugin has the base provider but should also implement the userlist provider for full support of privacy functionality.';
$string['viewrequest'] = 'View the request';
$string['visible'] = 'Expand all';
$string['unexpiredrolewithretention'] = '{$a->retention} (Unexpired)';
$string['expiredrolewithretention'] = '{$a->retention} (Expired)';
$string['defaultexpired'] = 'Data for all users';
$string['defaultexpiredexcept'] = 'Data for all users, except those who hold any of the following roles:<br>
{$a->unexpired}';
$string['defaultunexpiredwithexceptions'] = 'Only data for users who hold any of the following roles:<br>
{$a->expired}
Unless they also hold any of the following roles:<br>
{$a->unexpired}';
$string['defaultunexpired'] = 'Only data for users holding any of the following roles:<br>
{$a->expired}';
$string['tobedeleted'] = 'Data to be deleted';
$string['addroleoverride'] = 'Add role override';
$string['roleoverride'] = 'Role override';
$string['role'] = 'Role';
$string['role_help'] = 'The role which the override should apply to.';
$string['duplicaterole'] = 'Role already specified';
$string['purposeoverview'] = 'A purpose describes the intended use and retention policy for stored data. The basis for storing and retaining that data is also described in the purpose.';
$string['roleoverrideoverview'] = 'The default retention policy can be overridden for specific user roles, allowing you to specify a longer, or a shorter, retention policy. A user is only expired when all of their roles have expired.';
+71 -15
View File
@@ -63,6 +63,42 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser
$node = new core_user\output\myprofile\node('privacyandpolicies', 'datarequests',
get_string('datarequests', 'tool_dataprivacy'), null, $url);
$category->add_node($node);
// Check if the user has an ongoing data export request.
$hasexportrequest = \tool_dataprivacy\api::has_ongoing_request($user->id, \tool_dataprivacy\api::DATAREQUEST_TYPE_EXPORT);
// Show data export link only if the user doesn't have an ongoing data export request.
if (!$hasexportrequest) {
$exportparams = ['type' => \tool_dataprivacy\api::DATAREQUEST_TYPE_EXPORT];
$exporturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', $exportparams);
$exportnode = new core_user\output\myprofile\node('privacyandpolicies', 'requestdataexport',
get_string('requesttypeexport', 'tool_dataprivacy'), null, $exporturl);
$category->add_node($exportnode);
}
// Check if the user has an ongoing data deletion request.
$hasdeleterequest = \tool_dataprivacy\api::has_ongoing_request($user->id, \tool_dataprivacy\api::DATAREQUEST_TYPE_DELETE);
// Show data deletion link only if the user doesn't have an ongoing data deletion request.
if (!$hasdeleterequest) {
$deleteparams = ['type' => \tool_dataprivacy\api::DATAREQUEST_TYPE_DELETE];
$deleteurl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', $deleteparams);
$deletenode = new core_user\output\myprofile\node('privacyandpolicies', 'requestdatadeletion',
get_string('deletemyaccount', 'tool_dataprivacy'), null, $deleteurl);
$category->add_node($deletenode);
}
}
// A returned 0 means that the setting was set and disabled, false means that there is no value for the provided setting.
$showsummary = get_config('tool_dataprivacy', 'showdataretentionsummary');
if ($showsummary === false) {
// This means that no value is stored in db. We use the default value in this case.
$showsummary = true;
}
if ($showsummary) {
$summaryurl = new moodle_url('/admin/tool/dataprivacy/summary.php');
$summarynode = new core_user\output\myprofile\node('privacyandpolicies', 'retentionsummary',
get_string('dataretentionsummary', 'tool_dataprivacy'), null, $summaryurl);
$category->add_node($summarynode);
}
// Add the Privacy category to the tree if it's not empty and it doesn't exist.
@@ -77,6 +113,29 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser
return false;
}
/**
* Callback to add footer elements.
*
* @return string HTML footer content
*/
function tool_dataprivacy_standard_footer_html() {
$output = '';
// A returned 0 means that the setting was set and disabled, false means that there is no value for the provided setting.
$showsummary = get_config('tool_dataprivacy', 'showdataretentionsummary');
if ($showsummary === false) {
// This means that no value is stored in db. We use the default value in this case.
$showsummary = true;
}
if ($showsummary) {
$url = new moodle_url('/admin/tool/dataprivacy/summary.php');
$output = html_writer::link($url, get_string('dataretentionsummary', 'tool_dataprivacy'));
$output = html_writer::div($output, 'tool_dataprivacy');
}
return $output;
}
/**
* Fragment to add a new purpose.
*
@@ -185,26 +244,23 @@ function tool_dataprivacy_output_fragment_contextlevel_form($args) {
* @return bool Returns false if we don't find a file.
*/
function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
global $USER;
if ($context->contextlevel == CONTEXT_USER) {
// Make sure the user is logged in.
require_login(null, false);
// Validate the user downloading this archive.
$usercontext = context_user::instance($USER->id);
// The user downloading this is not the user the archive has been prepared for. Check if it's the requester (e.g. parent).
if ($usercontext->instanceid !== $context->instanceid) {
// Get the data request ID. This should be the first element of the $args array.
$itemid = $args[0];
// Fetch the data request object. An invalid ID will throw an exception.
$datarequest = new \tool_dataprivacy\data_request($itemid);
// Get the data request ID. This should be the first element of the $args array.
$itemid = $args[0];
// Fetch the data request object. An invalid ID will throw an exception.
$datarequest = new \tool_dataprivacy\data_request($itemid);
// Check if the user is the requester and has the capability to make data requests for the target user.
$candownloadforuser = has_capability('tool/dataprivacy:makedatarequestsforchildren', $context);
if ($USER->id != $datarequest->get('requestedby') || !$candownloadforuser) {
return false;
}
// Check if user is allowed to download it.
if (!\tool_dataprivacy\api::can_download_data_request_for_user($context->instanceid, $datarequest->get('requestedby'))) {
return false;
}
// Make the file unavailable if it has expired.
if (\tool_dataprivacy\data_request::is_expired($datarequest)) {
send_file_not_found();
}
// All good. Serve the exported data.
+1 -1
View File
@@ -55,7 +55,7 @@ $PAGE->set_title($title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$requests = tool_dataprivacy\api::get_data_requests($USER->id);
$requests = tool_dataprivacy\api::get_data_requests($USER->id, [], [], [], 'timecreated DESC');
$requestlist = new tool_dataprivacy\output\my_data_requests_page($requests);
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
echo $requestlistoutput->render($requestlist);
+14 -5
View File
@@ -25,6 +25,8 @@
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_login(null, false);
$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
$contextid = optional_param('contextid', 0, PARAM_INT);
@@ -36,11 +38,18 @@ $title = get_string('pluginregistry', 'tool_dataprivacy');
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
// Get data!
$metadatatool = new \tool_dataprivacy\metadata_registry();
$metadata = $metadatatool->get_registry_metadata();
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
// Get data!
$metadatatool = new \tool_dataprivacy\metadata_registry();
$metadata = $metadatatool->get_registry_metadata();
$dataregistry = new tool_dataprivacy\output\data_registry_compliance_page($metadata);
$dataregistry = new tool_dataprivacy\output\data_registry_compliance_page($metadata);
echo $output->render($dataregistry);
} else {
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
echo $OUTPUT->notification($message, 'error');
}
echo $output->render($dataregistry);
echo $OUTPUT->footer();
+3
View File
@@ -24,6 +24,8 @@
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$url = new moodle_url("/admin/tool/dataprivacy/purposes.php");
$title = get_string('editpurposes', 'tool_dataprivacy');
@@ -31,6 +33,7 @@ $title = get_string('editpurposes', 'tool_dataprivacy');
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
echo $output->heading($title);
$purposes = \tool_dataprivacy\api::get_purposes();
$renderable = new \tool_dataprivacy\output\purposes($purposes);
@@ -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/>.
/**
* Display the request reject + resubmit confirmation page.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
require_once('../../../config.php');
$requestid = required_param('requestid', PARAM_INT);
$confirm = optional_param('confirm', null, PARAM_INT);
$PAGE->set_url(new moodle_url('/admin/tool/dataprivacy/resubmitrequest.php', ['requestid' => $requestid]));
require_login();
$PAGE->set_context(\context_system::instance());
require_capability('tool/dataprivacy:managedatarequests', $PAGE->context);
$manageurl = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
$originalrequest = \tool_dataprivacy\api::get_request($requestid);
$user = \core_user::get_user($originalrequest->get('userid'));
$stringparams = (object) [
'username' => fullname($user),
'type' => \tool_dataprivacy\local\helper::get_shortened_request_type_string($originalrequest->get('type')),
];
if (null !== $confirm && confirm_sesskey()) {
$originalrequest->resubmit_request();
redirect($manageurl, get_string('resubmittedrequest', 'tool_dataprivacy', $stringparams));
}
$heading = get_string('resubmitrequest', 'tool_dataprivacy', $stringparams);
$PAGE->set_title($heading);
$PAGE->set_heading($heading);
echo $OUTPUT->header();
$confirmstring = get_string('confirmrequestresubmit', 'tool_dataprivacy', $stringparams);
$confirmurl = new \moodle_url($PAGE->url, ['confirm' => 1]);
echo $OUTPUT->confirm($confirmstring, $confirmurl, $manageurl);
echo $OUTPUT->footer();
+45 -16
View File
@@ -34,6 +34,20 @@ if ($hassiteconfig) {
new lang_string('contactdataprotectionofficer_desc', 'tool_dataprivacy'), 0)
);
// Automatically create delete data request for users upon user deletion.
// Automatically create delete data request for pre-existing deleted users.
// Disabled by default.
$privacysettings->add(new admin_setting_configcheckbox('tool_dataprivacy/automaticdeletionrequests',
new lang_string('automaticdeletionrequests', 'tool_dataprivacy'),
new lang_string('automaticdeletionrequests_desc', 'tool_dataprivacy'), 0)
);
// Set days approved data requests will be accessible. 1 week default.
$privacysettings->add(new admin_setting_configduration('tool_dataprivacy/privacyrequestexpiry',
new lang_string('privacyrequestexpiry', 'tool_dataprivacy'),
new lang_string('privacyrequestexpiry_desc', 'tool_dataprivacy'),
WEEKSECS, 1));
// Fetch roles that are assignable.
$assignableroles = get_assignable_roles(context_system::instance());
@@ -54,25 +68,40 @@ if ($hassiteconfig) {
new lang_string('dporolemapping_desc', 'tool_dataprivacy'), null, $roles)
);
}
// When calculating user expiry, should courses which have no end date be considered.
$privacysettings->add(new admin_setting_configcheckbox('tool_dataprivacy/requireallenddatesforuserdeletion',
new lang_string('requireallenddatesforuserdeletion', 'tool_dataprivacy'),
new lang_string('requireallenddatesforuserdeletion_desc', 'tool_dataprivacy'),
1));
// Whether the data retention summary should be shown in the page footer and in the user profile page.
$privacysettings->add(new admin_setting_configcheckbox('tool_dataprivacy/showdataretentionsummary',
new lang_string('showdataretentionsummary', 'tool_dataprivacy'),
new lang_string('showdataretentionsummary_desc', 'tool_dataprivacy'),
1));
}
}
// Link that leads to the data requests management page.
$ADMIN->add('privacy', new admin_externalpage('datarequests', get_string('datarequests', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/datarequests.php'), 'tool/dataprivacy:managedatarequests')
);
// Restrict config links to the DPO.
if (tool_dataprivacy\api::is_site_dpo($USER->id)) {
// Link that leads to the data requests management page.
$ADMIN->add('privacy', new admin_externalpage('datarequests', get_string('datarequests', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/datarequests.php'), 'tool/dataprivacy:managedatarequests')
);
// Link that leads to the data registry management page.
$ADMIN->add('privacy', new admin_externalpage('dataregistry', get_string('dataregistry', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/dataregistry.php'), 'tool/dataprivacy:managedataregistry')
);
// Link that leads to the data registry management page.
$ADMIN->add('privacy', new admin_externalpage('dataregistry', get_string('dataregistry', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/dataregistry.php'), 'tool/dataprivacy:managedataregistry')
);
// Link that leads to the review page of expired contexts that are up for deletion.
$ADMIN->add('privacy', new admin_externalpage('datadeletion', get_string('datadeletion', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/datadeletion.php'), 'tool/dataprivacy:managedataregistry')
);
// Link that leads to the review page of expired contexts that are up for deletion.
$ADMIN->add('privacy', new admin_externalpage('datadeletion', get_string('datadeletion', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/datadeletion.php'), 'tool/dataprivacy:managedataregistry')
);
// Link that leads to the other data registry management page.
$ADMIN->add('privacy', new admin_externalpage('pluginregistry', get_string('pluginregistry', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/pluginregistry.php'), 'tool/dataprivacy:managedataregistry')
);
// Link that leads to the other data registry management page.
$ADMIN->add('privacy', new admin_externalpage('pluginregistry', get_string('pluginregistry', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/pluginregistry.php'), 'tool/dataprivacy:managedataregistry')
);
}
+8
View File
@@ -24,3 +24,11 @@ dd a.contactdpo {
/* Reverting dd's left margin */
margin-left: inherit;
}
[data-region="data-requests-table"] .moodle-actionmenu {
min-width: 150px;
}
.context-level-view {
margin: 1em;
}
+41
View File
@@ -0,0 +1,41 @@
<?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 compliance data registry main page.
*
* @copyright 2018 onwards Adrian Greeve <adriangreeve.com>
* @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');
$url = new moodle_url('/' . $CFG->admin . '/tool/dataprivacy/summary.php');
$title = get_string('summary', 'tool_dataprivacy');
$context = \context_system::instance();
$PAGE->set_url($url);
$PAGE->set_context($context);
$PAGE->set_title($title);
$PAGE->set_heading($SITE->fullname);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$summarypage = new \tool_dataprivacy\output\summary_page();
echo $output->render($summarypage);
echo $output->footer();

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