Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Data provider.
19
 *
20
 * @package    mod_survey
21
 * @copyright  2018 Frédéric Massart
22
 * @author     Frédéric Massart <fred@branchup.tech>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace mod_survey\privacy;
27
defined('MOODLE_INTERNAL') || die();
28
 
29
use context;
30
use context_helper;
31
use context_module;
32
use core_privacy\local\metadata\collection;
33
use core_privacy\local\request\approved_contextlist;
34
use core_privacy\local\request\approved_userlist;
35
use core_privacy\local\request\helper;
36
use core_privacy\local\request\transform;
37
use core_privacy\local\request\userlist;
38
use core_privacy\local\request\writer;
39
 
40
require_once($CFG->dirroot . '/mod/survey/lib.php');
41
 
42
/**
43
 * Data provider class.
44
 *
45
 * @package    mod_survey
46
 * @copyright  2018 Frédéric Massart
47
 * @author     Frédéric Massart <fred@branchup.tech>
48
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 */
50
class provider implements
51
    \core_privacy\local\metadata\provider,
52
    \core_privacy\local\request\core_userlist_provider,
53
    \core_privacy\local\request\plugin\provider {
54
 
55
    /**
56
     * Returns metadata.
57
     *
58
     * @param collection $collection The initialised collection to add items to.
59
     * @return collection A listing of user data stored through this system.
60
     */
61
    public static function get_metadata(collection $collection): collection {
62
        $collection->add_database_table('survey_answers', [
63
            'userid' => 'privacy:metadata:answers:userid',
64
            'question' => 'privacy:metadata:answers:question',
65
            'answer1' => 'privacy:metadata:answers:answer1',
66
            'answer2' => 'privacy:metadata:answers:answer2',
67
            'time' => 'privacy:metadata:answers:time',
68
        ], 'privacy:metadata:answers');
69
 
70
        $collection->add_database_table('survey_analysis', [
71
            'userid' => 'privacy:metadata:analysis:userid',
72
            'notes' => 'privacy:metadata:analysis:notes',
73
        ], 'privacy:metadata:analysis');
74
 
75
        return $collection;
76
    }
77
 
78
    /**
79
     * Get the list of contexts that contain user information for the specified user.
80
     *
81
     * @param int $userid The user to search.
82
     * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
83
     */
84
    public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {
85
        $contextlist = new \core_privacy\local\request\contextlist();
86
 
87
        // While we should not have an analysis without answers, it's safer to gather contexts by looking at both tables.
88
        $sql = "
89
            SELECT DISTINCT ctx.id
90
              FROM {survey} s
91
              JOIN {modules} m
92
                ON m.name = :survey
93
              JOIN {course_modules} cm
94
                ON cm.instance = s.id
95
               AND cm.module = m.id
96
              JOIN {context} ctx
97
                ON ctx.instanceid = cm.id
98
               AND ctx.contextlevel = :modulelevel
99
         LEFT JOIN {survey_answers} sa
100
                ON sa.survey = s.id
101
               AND sa.userid = :userid1
102
         LEFT JOIN {survey_analysis} sy
103
                ON sy.survey = s.id
104
               AND sy.userid = :userid2
105
             WHERE s.template <> 0
106
               AND (sa.id IS NOT NULL
107
                OR sy.id IS NOT NULL)";
108
 
109
        $contextlist->add_from_sql($sql, [
110
            'survey' => 'survey',
111
            'modulelevel' => CONTEXT_MODULE,
112
            'userid1' => $userid,
113
            'userid2' => $userid,
114
        ]);
115
 
116
        return $contextlist;
117
    }
118
 
119
    /**
120
     * Get the list of users who have data within a context.
121
     *
122
     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
123
     */
124
    public static function get_users_in_context(userlist $userlist) {
125
        $context = $userlist->get_context();
126
 
127
        if (!is_a($context, \context_module::class)) {
128
            return;
129
        }
130
 
131
        $params = [
132
            'survey' => 'survey',
133
            'modulelevel' => CONTEXT_MODULE,
134
            'contextid' => $context->id,
135
        ];
136
 
137
        $sql = "
138
            SELECT sa.userid
139
              FROM {survey} s
140
              JOIN {modules} m
141
                ON m.name = :survey
142
              JOIN {course_modules} cm
143
                ON cm.instance = s.id
144
               AND cm.module = m.id
145
              JOIN {context} ctx
146
                ON ctx.instanceid = cm.id
147
               AND ctx.contextlevel = :modulelevel
148
              JOIN {survey_answers} sa
149
                ON sa.survey = s.id
150
             WHERE ctx.id = :contextid
151
               AND s.template <> 0";
152
 
153
        $userlist->add_from_sql('userid', $sql, $params);
154
 
155
        $sql = "
156
            SELECT sy.userid
157
              FROM {survey} s
158
              JOIN {modules} m
159
                ON m.name = :survey
160
              JOIN {course_modules} cm
161
                ON cm.instance = s.id
162
               AND cm.module = m.id
163
              JOIN {context} ctx
164
                ON ctx.instanceid = cm.id
165
               AND ctx.contextlevel = :modulelevel
166
              JOIN {survey_analysis} sy
167
                ON sy.survey = s.id
168
             WHERE ctx.id = :contextid
169
               AND s.template <> 0";
170
 
171
        $userlist->add_from_sql('userid', $sql, $params);
172
    }
173
 
174
    /**
175
     * Export all user data for the specified user, in the specified contexts.
176
     *
177
     * @param approved_contextlist $contextlist The approved contexts to export information for.
178
     */
179
    public static function export_user_data(approved_contextlist $contextlist) {
180
        global $DB;
181
 
182
        $user = $contextlist->get_user();
183
        $userid = $user->id;
184
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
185
            if ($context->contextlevel == CONTEXT_MODULE) {
186
                $carry[] = $context->instanceid;
187
            }
188
            return $carry;
189
        }, []);
190
 
191
        if (empty($cmids)) {
192
            return;
193
        }
194
 
195
        // Export the answers.
196
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
197
        $sql = "
198
            SELECT sa.*,
199
                   sq.id as qid,
200
                   sq.text as qtext,
201
                   sq.shorttext as qshorttext,
202
                   sq.intro as qintro,
203
                   sq.options as qoptions,
204
                   sq.type as qtype,
205
                   cm.id as cmid
206
              FROM {survey_answers} sa
207
              JOIN {survey_questions} sq
208
                ON sq.id = sa.question
209
              JOIN {survey} s
210
                ON s.id = sa.survey
211
              JOIN {modules} m
212
                ON m.name = :survey
213
              JOIN {course_modules} cm
214
                ON cm.instance = s.id
215
               AND cm.module = m.id
216
             WHERE cm.id $insql
217
               AND sa.userid = :userid
218
          ORDER BY s.id, sq.id";
219
        $params = array_merge($inparams, ['survey' => 'survey', 'userid' => $userid]);
220
 
221
        $recordset = $DB->get_recordset_sql($sql, $params);
222
        static::recordset_loop_and_export($recordset, 'cmid', [], function($carry, $record) {
223
            $q = survey_translate_question((object) [
224
                'text' => $record->qtext,
225
                'shorttext' => $record->qshorttext,
226
                'intro' => $record->qintro,
227
                'options' => $record->qoptions
228
            ]);
229
            $qtype = $record->qtype;
230
            $options = explode(',', $q->options ?? '');
231
 
232
            $carry[] = [
233
                'question' => array_merge((array) $q, [
234
                    'options' => $qtype > 0 ? $options : '-'
235
                ]),
236
                'answer' => [
237
                    'actual' => $qtype > 0 && !empty($record->answer1) ? $options[$record->answer1 - 1] : $record->answer1,
238
                    'preferred' => $qtype > 0 && !empty($record->answer2) ? $options[$record->answer2 - 1] : $record->answer2,
239
                ],
240
                'time' => transform::datetime($record->time),
241
            ];
242
            return $carry;
243
 
244
        }, function($cmid, $data) use ($user) {
245
            $context = context_module::instance($cmid);
246
            $contextdata = helper::get_context_data($context, $user);
247
            $contextdata = (object) array_merge((array) $contextdata, ['answers' => $data]);
248
            helper::export_context_files($context, $user);
249
            writer::with_context($context)->export_data([], $contextdata);
250
        });
251
 
252
        // Export the analysis.
253
        $sql = "
254
            SELECT sy.*, cm.id as cmid
255
              FROM {survey_analysis} sy
256
              JOIN {survey} s
257
                ON s.id = sy.survey
258
              JOIN {modules} m
259
                ON m.name = :survey
260
              JOIN {course_modules} cm
261
                ON cm.instance = s.id
262
               AND cm.module = m.id
263
             WHERE cm.id $insql
264
               AND sy.userid = :userid
265
          ORDER BY s.id";
266
        $params = array_merge($inparams, ['survey' => 'survey', 'userid' => $userid]);
267
 
268
        $recordset = $DB->get_recordset_sql($sql, $params);
269
        static::recordset_loop_and_export($recordset, 'cmid', null, function($carry, $record) {
270
            $carry = ['notes' => $record->notes];
271
            return $carry;
272
        }, function($cmid, $data) {
273
            $context = context_module::instance($cmid);
274
            writer::with_context($context)->export_related_data([], 'survey_analysis', (object) $data);
275
        });
276
    }
277
 
278
    /**
279
     * Delete all data for all users in the specified context.
280
     *
281
     * @param context $context The specific context to delete data for.
282
     */
283
    public static function delete_data_for_all_users_in_context(context $context) {
284
        global $DB;
285
 
286
        if ($context->contextlevel != CONTEXT_MODULE) {
287
            return;
288
        }
289
 
290
        if ($surveyid = static::get_survey_id_from_context($context)) {
291
            $DB->delete_records('survey_answers', ['survey' => $surveyid]);
292
            $DB->delete_records('survey_analysis', ['survey' => $surveyid]);
293
        }
294
    }
295
 
296
    /**
297
     * Delete all user data for the specified user, in the specified contexts.
298
     *
299
     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
300
     */
301
    public static function delete_data_for_user(approved_contextlist $contextlist) {
302
        global $DB;
303
 
304
        $userid = $contextlist->get_user()->id;
305
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
306
            if ($context->contextlevel == CONTEXT_MODULE) {
307
                $carry[] = $context->instanceid;
308
            }
309
            return $carry;
310
        }, []);
311
        if (empty($cmids)) {
312
            return;
313
        }
314
 
315
        // Fetch the survey IDs.
316
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
317
        $sql = "
318
            SELECT s.id
319
              FROM {survey} s
320
              JOIN {modules} m
321
                ON m.name = :survey
322
              JOIN {course_modules} cm
323
                ON cm.instance = s.id
324
               AND cm.module = m.id
325
             WHERE cm.id $insql";
326
        $params = array_merge($inparams, ['survey' => 'survey']);
327
        $surveyids = $DB->get_fieldset_sql($sql, $params);
328
 
329
        // Delete all the things.
330
        list($insql, $inparams) = $DB->get_in_or_equal($surveyids, SQL_PARAMS_NAMED);
331
        $params = array_merge($inparams, ['userid' => $userid]);
332
        $DB->delete_records_select('survey_answers', "survey $insql AND userid = :userid", $params);
333
        $DB->delete_records_select('survey_analysis', "survey $insql AND userid = :userid", $params);
334
    }
335
 
336
    /**
337
     * Delete multiple users within a single context.
338
     *
339
     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
340
     */
341
    public static function delete_data_for_users(approved_userlist $userlist) {
342
        global $DB;
343
        $context = $userlist->get_context();
344
 
345
        if ($context->contextlevel != CONTEXT_MODULE) {
346
            return;
347
        }
348
 
349
        // Fetch the survey ID.
350
        $sql = "
351
            SELECT s.id
352
              FROM {survey} s
353
              JOIN {modules} m
354
                ON m.name = :survey
355
              JOIN {course_modules} cm
356
                ON cm.instance = s.id
357
               AND cm.module = m.id
358
             WHERE cm.id = :cmid";
359
        $params = [
360
            'survey' => 'survey',
361
            'cmid' => $context->instanceid,
362
            ];
363
        $surveyid = $DB->get_field_sql($sql, $params);
364
        $userids = $userlist->get_userids();
365
 
366
        // Delete all the things.
367
        list($insql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
368
        $params['surveyid'] = $surveyid;
369
 
370
        $DB->delete_records_select('survey_answers', "survey = :surveyid AND userid {$insql}", $params);
371
        $DB->delete_records_select('survey_analysis', "survey = :surveyid AND userid {$insql}", $params);
372
    }
373
 
374
    /**
375
     * Get a survey ID from its context.
376
     *
377
     * @param context_module $context The module context.
378
     * @return int
379
     */
380
    protected static function get_survey_id_from_context(context_module $context) {
381
        $cm = get_coursemodule_from_id('survey', $context->instanceid);
382
        return $cm ? (int) $cm->instance : 0;
383
    }
384
    /**
385
     * Loop and export from a recordset.
386
     *
387
     * @param moodle_recordset $recordset The recordset.
388
     * @param string $splitkey The record key to determine when to export.
389
     * @param mixed $initial The initial data to reduce from.
390
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
391
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
392
     * @return void
393
     */
394
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
395
            callable $reducer, callable $export) {
396
 
397
        $data = $initial;
398
        $lastid = null;
399
 
400
        foreach ($recordset as $record) {
401
            if ($lastid && $record->{$splitkey} != $lastid) {
402
                $export($lastid, $data);
403
                $data = $initial;
404
            }
405
            $data = $reducer($data, $record);
406
            $lastid = $record->{$splitkey};
407
        }
408
        $recordset->close();
409
 
410
        if (!empty($lastid)) {
411
            $export($lastid, $data);
412
        }
413
    }
414
}