Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
provider
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
9 / 9
19
100.00% covered (success)
100.00%
1 / 1
 get_metadata
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 get_contexts_for_userid
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 get_users_in_context
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 export_user_data
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
3
 delete_data_for_all_users_in_context
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 delete_data_for_user
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 delete_data_for_users
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 user_has_data
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 anonymise_users
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2// This file is part of Moodle - https://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <https://www.gnu.org/licenses/>.
16
17namespace tool_autoflow\privacy;
18
19use core_privacy\local\metadata\collection;
20use core_privacy\local\request\approved_contextlist;
21use core_privacy\local\request\approved_userlist;
22use core_privacy\local\request\contextlist;
23use core_privacy\local\request\transform;
24use core_privacy\local\request\userlist;
25use core_privacy\local\request\writer;
26
27/**
28 * Privacy provider for tool_autoflow.
29 *
30 * Workflows and runs live in the system context. Runs reference the user the
31 * triggering event was about; workflows reference their editor and run-as user.
32 *
33 * @package    tool_autoflow
34 * @copyright  2026 DevLMS <contact@devlms.com>
35 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class provider implements
38    \core_privacy\local\metadata\provider,
39    \core_privacy\local\request\core_userlist_provider,
40    \core_privacy\local\request\plugin\provider {
41    /**
42     * Describe the data this plugin stores.
43     *
44     * @param collection $collection
45     * @return collection
46     */
47    public static function get_metadata(collection $collection): collection {
48        $collection->add_database_table('tool_autoflow_run', [
49            'userid' => 'privacy:metadata:run:userid',
50            'log' => 'privacy:metadata:run:log',
51            'error' => 'privacy:metadata:run:error',
52            'timecreated' => 'privacy:metadata:run:timecreated',
53        ], 'privacy:metadata:run');
54        $collection->add_database_table('tool_autoflow_workflow', [
55            'usermodified' => 'privacy:metadata:workflow:usermodified',
56            'runasuserid' => 'privacy:metadata:workflow:runasuserid',
57            'timemodified' => 'privacy:metadata:workflow:timemodified',
58        ], 'privacy:metadata:workflow');
59        $collection->add_external_location_link('httprequest', [
60            'data' => 'privacy:metadata:httprequest:data',
61        ], 'privacy:metadata:httprequest');
62        return $collection;
63    }
64
65    /**
66     * Contexts holding data for a user: the system context if any row mentions them.
67     *
68     * @param int $userid
69     * @return contextlist
70     */
71    public static function get_contexts_for_userid(int $userid): contextlist {
72        $contextlist = new contextlist();
73        if (self::user_has_data($userid)) {
74            $contextlist->add_system_context();
75        }
76        return $contextlist;
77    }
78
79    /**
80     * Users with data in a context.
81     *
82     * @param userlist $userlist
83     */
84    public static function get_users_in_context(userlist $userlist) {
85        if ($userlist->get_context()->contextlevel != CONTEXT_SYSTEM) {
86            return;
87        }
88        $userlist->add_from_sql('userid', 'SELECT userid FROM {tool_autoflow_run} WHERE userid IS NOT NULL', []);
89        $userlist->add_from_sql('usermodified', 'SELECT usermodified FROM {tool_autoflow_workflow}', []);
90        $userlist->add_from_sql(
91            'runasuserid',
92            'SELECT runasuserid FROM {tool_autoflow_workflow} WHERE runasuserid IS NOT NULL',
93            []
94        );
95    }
96
97    /**
98     * Export a user's runs and the workflows they edit or run as.
99     *
100     * @param approved_contextlist $contextlist
101     */
102    public static function export_user_data(approved_contextlist $contextlist) {
103        global $DB;
104        $userid = $contextlist->get_user()->id;
105        foreach ($contextlist->get_contexts() as $context) {
106            if ($context->contextlevel != CONTEXT_SYSTEM) {
107                continue;
108            }
109            $runs = $DB->get_records(
110                'tool_autoflow_run',
111                ['userid' => $userid],
112                'id ASC',
113                'id, workflowid, status, triggertype, timecreated'
114            );
115            $workflows = $DB->get_records_select(
116                'tool_autoflow_workflow',
117                'usermodified = :u1 OR runasuserid = :u2',
118                ['u1' => $userid, 'u2' => $userid],
119                'id ASC',
120                'id, name, timemodified'
121            );
122            $data = (object) [
123                'runs' => array_values(array_map(fn($r) => [
124                    'workflowid' => $r->workflowid,
125                    'status' => $r->status,
126                    'trigger' => $r->triggertype,
127                    'timecreated' => transform::datetime($r->timecreated),
128                ], $runs)),
129                'workflows' => array_values(array_map(fn($w) => [
130                    'name' => $w->name,
131                    'timemodified' => transform::datetime($w->timemodified),
132                ], $workflows)),
133            ];
134            writer::with_context($context)->export_data([get_string('pluginname', 'tool_autoflow')], $data);
135        }
136    }
137
138    /**
139     * Delete everything about all users in a context.
140     *
141     * @param \context $context
142     */
143    public static function delete_data_for_all_users_in_context(\context $context) {
144        global $DB;
145        if ($context->contextlevel != CONTEXT_SYSTEM) {
146            return;
147        }
148        $DB->delete_records('tool_autoflow_run');
149        $DB->set_field('tool_autoflow_workflow', 'usermodified', 0);
150        $DB->set_field_select('tool_autoflow_workflow', 'runasuserid', null, 'runasuserid IS NOT NULL');
151    }
152
153    /**
154     * Delete one user's data.
155     *
156     * @param approved_contextlist $contextlist
157     */
158    public static function delete_data_for_user(approved_contextlist $contextlist) {
159        foreach ($contextlist->get_contexts() as $context) {
160            if ($context->contextlevel == CONTEXT_SYSTEM) {
161                self::anonymise_users([$contextlist->get_user()->id]);
162            }
163        }
164    }
165
166    /**
167     * Delete data for several users in a context.
168     *
169     * @param approved_userlist $userlist
170     */
171    public static function delete_data_for_users(approved_userlist $userlist) {
172        if ($userlist->get_context()->contextlevel == CONTEXT_SYSTEM) {
173            self::anonymise_users($userlist->get_userids());
174        }
175    }
176
177    /**
178     * Whether any row mentions the user.
179     *
180     * @param int $userid
181     * @return bool
182     */
183    private static function user_has_data(int $userid): bool {
184        global $DB;
185        return $DB->record_exists('tool_autoflow_run', ['userid' => $userid])
186            || $DB->record_exists_select(
187                'tool_autoflow_workflow',
188                'usermodified = :u1 OR runasuserid = :u2',
189                ['u1' => $userid, 'u2' => $userid]
190            );
191    }
192
193    /**
194     * Remove user data. Runs about the user are deleted, because their logs can hold the
195     * user's details. Workflows stay, as they belong to the site; only the user references go.
196     *
197     * @param int[] $userids
198     */
199    private static function anonymise_users(array $userids) {
200        global $DB;
201        if (!$userids) {
202            return;
203        }
204        [$insql, $params] = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
205        $DB->delete_records_select('tool_autoflow_run', "userid $insql", $params);
206        $DB->set_field_select('tool_autoflow_workflow', 'usermodified', 0, "usermodified $insql", $params);
207        $DB->set_field_select('tool_autoflow_workflow', 'runasuserid', null, "runasuserid $insql", $params);
208    }
209}