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
 * Privacy Subsystem implementation for core_question.
19
 *
20
 * @package    core_question
21
 * @category   privacy
22
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace core_question\privacy;
27
 
28
use core_privacy\local\metadata\collection;
29
use core_privacy\local\request\approved_contextlist;
30
use core_privacy\local\request\approved_userlist;
31
use core_privacy\local\request\contextlist;
32
use core_privacy\local\request\transform;
33
use core_privacy\local\request\userlist;
34
use core_privacy\local\request\writer;
35
 
36
defined('MOODLE_INTERNAL') || die();
37
 
38
require_once($CFG->libdir . '/questionlib.php');
39
require_once($CFG->dirroot . '/question/format.php');
40
require_once($CFG->dirroot . '/question/editlib.php');
41
require_once($CFG->dirroot . '/question/engine/datalib.php');
42
 
43
/**
44
 * Privacy Subsystem implementation for core_question.
45
 *
46
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
47
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48
 */
49
class provider implements
50
    // This component has data.
51
    // We need to return all question information where the user is
52
    // listed in either the question.createdby or question.modifiedby fields.
53
    // We may also need to fetch this informtion from individual plugins in some cases.
54
    // e.g. to fetch the full and other question-specific meta-data.
55
    \core_privacy\local\metadata\provider,
56
 
57
    // This is a subsysytem which provides information to core.
58
    \core_privacy\local\request\subsystem\provider,
59
 
60
    // This is a subsysytem which provides information to plugins.
61
    \core_privacy\local\request\subsystem\plugin_provider,
62
 
63
    // This plugin is capable of determining which users have data within it.
64
    \core_privacy\local\request\core_userlist_provider,
65
 
66
    // This plugin is capable of determining which users have data within it for the plugins it provides data to.
67
    \core_privacy\local\request\shared_userlist_provider
68
{
69
 
70
    /**
71
     * Describe the types of data stored by the question subsystem.
72
     *
73
     * @param   collection  $items  The collection to add metadata to.
74
     * @return  collection  The array of metadata
75
     */
76
    public static function get_metadata(collection $items): collection {
77
        // Other tables link against it.
78
 
79
        // The 'question_usages' table does not contain any user data.
80
        // The table links the but doesn't store itself.
81
 
82
        // The 'question_attempts' table contains data about question attempts.
83
        // It does not contain any user ids - these are stored by the caller.
84
        $items->add_database_table('question_attempts', [
85
            'flagged'           => 'privacy:metadata:database:question_attempts:flagged',
86
            'responsesummary'   => 'privacy:metadata:database:question_attempts:responsesummary',
87
            'timemodified'      => 'privacy:metadata:database:question_attempts:timemodified',
88
        ], 'privacy:metadata:database:question_attempts');;
89
 
90
        // The 'question_attempt_steps' table contains data about changes to the state of a question attempt.
91
        $items->add_database_table('question_attempt_steps', [
92
            'state'             => 'privacy:metadata:database:question_attempt_steps:state',
93
            'timecreated'       => 'privacy:metadata:database:question_attempt_steps:timecreated',
94
            'fraction'          => 'privacy:metadata:database:question_attempt_steps:fraction',
95
            'userid'            => 'privacy:metadata:database:question_attempt_steps:userid',
96
        ], 'privacy:metadata:database:question_attempt_steps');
97
 
98
        // The 'question_attempt_step_data' table contains specific all metadata for each state.
99
        $items->add_database_table('question_attempt_step_data', [
100
            'name'              => 'privacy:metadata:database:question_attempt_step_data:name',
101
            'value'             => 'privacy:metadata:database:question_attempt_step_data:value',
102
        ], 'privacy:metadata:database:question_attempt_step_data');
103
 
104
        // These are all part of the set of the question definition
105
        // The 'question' table is used to store instances of each question.
106
        // It contains a createdby and modifiedby which related to specific users.
107
        $items->add_database_table('question', [
108
            'name'              => 'privacy:metadata:database:question:name',
109
            'questiontext'      => 'privacy:metadata:database:question:questiontext',
110
            'generalfeedback'   => 'privacy:metadata:database:question:generalfeedback',
111
            'timecreated'       => 'privacy:metadata:database:question:timecreated',
112
            'timemodified'      => 'privacy:metadata:database:question:timemodified',
113
            'createdby'         => 'privacy:metadata:database:question:createdby',
114
            'modifiedby'        => 'privacy:metadata:database:question:modifiedby',
115
        ], 'privacy:metadata:database:question');
116
 
117
        // The 'question_answers' table is used to store the set of answers, with appropriate feedback for each question.
118
        // It does not contain user data.
119
 
120
        // The 'question_hints' table is used to store hints about the correct answer for a question.
121
        // It does not contain user data.
122
 
123
        // The 'question_categories' table contains structural information about how questions are presented in the UI.
124
        // It does not contain user data.
125
 
126
        // The 'question_statistics' table contains aggregated statistics about responses.
127
        // It does not contain any identifiable user data.
128
 
129
        $items->add_database_table('question_bank_entries', [
130
            'ownerid' => 'privacy:metadata:database:question_bank_entries:ownerid',
131
        ], 'privacy:metadata:database:question_bank_entries');
132
 
133
        // The question subsystem makes use of the qtype, qformat, and qbehaviour plugin types.
134
        $items->add_plugintype_link('qtype', [], 'privacy:metadata:link:qtype');
135
        $items->add_plugintype_link('qformat', [], 'privacy:metadata:link:qformat');
136
        $items->add_plugintype_link('qbehaviour', [], 'privacy:metadata:link:qbehaviour');
137
 
138
        return $items;
139
    }
140
 
141
    /**
142
     * Export the data for all question attempts on this question usage.
143
     *
144
     * Where a user is the owner of the usage, then the full detail of that usage will be included.
145
     * Where a user has been involved in the usage, but it is not their own usage, then only their specific
146
     * involvement will be exported.
147
     *
148
     * @param   int             $userid     The userid to export.
149
     * @param   \context        $context    The context that the question was used within.
150
     * @param   array           $usagecontext  The subcontext of this usage.
151
     * @param   int             $usage      The question usage ID.
152
     * @param   \question_display_options   $options    The display options used for formatting.
153
     * @param   bool            $isowner    Whether the user being exported is the user who used the question.
154
     */
155
    public static function export_question_usage(
156
            int $userid,
157
            \context $context,
158
            array $usagecontext,
159
            int $usage,
160
            \question_display_options $options,
161
            bool $isowner
162
        ) {
163
        // Determine the questions in this usage.
164
        $quba = \question_engine::load_questions_usage_by_activity($usage);
165
 
166
        $basepath = $usagecontext;
167
        $questionscontext = array_merge($usagecontext, [
168
            get_string('questions', 'core_question'),
169
        ]);
170
 
171
        foreach ($quba->get_attempt_iterator() as $qa) {
172
            $question = $qa->get_question(false);
173
            $slotno = $qa->get_slot();
174
            $questionnocontext = array_merge($questionscontext, [$slotno]);
175
 
176
            if ($isowner) {
177
                // This user is the overal owner of the question attempt and all data wil therefore be exported.
178
                //
179
                // Respect _some_ of the question_display_options to ensure that they don't have access to
180
                // generalfeedback and mark if the display options prevent this.
181
                // This is defensible because they can submit questions without completing a quiz and perform an SAR to
182
                // get prior access to the feedback and mark to improve upon it.
183
                // Export the response.
184
                $data = (object) [
185
                    'name' => $question->name,
186
                    'question' => $qa->get_question_summary(),
187
                    'answer' => $qa->get_response_summary(),
188
                    'timemodified' => transform::datetime($qa->timemodified),
189
                ];
190
 
191
                if ($options->marks >= \question_display_options::MARK_AND_MAX) {
192
                    $data->mark = $qa->format_mark($options->markdp);
193
                }
194
 
195
                if ($options->flags != \question_display_options::HIDDEN) {
196
                    $data->flagged = transform::yesno($qa->is_flagged());
197
                }
198
 
199
                if ($options->generalfeedback != \question_display_options::HIDDEN) {
200
                    $data->generalfeedback = $question->format_generalfeedback($qa);
201
                }
202
 
203
                if ($options->manualcomment != \question_display_options::HIDDEN) {
204
                    if ($qa->has_manual_comment()) {
205
                        // Note - the export of the step data will ensure that the files are exported.
206
                        // No need to do it again here.
207
                        list($comment, $commentformat, $step) = $qa->get_manual_comment();
208
 
209
                        $comment = writer::with_context($context)
210
                            ->rewrite_pluginfile_urls(
211
                                $questionnocontext,
212
                                'question',
213
                                'response_bf_comment',
214
                                $step->get_id(),
215
                                $comment
216
                            );
217
                        $data->comment = $qa->get_behaviour(false)->format_comment($comment, $commentformat);
218
                    }
219
                }
220
 
221
                writer::with_context($context)
222
                    ->export_data($questionnocontext, $data);
223
 
224
                // Export the step data.
225
                static::export_question_attempt_steps($userid, $context, $questionnocontext, $qa, $options, $isowner);
226
            }
227
        }
228
    }
229
 
230
    /**
231
     * Export the data for each step transition for each question in each question attempt.
232
     *
233
     * Where a user is the owner of the usage, then all steps in the question usage will be exported.
234
     * Where a user is not the owner, but has been involved in the usage, then only their specific
235
     * involvement will be exported.
236
     *
237
     * @param   int                 $userid     The user to export for
238
     * @param   \context            $context    The context that the question was used within.
239
     * @param   array               $questionnocontext  The subcontext of this question number.
240
     * @param   \question_attempt   $qa         The attempt being checked
241
     * @param   \question_display_options   $options    The display options used for formatting.
242
     * @param   bool                $isowner    Whether the user being exported is the user who used the question.
243
     */
244
    public static function export_question_attempt_steps(
245
            int $userid,
246
            \context $context,
247
            array $questionnocontext,
248
            \question_attempt $qa,
249
            \question_display_options $options,
250
            $isowner
251
        ) {
252
        $attemptdata = (object) [
253
                'steps' => [],
254
            ];
255
        $stepno = 0;
256
        foreach ($qa->get_step_iterator() as $i => $step) {
257
            $stepno++;
258
 
259
            if ($isowner || ($step->get_user_id() != $userid)) {
260
                // The user is the owner, or the author of the step.
261
 
262
                $restrictedqa = new \question_attempt_with_restricted_history($qa, $i, null);
263
                $stepdata = (object) [
264
                    // Note: Do not include the user here.
265
                    'time' => transform::datetime($step->get_timecreated()),
266
                    'action' => $qa->summarise_action($step),
267
                ];
268
 
269
                if ($options->marks >= \question_display_options::MARK_AND_MAX) {
270
                    $stepdata->mark = $qa->format_fraction_as_mark($step->get_fraction(), $options->markdp);
271
                }
272
 
273
                if ($options->correctness != \question_display_options::HIDDEN) {
274
                    $stepdata->state = $restrictedqa->get_state_string($options->correctness);
275
                }
276
 
277
                if ($step->has_behaviour_var('comment')) {
278
                    $comment = $step->get_behaviour_var('comment');
279
                    $commentformat = $step->get_behaviour_var('commentformat');
280
 
281
                    if (empty(trim($comment))) {
282
                        // Skip empty comments.
283
                        continue;
284
                    }
285
 
286
                    // Format the comment.
287
                    $comment = writer::with_context($context)
288
                        ->rewrite_pluginfile_urls(
289
                            $questionnocontext,
290
                            'question',
291
                            'response_bf_comment',
292
                            $step->get_id(),
293
                            $comment
294
                        );
295
 
296
                    // Export any files associated with the comment files area.
297
                    writer::with_context($context)
298
                        ->export_area_files(
299
                            $questionnocontext,
300
                            'question',
301
                            "response_bf_comment",
302
                            $step->get_id()
303
                        );
304
 
305
                    $stepdata->comment = $qa->get_behaviour(false)->format_comment($comment, $commentformat);
306
                }
307
 
308
                // Export any response files associated with this step.
309
                foreach (\question_engine::get_all_response_file_areas() as $filearea) {
310
                    writer::with_context($context)
311
                        ->export_area_files(
312
                                $questionnocontext,
313
                                'question',
314
                                $filearea,
315
                                $step->get_id()
316
                            );
317
                }
318
 
319
                $attemptdata->steps[$stepno] = $stepdata;
320
            }
321
        }
322
 
323
        if (!empty($attemptdata->steps)) {
324
            writer::with_context($context)
325
                ->export_related_data($questionnocontext, 'steps', $attemptdata);
326
        }
327
    }
328
 
329
    /**
330
     * Get the list of contexts where the specified user has either created, or edited a question.
331
     *
332
     * To export usage of a question, please call {@link provider::export_question_usage()} from the module which
333
     * instantiated the usage of the question.
334
     *
335
     * @param   int             $userid The user to search.
336
     * @return  contextlist     $contextlist The contextlist containing the list of contexts used in this plugin.
337
     */
338
    public static function get_contexts_for_userid(int $userid): contextlist {
339
        $contextlist = new contextlist();
340
 
341
        // A user may have created or updated a question.
342
        // Questions are linked against a question category, which has a contextid field.
343
        $sql = "SELECT qc.contextid
344
                  FROM {question} q
345
                  JOIN {question_versions} qv ON qv.questionid = q.id
346
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
347
                  JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
348
                 WHERE q.createdby = :useridcreated
349
                       OR q.modifiedby = :useridmodified";
350
        $params = [
351
            'useridcreated' => $userid,
352
            'useridmodified' => $userid,
353
        ];
354
        $contextlist->add_from_sql($sql, $params);
355
 
356
        return $contextlist;
357
    }
358
 
359
    /**
360
     * Get the list of users who have data within a context.
361
     *
362
     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
363
     */
364
    public static function get_users_in_context(userlist $userlist) {
365
        $context = $userlist->get_context();
366
 
367
        // A user may have created or updated a question.
368
        // Questions are linked against a question category, which has a contextid field.
369
        $sql = "SELECT q.createdby, q.modifiedby
370
                  FROM {question} q
371
                  JOIN {question_versions} qv ON qv.questionid = q.id
372
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
373
                  JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
374
                 WHERE qc.contextid = :contextid";
375
 
376
        $params = [
377
            'contextid' => $context->id
378
        ];
379
 
380
        $userlist->add_from_sql('createdby', $sql, $params);
381
        $userlist->add_from_sql('modifiedby', $sql, $params);
382
    }
383
 
384
    /**
385
     * Determine related question usages for a user.
386
     *
387
     * @param   string          $prefix     A unique prefix to add to the table alias
388
     * @param   string          $component  The name of the component to fetch usages for.
389
     * @param   string          $joinfield  The SQL field name to use in the JOIN ON - e.g. q.usageid
390
     * @param   int             $userid     The user to search.
391
     * @return  \qubaid_join
392
     */
393
    public static function get_related_question_usages_for_user(string $prefix, string $component, string $joinfield, int $userid): \qubaid_join {
394
        return new \qubaid_join("
395
                JOIN {question_usages} {$prefix}_qu ON {$prefix}_qu.id = {$joinfield}
396
                 AND {$prefix}_qu.component = :{$prefix}_usagecomponent
397
                JOIN {question_attempts} {$prefix}_qa ON {$prefix}_qa.questionusageid = {$prefix}_qu.id
398
                JOIN {question_attempt_steps} {$prefix}_qas ON {$prefix}_qas.questionattemptid = {$prefix}_qa.id",
399
            "{$prefix}_qu.id",
400
            "{$prefix}_qas.userid = :{$prefix}_stepuserid",
401
            [
402
                "{$prefix}_stepuserid" => $userid,
403
                "{$prefix}_usagecomponent" => $component,
404
            ]);
405
    }
406
 
407
    /**
408
     * Add the list of users who have rated in the specified constraints.
409
     *
410
     * @param   userlist    $userlist   The userlist to add the users to.
411
     * @param   string      $prefix     A unique prefix to add to the table alias to avoid interference with your own sql.
412
     * @param   string      $insql      The SQL to use in a sub-select for the question_usages.id query.
413
     * @param   array       $params     The params required for the insql.
414
     * @param   int|null    $contextid  An optional context id, in case the $sql query is not already filtered by that.
415
     */
416
    public static function get_users_in_context_from_sql(userlist $userlist, string $prefix, string $insql, $params,
417
            int $contextid = null) {
418
 
419
        $sql = "SELECT {$prefix}_qas.userid
420
                  FROM {question_attempt_steps} {$prefix}_qas
421
                  JOIN {question_attempts} {$prefix}_qa ON {$prefix}_qas.questionattemptid = {$prefix}_qa.id
422
                  JOIN {question_usages} {$prefix}_qu ON {$prefix}_qa.questionusageid = {$prefix}_qu.id
423
                 WHERE {$prefix}_qu.id IN ({$insql})";
424
 
425
        if ($contextid) {
426
            $sql .= " AND {$prefix}_qu.contextid = :{$prefix}_contextid";
427
            $params["{$prefix}_contextid"] = $contextid;
428
        }
429
 
430
        $userlist->add_from_sql('userid', $sql, $params);
431
    }
432
 
433
    /**
434
     * Export all user data for the specified user, in the specified contexts.
435
     *
436
     * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
437
     */
438
    public static function export_user_data(approved_contextlist $contextlist) {
439
        global $CFG, $DB, $SITE;
440
        if (empty($contextlist)) {
441
            return;
442
        }
443
 
444
        // Use the Moodle XML Data format.
445
        // It is the only lossless format that we support.
446
        $format = "xml";
447
        require_once($CFG->dirroot . "/question/format/{$format}/format.php");
448
 
449
        // THe export system needs questions in a particular format.
450
        // The easiest way to fetch these is with get_questions_category() which takes the details of a question
451
        // category.
452
        // We fetch the root question category for each context and the get_questions_category function recurses to
453
        // After fetching them, we filter out any not created or modified by the requestor.
454
        $user = $contextlist->get_user();
455
        $userid = $user->id;
456
 
457
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
458
        $categories = $DB->get_records_select('question_categories', "contextid {$contextsql} AND parent = 0", $contextparams);
459
 
460
        $classname = "qformat_{$format}";
461
        foreach ($categories as $category) {
462
            $context = \context::instance_by_id($category->contextid);
463
 
464
            $questions = get_questions_category($category, true);
465
            $questions = array_filter($questions, function($question) use ($userid) {
466
                return ($question->createdby == $userid) || ($question->modifiedby == $userid);
467
            }, ARRAY_FILTER_USE_BOTH);
468
 
469
            if (empty($questions)) {
470
                continue;
471
            }
472
 
473
            $qformat = new $classname();
474
            $qformat->setQuestions($questions);
475
 
476
            $qformat->setContexts([$context]);
477
            $qformat->setContexttofile(true);
478
 
479
            // We do not know which course this belongs to, and it's not actually used except in error, so use Site.
480
            $qformat->setCourse($SITE);
481
            $content = '';
482
            if ($qformat->exportpreprocess()) {
483
                $content = $qformat->exportprocess(false);
484
            }
485
 
486
            $subcontext = [
487
                get_string('questionbank', 'core_question'),
488
            ];
489
            writer::with_context($context)->export_custom_file($subcontext, 'questions.xml', $content);
490
        }
491
    }
492
 
493
    /**
494
     * Delete all data for all users in the specified context.
495
     *
496
     * @param \context $context The specific context to delete data for.
497
     * @throws \dml_exception
498
     */
499
    public static function delete_data_for_all_users_in_context(\context $context) {
500
        global $DB;
501
 
502
        // Questions are considered to be 'owned' by the institution, even if they were originally written by a specific
503
        // user. They are still exported in the list of a users data, but they are not removed.
504
        // The userid is instead anonymised.
505
 
506
        $sql = 'SELECT q.*
507
                  FROM {question} q
508
                  JOIN {question_versions} qv ON qv.questionid = q.id
509
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
510
                  JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
511
                 WHERE qc.contextid = ?';
512
 
513
        $questions = $DB->get_records_sql($sql, [$context->id]);
514
        foreach ($questions as $question) {
515
            $question->createdby = 0;
516
            $question->modifiedby = 0;
517
            $DB->update_record('question', $question);
518
        }
519
    }
520
 
521
    /**
522
     * Delete all user data for the specified user, in the specified contexts.
523
     *
524
     * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
525
     */
526
    public static function delete_data_for_user(approved_contextlist $contextlist) {
527
        global $DB;
528
 
529
        // Questions are considered to be 'owned' by the institution, even if they were originally written by a specific
530
        // user. They are still exported in the list of a users data, but they are not removed.
531
        // The userid is instead anonymised.
532
 
533
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
534
        $contextparams['createdby'] = $contextlist->get_user()->id;
535
        $questiondata = $DB->get_records_sql(
536
            "SELECT q.*
537
               FROM {question} q
538
               JOIN {question_versions} qv ON qv.questionid = q.id
539
               JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
540
               JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
541
              WHERE qc.contextid {$contextsql}
542
                    AND q.createdby = :createdby", $contextparams);
543
 
544
        foreach ($questiondata as $question) {
545
            $question->createdby = 0;
546
            $DB->update_record('question', $question);
547
        }
548
 
549
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
550
        $contextparams['modifiedby'] = $contextlist->get_user()->id;
551
        $questiondata = $DB->get_records_sql(
552
            "SELECT q.*
553
               FROM {question} q
554
               JOIN {question_versions} qv ON qv.questionid = q.id
555
               JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
556
               JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
557
              WHERE qc.contextid {$contextsql}
558
                    AND q.modifiedby = :modifiedby", $contextparams);
559
 
560
        foreach ($questiondata as $question) {
561
            $question->modifiedby = 0;
562
            $DB->update_record('question', $question);
563
        }
564
 
565
    }
566
 
567
    /**
568
     * Delete multiple users within a single context.
569
     *
570
     * @param   approved_userlist   $userlist   The approved context and user information to delete information for.
571
     */
572
    public static function delete_data_for_users(approved_userlist $userlist) {
573
        global $DB;
574
 
575
        // Questions are considered to be 'owned' by the institution, even if they were originally written by a specific
576
        // user. They are still exported in the list of a users data, but they are not removed.
577
        // The userid is instead anonymised.
578
 
579
        $context = $userlist->get_context();
580
        $userids = $userlist->get_userids();
581
 
582
        list($createdbysql, $createdbyparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
583
        list($modifiedbysql, $modifiedbyparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
584
 
585
        $params = ['contextid' => $context->id];
586
 
587
        $questiondata = $DB->get_records_sql(
588
            "SELECT q.*
589
               FROM {question} q
590
               JOIN {question_versions} qv ON qv.questionid = q.id
591
               JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
592
               JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
593
              WHERE qc.contextid = :contextid
594
                    AND q.createdby {$createdbysql}", $params + $createdbyparams);
595
 
596
        foreach ($questiondata as $question) {
597
            $question->createdby = 0;
598
            $DB->update_record('question', $question);
599
        }
600
 
601
        $questiondata = $DB->get_records_sql(
602
            "SELECT q.*
603
               FROM {question} q
604
               JOIN {question_versions} qv ON qv.questionid = q.id
605
               JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
606
               JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
607
              WHERE qc.contextid = :contextid
608
                    AND q.modifiedby {$modifiedbysql}", $params + $modifiedbyparams);
609
 
610
        foreach ($questiondata as $question) {
611
            $question->modifiedby = 0;
612
            $DB->update_record('question', $question);
613
        }
614
    }
615
}