Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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
namespace core_enrol;
18
 
19
use core_enrol_external;
20
use core_external\external_api;
21
use enrol_user_enrolment_form;
22
use externallib_advanced_testcase;
23
use stdClass;
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
global $CFG;
28
 
29
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
30
require_once($CFG->dirroot . '/enrol/externallib.php');
31
 
32
/**
33
 * Enrol external PHPunit tests
34
 *
35
 * @package    core_enrol
36
 * @category   external
37
 * @copyright  2012 Jerome Mouneyrac
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 * @since Moodle 2.4
40
 */
41
class externallib_test extends externallib_advanced_testcase {
42
 
43
    /**
44
     * dataProvider for test_get_enrolled_users_visibility().
45
     */
46
    public function get_enrolled_users_visibility_provider() {
47
        return array(
48
            'Course without groups, default behavior (not filtering by cap, group, active)' =>
49
            array(
50
                'settings' => array(
51
                    'coursegroupmode' => NOGROUPS,
52
                    'withcapability' => null,
53
                    'groupid' => null,
54
                    'onlyactive' => false,
55
                    'allowedcaps' => array(),
56
                ),
57
                'results' => array( // Everybody can view everybody.
58
                    'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
59
                    'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
60
                    'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
61
                    'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
62
                    'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
63
                ),
64
            ),
65
 
66
            'Course with visible groups, default behavior (not filtering by cap, group, active)' =>
67
            array(
68
                'settings' => array(
69
                    'coursegroupmode' => VISIBLEGROUPS,
70
                    'withcapability' => null,
71
                    'groupid' => null,
72
                    'onlyactive' => false,
73
                    'allowedcaps' => array(),
74
                ),
75
                'results' => array( // Everybody can view everybody.
76
                    'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
77
                    'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
78
                    'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
79
                    'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
80
                    'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
81
                ),
82
            ),
83
 
84
            'Course with separate groups, default behavior (not filtering by cap, group, active)' =>
85
            array(
86
                'settings' => array(
87
                    'coursegroupmode' => SEPARATEGROUPS,
88
                    'withcapability' => null,
89
                    'groupid' => null,
90
                    'onlyactive' => false,
91
                    'allowedcaps' => array(),
92
                ),
93
                'results' => array( // Only users from own groups are visible.
94
                    'user0' => array('canview' => array()), // Poor guy, cannot see anybody, himself included.
95
                    'user1' => array('canview' => array('user1', 'userall')),
96
                    'user2' => array('canview' => array('user2', 'user2su', 'userall')),
97
                    'user31' => array('canview' => array('user31', 'user32', 'userall')),
98
                    'userall' => array('canview' => array('user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
99
                ),
100
            ),
101
 
102
            'Course with separate groups, default behavior (not filtering but having moodle/site:accessallgroups)' =>
103
            array(
104
                'settings' => array(
105
                    'coursegroupmode' => VISIBLEGROUPS,
106
                    'withcapability' => null,
107
                    'groupid' => null,
108
                    'onlyactive' => false,
109
                    'allowedcaps' => array('moodle/site:accessallgroups'),
110
                ),
111
                'results' => array( // Everybody can view everybody.
112
                    'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
113
                    'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
114
                    'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
115
                    'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
116
                    'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
117
                ),
118
            ),
119
 
120
            'Course with separate groups, filtering onlyactive (missing moodle/course:enrolreview)' =>
121
            array(
122
                'settings' => array(
123
                    'coursegroupmode' => SEPARATEGROUPS,
124
                    'withcapability' => null,
125
                    'groupid' => null,
126
                    'onlyactive' => true,
127
                    'allowedcaps' => array(),
128
                ),
129
                'results' => array( // returns exception, cannot view anybody without the cap.
130
                    'user2' => array('exception' => array(
131
                        'type' => 'required_capability_exception',
132
                        'message' => 'Review course enrolments')),
133
                    'userall' => array('exception' => array(
134
                        'type' => 'required_capability_exception',
135
                        'message' => 'Review course enrolments')),
136
                ),
137
            ),
138
 
139
            'Course with separate groups, filtering onlyactive (having moodle/course:enrolreview)' =>
140
            array(
141
                'settings' => array(
142
                    'coursegroupmode' => SEPARATEGROUPS,
143
                    'withcapability' => null,
144
                    'groupid' => null,
145
                    'onlyactive' => true,
146
                    'allowedcaps' => array('moodle/course:enrolreview'),
147
                ),
148
                'results' => array( // Suspended are not returned.
149
                    'user2' => array('canview' => array('user2', 'userall')),
150
                    'user31' => array('canview' => array('user31', 'user32', 'userall')),
151
                    'userall' => array('canview' => array('user1', 'user2', 'user31', 'user32', 'userall')),
152
                ),
153
            ),
154
 
155
            'Course with separate groups, filtering by groupid (not having moodle/site:accessallgroups)' =>
156
            array(
157
                'settings' => array(
158
                    'coursegroupmode' => SEPARATEGROUPS,
159
                    'withcapability' => null,
160
                    'groupid' => 'group2',
161
                    'onlyactive' => false,
162
                    'allowedcaps' => array(),
163
                ),
164
                'results' => array( // Only group 2 members and only for members. Exception for non-members.
165
                    'user0' => array('exception' => array(
166
                        'type' => 'required_capability_exception',
167
                        'message' => 'Access all groups')),
168
                    'user1' => array('exception' => array(
169
                        'type' => 'required_capability_exception',
170
                        'message' => 'Access all groups')),
171
                    'user2' => array('canview' => array('user2', 'user2su', 'userall')),
172
                    'userall' => array('canview' => array('user2', 'user2su', 'userall')),
173
                ),
174
            ),
175
 
176
            'Course with separate groups, filtering by groupid (having moodle/site:accessallgroups)' =>
177
            array(
178
                'settings' => array(
179
                    'coursegroupmode' => SEPARATEGROUPS,
180
                    'withcapability' => null,
181
                    'groupid' => 'group2',
182
                    'onlyactive' => false,
183
                    'allowedcaps' => array('moodle/site:accessallgroups'),
184
                ),
185
                'results' => array( // All users with 'moodle/site:accessallgroups' can view group 2
186
                    'user0' => array('canview' => array('user2', 'user2su', 'userall')),
187
                    'user1' => array('canview' => array('user2', 'user2su', 'userall')),
188
                    'user2' => array('canview' => array('user2', 'user2su', 'userall')),
189
                    'userall' => array('canview' => array('user2', 'user2su', 'userall')),
190
                ),
191
            ),
192
 
193
            'Course with separate groups, filtering by withcapability (not having moodle/role:review)' =>
194
            array(
195
                'settings' => array(
196
                    'coursegroupmode' => SEPARATEGROUPS,
197
                    'withcapability' => 'moodle/course:bulkmessaging',
198
                    'groupid' => null,
199
                    'onlyactive' => false,
200
                    'allowedcaps' => array(),
201
                ),
202
                'results' => array( // No user has 'moodle/role:review' so exception.
203
                    'user0' => array('exception' => array(
204
                        'type' => 'required_capability_exception',
205
                        'message' => 'Review permissions for others')),
206
                    'user1' => array('exception' => array(
207
                        'type' => 'required_capability_exception',
208
                        'message' => 'Review permissions for others')),
209
                    'user2' => array('exception' => array(
210
                        'type' => 'required_capability_exception',
211
                        'message' => 'Review permissions for others')),
212
                    'userall' => array('exception' => array(
213
                        'type' => 'required_capability_exception',
214
                        'message' => 'Review permissions for others')),
215
                ),
216
            ),
217
 
218
            'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
219
            array(
220
                'settings' => array(
221
                    'coursegroupmode' => SEPARATEGROUPS,
222
                    'withcapability' => 'moodle/course:bulkmessaging',
223
                    'groupid' => null,
224
                    'onlyactive' => false,
225
                    'allowedcaps' => array('moodle/role:review'),
226
                ),
227
                'results' => array( // No user has withcapability, but all have 'moodle/role:review'. Empties.
228
                    'user0' => array('canview' => array()),
229
                    'user1' => array('canview' => array()),
230
                    'user2' => array('canview' => array()),
231
                    'userall' => array('canview' => array()),
232
                ),
233
            ),
234
 
235
            'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
236
            array(
237
                'settings' => array(
238
                    'coursegroupmode' => SEPARATEGROUPS,
239
                    'withcapability' => 'moodle/course:bulkmessaging',
240
                    'groupid' => null,
241
                    'onlyactive' => false,
242
                    'allowedcaps' => array('moodle/role:review', 'moodle/course:bulkmessaging'),
243
                ),
244
                'results' => array( // Users (previous) have withcapability, and all have 'moodle/role:review'.
245
                    'user0' => array('canview' => array()),
246
                    'user1' => array('canview' => array('user1')),
247
                    'user2' => array('canview' => array('user2')),
248
                    'userall' => array('canview' => array('user1', 'user2', 'userall')),
249
                ),
250
            ),
251
        );
252
    }
253
 
254
    /**
255
     * Verify get_enrolled_users() returned users are the expected in every situation.
256
     *
257
     * @dataProvider get_enrolled_users_visibility_provider
258
     */
259
    public function test_get_enrolled_users_visibility($settings, $results) {
260
 
261
        global $USER;
262
 
263
        $this->resetAfterTest();
264
 
265
        // Create the course and the users.
266
        $course = $this->getDataGenerator()->create_course(array('groupmode' => $settings['coursegroupmode']));
267
        $coursecontext = \context_course::instance($course->id);
268
        $user0 = $this->getDataGenerator()->create_user(array('username' => 'user0'));     // A user without group.
269
        $user1 = $this->getDataGenerator()->create_user(array('username' => 'user1'));     // User for group 1.
270
        $user2 = $this->getDataGenerator()->create_user(array('username' => 'user2'));     // Two users for group 2.
271
        $user2su = $this->getDataGenerator()->create_user(array('username' => 'user2su')); // (one suspended).
272
        $user31 = $this->getDataGenerator()->create_user(array('username' => 'user31'));   // Two users for group 3.
273
        $user32 = $this->getDataGenerator()->create_user(array('username' => 'user32'));   // (both enabled).
274
        $userall = $this->getDataGenerator()->create_user(array('username' => 'userall')); // A user in all groups.
275
 
276
        // Create utility array of created users, to produce better assertion messages.
277
        $createdusers = array();
278
        foreach (array($user0, $user1, $user2, $user2su, $user31, $user32, $userall) as $createduser) {
279
            $createdusers[$createduser->id] = $createduser->username;
280
        }
281
 
282
        // Enrol the users in the course.
283
        $this->getDataGenerator()->enrol_user($user0->id, $course->id);
284
        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
285
        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
286
        $this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
287
        $this->getDataGenerator()->enrol_user($user31->id, $course->id);
288
        $this->getDataGenerator()->enrol_user($user32->id, $course->id);
289
        $this->getDataGenerator()->enrol_user($userall->id, $course->id);
290
 
291
        // Create 3 groups.
292
        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
293
        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
294
        $group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
295
 
296
        // Add the users to the groups.
297
        $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
298
        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
299
        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2su->id));
300
        $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user31->id));
301
        $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user32->id));
302
        $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $userall->id));
303
        $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id));
304
        $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id));
305
 
306
        // Create a role to add the allowedcaps. Users will have this role assigned.
307
        $roleid = $this->getDataGenerator()->create_role();
308
        // Allow the specified capabilities.
309
        if (!empty($settings['allowedcaps'])) {
310
            foreach ($settings['allowedcaps'] as $capability) {
311
                assign_capability($capability, CAP_ALLOW, $roleid, $coursecontext);
312
            }
313
        }
314
 
315
        // For each of the users, configure everything, perform the call, and assert results.
316
        foreach ($results as $user => $expectations) {
317
            // Convert canview expectations into a nice array of ids for easier handling.
318
            $canview = array();
319
            $exception = null;
320
            // Analyse the expectations.
321
            if (isset($expectations['canview'])) {
322
                foreach ($expectations['canview'] as $canviewuser) {
323
                    $canview[] = $createdusers[${$canviewuser}->id];
324
                }
325
            } else if (isset($expectations['exception'])) {
326
                $exception = $expectations['exception'];
327
                $this->expectException($exception['type']);
328
                $this->expectExceptionMessage($exception['message']);
329
            } else {
330
                // Failed, only canview and exception are supported.
331
                throw new \coding_exception('Incomplete, only canview and exception are supported');
332
            }
333
 
334
            // Switch to the user and assign the role.
335
            $this->setUser(${$user});
336
            role_assign($roleid, $USER->id, $coursecontext);
337
 
338
            // Convert groupid to proper id.
339
            $groupid = 0;
340
            if (isset($settings['groupid'])) {
341
                $groupid = ${$settings['groupid']}->id;
342
            }
343
 
344
            // Call to the function.
345
            $options = array(
346
                array('name' => 'withcapability', 'value' => $settings['withcapability']),
347
                array('name' => 'groupid', 'value' => $groupid),
348
                array('name' => 'onlyactive', 'value' => $settings['onlyactive']),
349
                array('name' => 'userfields', 'value' => 'id')
350
            );
351
            $enrolledusers = core_enrol_external::get_enrolled_users($course->id, $options);
352
 
353
            // We need to execute the return values cleaning process to simulate the web service server.
354
            $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
355
 
356
            // We are only interested in ids to check visibility.
357
            $viewed = array();
358
            // Verify the user canview the expected users.
359
            foreach ($enrolledusers as $enrolleduser) {
360
                $viewed[] = $createdusers[$enrolleduser['id']];
361
            }
362
            // Verify viewed matches canview expectation (using canonicalize to ignore ordering).
363
            $this->assertEqualsCanonicalizing($canview, $viewed, "Problem checking visible users for '{$createdusers[$USER->id]}'");
364
        }
365
    }
366
 
367
    /**
368
     * Verify get_enrolled_users() returned users according to their status.
369
     */
370
    public function test_get_enrolled_users_active_suspended() {
371
        global $USER;
372
 
373
        $this->resetAfterTest();
374
 
375
        // Create the course and the users.
376
        $course = $this->getDataGenerator()->create_course();
377
        $coursecontext = \context_course::instance($course->id);
378
        $user0 = $this->getDataGenerator()->create_user(['username' => 'user0active']);
379
        $user1 = $this->getDataGenerator()->create_user(['username' => 'user1active']);
380
        $user2 = $this->getDataGenerator()->create_user(['username' => 'user2active']);
381
        $user2su = $this->getDataGenerator()->create_user(['username' => 'user2suspended']); // Suspended user.
382
        $user3 = $this->getDataGenerator()->create_user(['username' => 'user3active']);
383
        $user3su = $this->getDataGenerator()->create_user(['username' => 'user3suspended']); // Suspended user.
384
 
385
        // Enrol the users in the course.
386
        $this->getDataGenerator()->enrol_user($user0->id, $course->id);
387
        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
388
        $this->getDataGenerator()->enrol_user($user2->id, $course->id);
389
        $this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
390
        $this->getDataGenerator()->enrol_user($user3->id, $course->id);
391
        $this->getDataGenerator()->enrol_user($user3su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
392
 
393
        // Create a role to add the allowedcaps. Users will have this role assigned.
394
        $roleid = $this->getDataGenerator()->create_role();
395
        // Allow the specified capabilities.
396
        assign_capability('moodle/course:enrolreview', CAP_ALLOW, $roleid, $coursecontext);
397
        assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $roleid, $coursecontext);
398
 
399
        // Switch to the user and assign the role.
400
        $this->setUser($user0);
401
        role_assign($roleid, $USER->id, $coursecontext);
402
 
403
        // Suspended users.
404
        $options = [
405
            ['name' => 'onlysuspended', 'value' => true],
406
            ['name' => 'userfields', 'value' => 'id,username']
407
        ];
408
        $suspendedusers = core_enrol_external::get_enrolled_users($course->id, $options);
409
 
410
        // We need to execute the return values cleaning process to simulate the web service server.
411
        $suspendedusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $suspendedusers);
412
        $this->assertCount(2, $suspendedusers);
413
 
414
        foreach ($suspendedusers as $suspendeduser) {
415
            $this->assertStringContainsString('suspended', $suspendeduser['username']);
416
        }
417
 
418
        // Active users.
419
        $options = [
420
            ['name' => 'onlyactive', 'value' => true],
421
            ['name' => 'userfields', 'value' => 'id,username']
422
        ];
423
        $activeusers = core_enrol_external::get_enrolled_users($course->id, $options);
424
 
425
        // We need to execute the return values cleaning process to simulate the web service server.
426
        $activeusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $activeusers);
427
        $this->assertCount(4, $activeusers);
428
 
429
        foreach ($activeusers as $activeuser) {
430
            $this->assertStringContainsString('active', $activeuser['username']);
431
        }
432
 
433
        // All enrolled users.
434
        $options = [
435
            ['name' => 'userfields', 'value' => 'id,username']
436
        ];
437
        $allusers = core_enrol_external::get_enrolled_users($course->id, $options);
438
 
439
        // We need to execute the return values cleaning process to simulate the web service server.
440
        $allusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $allusers);
441
        $this->assertCount(6, $allusers);
442
 
443
        // Active and suspended. Test exception is thrown.
444
        $options = [
445
            ['name' => 'onlyactive', 'value' => true],
446
            ['name' => 'onlysuspended', 'value' => true],
447
            ['name' => 'userfields', 'value' => 'id,username']
448
        ];
449
        $this->expectException('coding_exception');
450
        $message = 'Coding error detected, it must be fixed by a programmer: Both onlyactive ' .
451
                        'and onlysuspended are set, this is probably not what you want!';
452
        $this->expectExceptionMessage($message);
453
        core_enrol_external::get_enrolled_users($course->id, $options);
454
    }
455
 
456
    /**
457
     * Test get_users_courses
458
     */
459
    public function test_get_users_courses() {
460
        global $CFG, $DB;
461
        require_once($CFG->dirroot . '/completion/criteria/completion_criteria_self.php');
462
 
463
        $this->resetAfterTest(true);
464
        $CFG->enablecompletion = 1;
465
 
466
        $timenow = time();
467
        $coursedata1 = array(
468
            // Adding tags here to check that \core_external\util::format_string works.
469
            'fullname'         => '<b>Course 1</b>',
470
            // Adding tags here to check that \core_external\util::format_string works.
471
            'shortname'         => '<b>Course 1</b>',
472
            'summary'          => 'Lightwork Course 1 description',
473
            'summaryformat'    => FORMAT_MOODLE,
474
            'lang'             => 'en',
475
            'enablecompletion' => true,
476
            'showgrades'       => true,
477
            'startdate'        => $timenow,
478
            'enddate'          => $timenow + WEEKSECS,
479
            'marker'           => 1
480
        );
481
 
482
        $coursedata2 = array(
483
            'lang'             => 'kk', // Check invalid language pack.
484
        );
485
 
486
        $course1 = self::getDataGenerator()->create_course($coursedata1);
487
        $course2 = self::getDataGenerator()->create_course($coursedata2);
488
        $courses = array($course1, $course2);
489
        $contexts = array ($course1->id => \context_course::instance($course1->id),
490
            $course2->id => \context_course::instance($course2->id));
491
 
492
        $student = $this->getDataGenerator()->create_user();
493
        $otherstudent = $this->getDataGenerator()->create_user();
494
        $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
495
        $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentroleid);
496
        $this->getDataGenerator()->enrol_user($otherstudent->id, $course1->id, $studentroleid);
497
        $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentroleid);
498
 
499
        // Force last access.
500
        $timenow = time();
501
        $lastaccess = array(
502
            'userid' => $student->id,
503
            'courseid' => $course1->id,
504
            'timeaccess' => $timenow
505
        );
506
        $DB->insert_record('user_lastaccess', $lastaccess);
507
 
508
        // Force completion, setting at least one criteria.
509
        require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
510
        $criteriadata = new \stdClass();
511
        $criteriadata->id = $course1->id;
512
        // Self completion.
513
        $criteriadata->criteria_self = 1;
514
 
515
        $criterion = new \completion_criteria_self();
516
        $criterion->update_config($criteriadata);
517
 
518
        $ccompletion = new \completion_completion(array('course' => $course1->id, 'userid' => $student->id));
519
        $ccompletion->mark_complete();
520
 
521
        // Set course hidden and favourited.
522
        set_user_preference('block_myoverview_hidden_course_' . $course1->id, 1, $student);
523
        $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($student->id));
524
        $ufservice->create_favourite('core_course', 'courses', $course1->id, \context_system::instance());
525
 
526
        $this->setUser($student);
527
        // Call the external function.
528
        $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
529
 
530
        // We need to execute the return values cleaning process to simulate the web service server.
531
        $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
532
 
533
        // Check we retrieve the good total number of enrolled users.
534
        $this->assertEquals(2, count($enrolledincourses));
535
 
536
        // We need to format summary and summaryformat before to compare them with those values returned by the webservice.
537
        [$course1->summary, $course1->summaryformat] = \core_external\util::format_text(
538
            $course1->summary,
539
            $course1->summaryformat,
540
            $contexts[$course1->id],
541
            'course',
542
            'summary',
543
 
544
        );
545
 
546
        // Check there are no differences between $course1 properties and course values returned by the webservice
547
        // only for those fields listed in the $coursedata1 array.
548
        $course1->fullname = \core_external\util::format_string($course1->fullname, $contexts[$course1->id]->id);
549
        $course1->shortname = \core_external\util::format_string($course1->shortname, $contexts[$course1->id]->id);
550
        foreach ($enrolledincourses as $courseenrol) {
551
            if ($courseenrol['id'] == $course1->id) {
552
                foreach ($coursedata1 as $fieldname => $value) {
553
                    $this->assertEquals($courseenrol[$fieldname], $course1->$fieldname);
554
                }
555
                // Text extra fields.
556
                $this->assertEquals($course1->fullname, $courseenrol['displayname']);
557
                $this->assertEquals([], $courseenrol['overviewfiles']);
558
                $this->assertEquals($timenow, $courseenrol['lastaccess']);
559
                $this->assertEquals(100.0, $courseenrol['progress']);
560
                $this->assertEquals(true, $courseenrol['completed']);
561
                $this->assertTrue($courseenrol['completionhascriteria']);
562
                $this->assertTrue($courseenrol['completionusertracked']);
563
                $this->assertTrue($courseenrol['hidden']);
564
                $this->assertTrue($courseenrol['isfavourite']);
565
                $this->assertEquals(2, $courseenrol['enrolledusercount']);
566
                $this->assertEquals($course1->timemodified, $courseenrol['timemodified']);
567
                $url = "https://www.example.com/moodle/pluginfile.php/{$contexts[$course1->id]->id}/course/generated/course.svg";
568
                $this->assertEquals($url, $courseenrol['courseimage']);
569
            } else {
570
                // Check language pack. Should be empty since an incorrect one was used when creating the course.
571
                $this->assertEmpty($courseenrol['lang']);
572
                $this->assertEquals($course2->fullname, $courseenrol['displayname']);
573
                $this->assertEquals([], $courseenrol['overviewfiles']);
574
                $this->assertEquals(0, $courseenrol['lastaccess']);
575
                $this->assertEquals(0, $courseenrol['progress']);
576
                $this->assertEquals(false, $courseenrol['completed']);
577
                $this->assertFalse($courseenrol['completionhascriteria']);
578
                $this->assertFalse($courseenrol['completionusertracked']);
579
                $this->assertFalse($courseenrol['hidden']);
580
                $this->assertFalse($courseenrol['isfavourite']);
581
                $this->assertEquals(1, $courseenrol['enrolledusercount']);
582
                $this->assertEquals($course2->timemodified, $courseenrol['timemodified']);
583
                $url = "https://www.example.com/moodle/pluginfile.php/{$contexts[$course2->id]->id}/course/generated/course.svg";
584
                $this->assertEquals($url, $courseenrol['courseimage']);
585
            }
586
        }
587
 
588
        // Check that returnusercount works correctly.
589
        $enrolledincourses = core_enrol_external::get_users_courses($student->id, false);
590
        $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
591
        foreach ($enrolledincourses as $courseenrol) {
592
            $this->assertFalse(isset($courseenrol['enrolledusercount']));
593
        }
594
 
595
        // Now check that admin users can see all the info.
596
        $this->setAdminUser();
597
 
598
        $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
599
        $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
600
        $this->assertEquals(2, count($enrolledincourses));
601
        foreach ($enrolledincourses as $courseenrol) {
602
            if ($courseenrol['id'] == $course1->id) {
603
                $this->assertEquals($timenow, $courseenrol['lastaccess']);
604
                $this->assertEquals(100.0, $courseenrol['progress']);
605
                $this->assertTrue($courseenrol['completionhascriteria']);
606
                $this->assertTrue($courseenrol['completionusertracked']);
607
                $this->assertFalse($courseenrol['isfavourite']);    // This always false.
608
                $this->assertFalse($courseenrol['hidden']); // This always false.
609
            } else {
610
                $this->assertEquals(0, $courseenrol['progress']);
611
                $this->assertFalse($courseenrol['completionhascriteria']);
612
                $this->assertFalse($courseenrol['completionusertracked']);
613
                $this->assertFalse($courseenrol['isfavourite']);    // This always false.
614
                $this->assertFalse($courseenrol['hidden']); // This always false.
615
            }
616
        }
617
 
618
        // Check other users can't see private info.
619
        $this->setUser($otherstudent);
620
 
621
        $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
622
        $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
623
        $this->assertEquals(1, count($enrolledincourses));
624
 
625
        $this->assertEquals($timenow, $enrolledincourses[0]['lastaccess']); // I can see this, not hidden.
626
        $this->assertEquals(null, $enrolledincourses[0]['progress']);   // I can't see this, private.
627
 
628
        // Change some global profile visibility fields.
629
        $CFG->hiddenuserfields = 'lastaccess';
630
        $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
631
        $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
632
 
633
        $this->assertEquals(0, $enrolledincourses[0]['lastaccess']); // I can't see this, hidden by global setting.
634
    }
635
 
636
    /**
637
     * Test that get_users_courses respects the capability to view participants when viewing courses of other user
638
     */
639
    public function test_get_users_courses_can_view_participants(): void {
640
        global $DB;
641
 
642
        $this->resetAfterTest();
643
 
644
        $course = $this->getDataGenerator()->create_course();
645
        $context = \context_course::instance($course->id);
646
 
647
        $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
648
        $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
649
 
650
        $this->setUser($user1);
651
 
652
        $courses = core_enrol_external::clean_returnvalue(
653
            core_enrol_external::get_users_courses_returns(),
654
            core_enrol_external::get_users_courses($user2->id, false)
655
        );
656
 
657
        $this->assertCount(1, $courses);
658
        $this->assertEquals($course->id, reset($courses)['id']);
659
 
660
        // Prohibit the capability for viewing course participants.
661
        $studentrole = $DB->get_field('role', 'id', ['shortname' => 'student']);
662
        assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $studentrole, $context->id);
663
 
664
        $courses = core_enrol_external::clean_returnvalue(
665
            core_enrol_external::get_users_courses_returns(),
666
            core_enrol_external::get_users_courses($user2->id, false)
667
        );
668
        $this->assertEmpty($courses);
669
    }
670
 
671
    /*
672
     * Test that get_users_courses respects the capability to view a users profile when viewing courses of other user
673
     */
674
    public function test_get_users_courses_can_view_profile(): void {
675
        $this->resetAfterTest();
676
 
677
        $course = $this->getDataGenerator()->create_course([
678
            'groupmode' => VISIBLEGROUPS,
679
        ]);
680
 
681
        $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
682
        $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
683
 
684
        // Create separate groups for each of our students.
685
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
686
        groups_add_member($group1, $user1);
687
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
688
        groups_add_member($group2, $user2);
689
 
690
        $this->setUser($user1);
691
 
692
        $courses = core_enrol_external::clean_returnvalue(
693
            core_enrol_external::get_users_courses_returns(),
694
            core_enrol_external::get_users_courses($user2->id, false)
695
        );
696
 
697
        $this->assertCount(1, $courses);
698
        $this->assertEquals($course->id, reset($courses)['id']);
699
 
700
        // Change to separate groups mode, so students can't view information about each other in different groups.
701
        $course->groupmode = SEPARATEGROUPS;
702
        update_course($course);
703
 
704
        $courses = core_enrol_external::clean_returnvalue(
705
            core_enrol_external::get_users_courses_returns(),
706
            core_enrol_external::get_users_courses($user2->id, false)
707
        );
708
        $this->assertEmpty($courses);
709
    }
710
 
711
    /**
712
     * Test get_users_courses with mathjax in the name.
713
     */
714
    public function test_get_users_courses_with_mathjax() {
715
        global $DB;
716
 
717
        $this->resetAfterTest(true);
718
 
719
        // Enable MathJax filter in content and headings.
720
        $this->configure_filters([
721
            ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
722
        ]);
723
 
724
        // Create a course with MathJax in the name and summary.
725
        $coursedata = [
726
            'fullname'         => 'Course 1 $$(a+b)=2$$',
727
            'shortname'         => 'Course 1 $$(a+b)=2$$',
728
            'summary'          => 'Lightwork Course 1 description $$(a+b)=2$$',
729
            'summaryformat'    => FORMAT_HTML,
730
        ];
731
 
732
        $course = self::getDataGenerator()->create_course($coursedata);
733
        $context = \context_course::instance($course->id);
734
 
735
        // Enrol a student in the course.
736
        $student = $this->getDataGenerator()->create_user();
737
        $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student']);
738
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentroleid);
739
 
740
        $this->setUser($student);
741
 
742
        // Call the external function.
743
        $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
744
 
745
        // We need to execute the return values cleaning process to simulate the web service server.
746
        $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
747
 
748
        // Check that the amount of courses is the right one.
749
        $this->assertCount(1, $enrolledincourses);
750
 
751
        // Filter the values to compare them with the returned ones.
752
        $course->fullname = \core_external\util::format_string($course->fullname, $context->id);
753
        $course->shortname = \core_external\util::format_string($course->shortname, $context->id);
754
        [$course->summary, $course->summaryformat] = \core_external\util::format_text(
755
            $course->summary,
756
            $course->summaryformat,
757
            $context,
758
            'course',
759
            'summary',
760
 
761
        );
762
 
763
        // Compare the values.
764
        $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $enrolledincourses[0]['fullname']);
765
        $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $enrolledincourses[0]['shortname']);
766
        $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $enrolledincourses[0]['summary']);
767
        $this->assertEquals($course->fullname, $enrolledincourses[0]['fullname']);
768
        $this->assertEquals($course->shortname, $enrolledincourses[0]['shortname']);
769
        $this->assertEquals($course->summary, $enrolledincourses[0]['summary']);
770
    }
771
 
772
    /**
773
     * Test get_course_enrolment_methods
774
     */
775
    public function test_get_course_enrolment_methods() {
776
        global $DB;
777
 
778
        $this->resetAfterTest(true);
779
 
780
        // Get enrolment plugins.
781
        $selfplugin = enrol_get_plugin('self');
782
        $this->assertNotEmpty($selfplugin);
783
        $manualplugin = enrol_get_plugin('manual');
784
        $this->assertNotEmpty($manualplugin);
785
 
786
        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
787
        $this->assertNotEmpty($studentrole);
788
 
789
        $course1 = self::getDataGenerator()->create_course();
790
        $coursedata = new \stdClass();
791
        $coursedata->visible = 0;
792
        $course2 = self::getDataGenerator()->create_course($coursedata);
793
 
794
        // Add enrolment methods for course.
795
        $instanceid1 = $selfplugin->add_instance($course1, array('status' => ENROL_INSTANCE_ENABLED,
796
                                                                'name' => 'Test instance 1',
797
                                                                'customint6' => 1,
798
                                                                'roleid' => $studentrole->id));
799
        $instanceid2 = $selfplugin->add_instance($course1, array('status' => ENROL_INSTANCE_DISABLED,
800
                                                                'name' => 'Test instance 2',
801
                                                                'roleid' => $studentrole->id));
802
 
803
        $instanceid3 = $manualplugin->add_instance($course1, array('status' => ENROL_INSTANCE_ENABLED,
804
                                                                'name' => 'Test instance 3'));
805
 
806
        $enrolmentmethods = $DB->get_records('enrol', array('courseid' => $course1->id, 'status' => ENROL_INSTANCE_ENABLED));
807
        $this->assertCount(2, $enrolmentmethods);
808
 
809
        $this->setAdminUser();
810
 
811
        // Check if information is returned.
812
        $enrolmentmethods = core_enrol_external::get_course_enrolment_methods($course1->id);
813
        $enrolmentmethods = external_api::clean_returnvalue(core_enrol_external::get_course_enrolment_methods_returns(),
814
                                                            $enrolmentmethods);
815
        // Enrolment information is currently returned by self enrolment plugin, so count == 1.
816
        // This should be changed as we implement get_enrol_info() for other enrolment plugins.
817
        $this->assertCount(1, $enrolmentmethods);
818
 
819
        $enrolmentmethod = $enrolmentmethods[0];
820
        $this->assertEquals($course1->id, $enrolmentmethod['courseid']);
821
        $this->assertEquals('self', $enrolmentmethod['type']);
822
        $this->assertTrue($enrolmentmethod['status']);
823
        $this->assertFalse(isset($enrolmentmethod['wsfunction']));
824
 
825
        $instanceid4 = $selfplugin->add_instance($course2, array('status' => ENROL_INSTANCE_ENABLED,
826
                                                                'name' => 'Test instance 4',
827
                                                                'roleid' => $studentrole->id,
828
                                                                'customint6' => 1,
829
                                                                'password' => 'test'));
830
        $enrolmentmethods = core_enrol_external::get_course_enrolment_methods($course2->id);
831
        $enrolmentmethods = external_api::clean_returnvalue(core_enrol_external::get_course_enrolment_methods_returns(),
832
                                                            $enrolmentmethods);
833
        $this->assertCount(1, $enrolmentmethods);
834
 
835
        $enrolmentmethod = $enrolmentmethods[0];
836
        $this->assertEquals($course2->id, $enrolmentmethod['courseid']);
837
        $this->assertEquals('self', $enrolmentmethod['type']);
838
        $this->assertTrue($enrolmentmethod['status']);
839
        $this->assertEquals('enrol_self_get_instance_info', $enrolmentmethod['wsfunction']);
840
 
841
        // Try to retrieve information using a normal user for a hidden course.
842
        $user = self::getDataGenerator()->create_user();
843
        $this->setUser($user);
844
        try {
845
            core_enrol_external::get_course_enrolment_methods($course2->id);
846
        } catch (\moodle_exception $e) {
847
            $this->assertEquals('coursehidden', $e->errorcode);
848
        }
849
    }
850
 
851
    public function get_enrolled_users_setup($capability) {
852
        global $USER;
853
 
854
        $this->resetAfterTest(true);
855
 
856
        $return = new \stdClass();
857
 
858
        $return->course = self::getDataGenerator()->create_course();
859
        $return->user1 = self::getDataGenerator()->create_user();
860
        $return->user2 = self::getDataGenerator()->create_user();
861
        $return->user3 = self::getDataGenerator()->create_user();
862
        $this->setUser($return->user3);
863
 
864
        // Set the required capabilities by the external function.
865
        $return->context = \context_course::instance($return->course->id);
866
        $return->roleid = $this->assignUserCapability($capability, $return->context->id);
867
        $this->assignUserCapability('moodle/user:viewdetails', $return->context->id, $return->roleid);
868
 
869
        // Enrol the users in the course.
870
        $this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
871
        $this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
872
        $this->getDataGenerator()->enrol_user($return->user3->id, $return->course->id, $return->roleid, 'manual');
873
 
874
        return $return;
875
    }
876
 
877
    /**
878
     * Test get_enrolled_users from core_enrol_external without additional
879
     * parameters.
880
     */
881
    public function test_get_enrolled_users_without_parameters() {
882
        $capability = 'moodle/course:viewparticipants';
883
        $data = $this->get_enrolled_users_setup($capability);
884
 
885
        // Call the external function.
886
        $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
887
 
888
        // We need to execute the return values cleaning process to simulate the web service server.
889
        $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
890
 
891
        // Check the result set.
892
        $this->assertEquals(3, count($enrolledusers));
893
        $this->assertArrayHasKey('email', $enrolledusers[0]);
894
    }
895
 
896
    /**
897
     * Test get_enrolled_users from core_enrol_external with some parameters set.
898
     */
899
    public function test_get_enrolled_users_with_parameters() {
900
        $capability = 'moodle/course:viewparticipants';
901
        $data = $this->get_enrolled_users_setup($capability);
902
 
903
        // Call the function with some parameters set.
904
        $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id, array(
905
            array('name' => 'limitfrom', 'value' => 2),
906
            array('name' => 'limitnumber', 'value' => 1),
907
            array('name' => 'userfields', 'value' => 'id')
908
        ));
909
 
910
        // We need to execute the return values cleaning process to simulate the web service server.
911
        $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
912
 
913
        // Check the result set, we should only get the 3rd result, which is $user3.
914
        $this->assertCount(1, $enrolledusers);
915
        $this->assertEquals($data->user3->id, $enrolledusers[0]['id']);
916
        $this->assertArrayHasKey('id', $enrolledusers[0]);
917
        $this->assertArrayNotHasKey('email', $enrolledusers[0]);
918
    }
919
 
920
 
921
    /**
922
     * Test get_enrolled_users last course access.
923
     */
924
    public function test_get_enrolled_users_including_lastcourseaccess() {
925
        global $DB;
926
        $capability = 'moodle/course:viewparticipants';
927
        $data = $this->get_enrolled_users_setup($capability);
928
 
929
        // Call the external function.
930
        $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
931
        // We need to execute the return values cleaning process to simulate the web service server.
932
        $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
933
 
934
        // Check the result set.
935
        $this->assertEquals(3, count($enrolledusers));
936
        $this->assertArrayHasKey('email', $enrolledusers[0]);
937
        $this->assertEquals(0, $enrolledusers[0]['lastcourseaccess']);
938
        $this->assertEquals(0, $enrolledusers[1]['lastcourseaccess']);
939
        $this->assertNotEquals(0, $enrolledusers[2]['lastcourseaccess']);   // We forced an access to the course via setUser.
940
 
941
        // Force last access.
942
        $timenow = time();
943
        $lastaccess = array(
944
            'userid' => $enrolledusers[0]['id'],
945
            'courseid' => $data->course->id,
946
            'timeaccess' => $timenow
947
        );
948
        $DB->insert_record('user_lastaccess', $lastaccess);
949
 
950
        $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
951
        $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
952
 
953
        // Check the result set.
954
        $this->assertEquals(3, count($enrolledusers));
955
        $this->assertEquals($timenow, $enrolledusers[0]['lastcourseaccess']);
956
        $this->assertEquals(0, $enrolledusers[1]['lastcourseaccess']);
957
        $this->assertNotEquals(0, $enrolledusers[2]['lastcourseaccess']);
958
    }
959
 
960
    /**
961
     * Test get_enrolled_users from core_enrol_external with capability to
962
     * viewparticipants removed.
963
     */
964
    public function test_get_enrolled_users_without_capability() {
965
        $capability = 'moodle/course:viewparticipants';
966
        $data = $this->get_enrolled_users_setup($capability);
967
 
968
        // Call without required capability.
969
        $this->unassignUserCapability($capability, $data->context->id, $data->roleid);
970
        $this->expectException(\moodle_exception::class);
971
        $categories = core_enrol_external::get_enrolled_users($data->course->id);
972
    }
973
 
974
    public function get_enrolled_users_with_capability_setup($capability) {
975
        global $USER, $DB;
976
 
977
        $this->resetAfterTest(true);
978
 
979
        $return = new \stdClass();
980
 
981
        // Create the course and fetch its context.
982
        $return->course = self::getDataGenerator()->create_course();
983
        $context = \context_course::instance($return->course->id);
984
 
985
        // Create one teacher, and two students.
986
        $return->teacher = self::getDataGenerator()->create_user();
987
        $return->student1 = self::getDataGenerator()->create_user();
988
        $return->student2 = self::getDataGenerator()->create_user();
989
 
990
        // Create a new student role based on the student archetype but with the capability prohibitted.
991
        $fakestudentroleid = create_role('Fake student role', 'fakestudent', 'Fake student role', 'student');
992
        assign_capability($capability, CAP_PROHIBIT, $fakestudentroleid, $context->id);
993
 
994
        // Enrol all of the users in the course.
995
        // * 'teacher'  is an editing teacher.
996
        // * 'student1' is a standard student.
997
        // * 'student2' is a student with the capability prohibitted.
998
        $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
999
        $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
1000
        $this->getDataGenerator()->enrol_user($return->teacher->id, $return->course->id, $editingteacherroleid);
1001
        $this->getDataGenerator()->enrol_user($return->student1->id, $return->course->id, $studentroleid);
1002
        $this->getDataGenerator()->enrol_user($return->student2->id, $return->course->id, $fakestudentroleid);
1003
 
1004
        // Log in as the teacher.
1005
        $this->setUser($return->teacher);
1006
 
1007
        // Clear caches.
1008
        accesslib_clear_all_caches_for_unit_testing();
1009
 
1010
        return $return;
1011
    }
1012
 
1013
    /**
1014
     * Test get_enrolled_users_with_capability without additional paramaters.
1015
     */
1016
    public function test_get_enrolled_users_with_capability_without_parameters() {
1017
        $capability = 'moodle/course:viewparticipants';
1018
        $data = $this->get_enrolled_users_with_capability_setup($capability);
1019
 
1020
        $result = core_enrol_external::get_enrolled_users_with_capability(
1021
            array(
1022
                'coursecapabilities' => array(
1023
                    'courseid' => $data->course->id,
1024
                    'capabilities' => array(
1025
                        $capability,
1026
                    ),
1027
                ),
1028
            ),
1029
            array()
1030
        );
1031
 
1032
        // We need to execute the return values cleaning process to simulate the web service server.
1033
        $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
1034
 
1035
        // Check an array containing the expected user for the course capability is returned.
1036
        $expecteduserlist = $result[0];
1037
        $this->assertEquals($data->course->id, $expecteduserlist['courseid']);
1038
        $this->assertEquals($capability, $expecteduserlist['capability']);
1039
        $this->assertEquals(2, count($expecteduserlist['users']));
1040
    }
1041
 
1042
    /**
1043
     * Test get_enrolled_users_with_capability
1044
     */
1045
    public function test_get_enrolled_users_with_capability_with_parameters() {
1046
        $capability = 'moodle/course:viewparticipants';
1047
        $data = $this->get_enrolled_users_with_capability_setup($capability);
1048
 
1049
        $result = core_enrol_external::get_enrolled_users_with_capability(
1050
            array(
1051
                'coursecapabilities' => array(
1052
                    'courseid' => $data->course->id,
1053
                    'capabilities' => array(
1054
                        $capability,
1055
                    ),
1056
                ),
1057
            ),
1058
            array(
1059
                array('name' => 'limitfrom', 'value' => 1),
1060
                array('name' => 'limitnumber', 'value' => 1),
1061
                array('name' => 'userfields', 'value' => 'id')
1062
            )
1063
        );
1064
 
1065
        // We need to execute the return values cleaning process to simulate the web service server.
1066
        $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
1067
 
1068
        // Check an array containing the expected user for the course capability is returned.
1069
        $expecteduserlist = $result[0]['users'];
1070
        $expecteduser = reset($expecteduserlist);
1071
        $this->assertEquals(1, count($expecteduserlist));
1072
        $this->assertEquals($data->student1->id, $expecteduser['id']);
1073
    }
1074
 
1075
    /**
1076
     * Test get_enrolled_users last course access.
1077
     */
1078
    public function test_get_enrolled_users_with_capability_including_lastcourseaccess() {
1079
        global $DB;
1080
        $capability = 'moodle/course:viewparticipants';
1081
        $data = $this->get_enrolled_users_with_capability_setup($capability);
1082
 
1083
        $parameters = array(
1084
            'coursecapabilities' => array(
1085
                'courseid' => $data->course->id,
1086
                'capabilities' => array(
1087
                    $capability,
1088
                ),
1089
            ),
1090
        );
1091
 
1092
        $result = core_enrol_external::get_enrolled_users_with_capability($parameters, array());
1093
        // We need to execute the return values cleaning process to simulate the web service server.
1094
        $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
1095
 
1096
        // Check an array containing the expected user for the course capability is returned.
1097
        $expecteduserlist = $result[0];
1098
        $this->assertEquals($data->course->id, $expecteduserlist['courseid']);
1099
        $this->assertEquals($capability, $expecteduserlist['capability']);
1100
        $this->assertEquals(2, count($expecteduserlist['users']));
1101
        // We forced an access to the course via setUser.
1102
        $this->assertNotEquals(0, $expecteduserlist['users'][0]['lastcourseaccess']);
1103
        $this->assertEquals(0, $expecteduserlist['users'][1]['lastcourseaccess']);
1104
 
1105
        // Force last access.
1106
        $timenow = time();
1107
        $lastaccess = array(
1108
            'userid' => $expecteduserlist['users'][1]['id'],
1109
            'courseid' => $data->course->id,
1110
            'timeaccess' => $timenow
1111
        );
1112
        $DB->insert_record('user_lastaccess', $lastaccess);
1113
 
1114
        $result = core_enrol_external::get_enrolled_users_with_capability($parameters, array());
1115
        // We need to execute the return values cleaning process to simulate the web service server.
1116
        $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
1117
 
1118
        // Check the result set.
1119
        $expecteduserlist = $result[0];
1120
        $this->assertEquals(2, count($expecteduserlist['users']));
1121
        $this->assertNotEquals(0, $expecteduserlist['users'][0]['lastcourseaccess']);
1122
        $this->assertEquals($timenow, $expecteduserlist['users'][1]['lastcourseaccess']);
1123
    }
1124
 
1125
    /**
1126
     * dataProvider for test_submit_user_enrolment_form().
1127
     */
1128
    public function submit_user_enrolment_form_provider() {
1129
        $now = new \DateTime();
1130
 
1131
        $nextmonth = clone($now);
1132
        $nextmonth->add(new \DateInterval('P1M'));
1133
 
1134
        return [
1135
            'Invalid data' => [
1136
                'customdata' => [
1137
                    'status' => ENROL_USER_ACTIVE,
1138
                    'timestart' => [
1139
                        'day' => $now->format('j'),
1140
                        'month' => $now->format('n'),
1141
                        'year' => $now->format('Y'),
1142
                        'hour' => $now->format('G'),
1143
                        'minute' => 0,
1144
                        'enabled' => 1,
1145
                    ],
1146
                    'timeend' => [
1147
                        'day' => $now->format('j'),
1148
                        'month' => $now->format('n'),
1149
                        'year' => $now->format('Y'),
1150
                        'hour' => $now->format('G'),
1151
                        'minute' => 0,
1152
                        'enabled' => 1,
1153
                    ],
1154
                ],
1155
                'expectedresult' => false,
1156
                'validationerror' => true,
1157
            ],
1158
            'Valid data' => [
1159
                'customdata' => [
1160
                    'status' => ENROL_USER_ACTIVE,
1161
                    'timestart' => [
1162
                        'day' => $now->format('j'),
1163
                        'month' => $now->format('n'),
1164
                        'year' => $now->format('Y'),
1165
                        'hour' => $now->format('G'),
1166
                        'minute' => 0,
1167
                        'enabled' => 1,
1168
                    ],
1169
                    'timeend' => [
1170
                        'day' => $nextmonth->format('j'),
1171
                        'month' => $nextmonth->format('n'),
1172
                        'year' => $nextmonth->format('Y'),
1173
                        'hour' => $nextmonth->format('G'),
1174
                        'minute' => 0,
1175
                        'enabled' => 1,
1176
                    ],
1177
                ],
1178
                'expectedresult' => true,
1179
                'validationerror' => false
1180
            ],
1181
            'Suspend user' => [
1182
                'customdata' => [
1183
                    'status' => ENROL_USER_SUSPENDED,
1184
                ],
1185
                'expectedresult' => true,
1186
                'validationerror' => false
1187
            ],
1188
        ];
1189
    }
1190
 
1191
    /**
1192
     * @param array $customdata The data we are providing to the webservice.
1193
     * @param bool $expectedresult The result we are expecting to receive from the webservice.
1194
     * @param bool $validationerror The validationerror we are expecting to receive from the webservice.
1195
     * @dataProvider submit_user_enrolment_form_provider
1196
     */
1197
    public function test_submit_user_enrolment_form($customdata, $expectedresult, $validationerror) {
1198
        global $CFG, $DB;
1199
 
1200
        $this->resetAfterTest(true);
1201
        $datagen = $this->getDataGenerator();
1202
 
1203
        /** @var \enrol_manual_plugin $manualplugin */
1204
        $manualplugin = enrol_get_plugin('manual');
1205
 
1206
        $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1207
        $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1208
        $course = $datagen->create_course();
1209
        $user = $datagen->create_user();
1210
        $teacher = $datagen->create_user();
1211
 
1212
        $instanceid = null;
1213
        $instances = enrol_get_instances($course->id, true);
1214
        foreach ($instances as $inst) {
1215
            if ($inst->enrol == 'manual') {
1216
                $instanceid = (int)$inst->id;
1217
                break;
1218
            }
1219
        }
1220
        if (empty($instanceid)) {
1221
            $instanceid = $manualplugin->add_default_instance($course);
1222
            if (empty($instanceid)) {
1223
                $instanceid = $manualplugin->add_instance($course);
1224
            }
1225
        }
1226
        $this->assertNotNull($instanceid);
1227
 
1228
        $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1229
        $manualplugin->enrol_user($instance, $user->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1230
        $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1231
        $ueid = (int) $DB->get_field(
1232
                'user_enrolments',
1233
                'id',
1234
                ['enrolid' => $instance->id, 'userid' => $user->id],
1235
                MUST_EXIST
1236
        );
1237
 
1238
        // Login as teacher.
1239
        $teacher->ignoresesskey = true;
1240
        $this->setUser($teacher);
1241
 
1242
        $formdata = [
1243
            'ue'        => $ueid,
1244
            'ifilter'   => 0,
1245
            'status'    => null,
1246
            'timestart' => null,
1247
            'duration'  => null,
1248
            'timeend'   => null,
1249
        ];
1250
 
1251
        $formdata = array_merge($formdata, $customdata);
1252
 
1253
        require_once("$CFG->dirroot/enrol/editenrolment_form.php");
1254
        $formdata = enrol_user_enrolment_form::mock_generate_submit_keys($formdata);
1255
 
1256
        $querystring = http_build_query($formdata, '', '&');
1257
 
1258
        $result = external_api::clean_returnvalue(
1259
                core_enrol_external::submit_user_enrolment_form_returns(),
1260
                core_enrol_external::submit_user_enrolment_form($querystring)
1261
        );
1262
 
1263
        $this->assertEqualsCanonicalizing(
1264
                ['result' => $expectedresult, 'validationerror' => $validationerror],
1265
                $result);
1266
 
1267
        if ($result['result']) {
1268
            $ue = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST);
1269
            $this->assertEquals($formdata['status'], $ue->status);
1270
        }
1271
    }
1272
 
1273
    /**
1274
     * Test for core_enrol_external::unenrol_user_enrolment().
1275
     */
1276
    public function test_unenerol_user_enrolment() {
1277
        global $DB;
1278
 
1279
        $this->resetAfterTest(true);
1280
        $datagen = $this->getDataGenerator();
1281
 
1282
        /** @var \enrol_manual_plugin $manualplugin */
1283
        $manualplugin = enrol_get_plugin('manual');
1284
        $this->assertNotNull($manualplugin);
1285
 
1286
        $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1287
        $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1288
        $course = $datagen->create_course();
1289
        $user = $datagen->create_user();
1290
        $teacher = $datagen->create_user();
1291
 
1292
        $instanceid = null;
1293
        $instances = enrol_get_instances($course->id, true);
1294
        foreach ($instances as $inst) {
1295
            if ($inst->enrol == 'manual') {
1296
                $instanceid = (int)$inst->id;
1297
                break;
1298
            }
1299
        }
1300
        if (empty($instanceid)) {
1301
            $instanceid = $manualplugin->add_default_instance($course);
1302
            if (empty($instanceid)) {
1303
                $instanceid = $manualplugin->add_instance($course);
1304
            }
1305
        }
1306
        $this->assertNotNull($instanceid);
1307
 
1308
        $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1309
        $manualplugin->enrol_user($instance, $user->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1310
        $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1311
        $ueid = (int)$DB->get_field(
1312
            'user_enrolments',
1313
            'id',
1314
            ['enrolid' => $instance->id, 'userid' => $user->id],
1315
            MUST_EXIST
1316
        );
1317
 
1318
        // Login as teacher.
1319
        $this->setUser($teacher);
1320
 
1321
        // Invalid data by passing invalid ueid.
1322
        $data = core_enrol_external::unenrol_user_enrolment(101010);
1323
        $data = external_api::clean_returnvalue(core_enrol_external::unenrol_user_enrolment_returns(), $data);
1324
        $this->assertFalse($data['result']);
1325
        $this->assertNotEmpty($data['errors']);
1326
 
1327
        // Valid data.
1328
        $data = core_enrol_external::unenrol_user_enrolment($ueid);
1329
        $data = external_api::clean_returnvalue(core_enrol_external::unenrol_user_enrolment_returns(), $data);
1330
        $this->assertTrue($data['result']);
1331
        $this->assertEmpty($data['errors']);
1332
 
1333
        // Check unenrol user enrolment.
1334
        $ue = $DB->count_records('user_enrolments', ['id' => $ueid]);
1335
        $this->assertEquals(0, $ue);
1336
    }
1337
 
1338
    /**
1339
     * Test for core_enrol_external::test_search_users().
1340
     */
1341
    public function test_search_users() {
1342
        global $DB;
1343
 
1344
        $this->resetAfterTest(true);
1345
        $datagen = $this->getDataGenerator();
1346
 
1347
        /** @var \enrol_manual_plugin $manualplugin */
1348
        $manualplugin = enrol_get_plugin('manual');
1349
        $this->assertNotNull($manualplugin);
1350
 
1351
        $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1352
        $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1353
 
1354
        $course1 = $datagen->create_course();
1355
        $course2 = $datagen->create_course();
1356
 
1357
        $user1 = $datagen->create_user(['firstname' => 'user 1']);
1358
        $user2 = $datagen->create_user(['firstname' => 'user 2']);
1359
        $user3 = $datagen->create_user(['firstname' => 'user 3']);
1360
        $teacher = $datagen->create_user(['firstname' => 'user 4']);
1361
 
1362
        $instanceid = null;
1363
        $instances = enrol_get_instances($course1->id, true);
1364
        foreach ($instances as $inst) {
1365
            if ($inst->enrol == 'manual') {
1366
                $instanceid = (int)$inst->id;
1367
                break;
1368
            }
1369
        }
1370
        if (empty($instanceid)) {
1371
            $instanceid = $manualplugin->add_default_instance($course1);
1372
            if (empty($instanceid)) {
1373
                $instanceid = $manualplugin->add_instance($course1);
1374
            }
1375
        }
1376
        $this->assertNotNull($instanceid);
1377
 
1378
        $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1379
        $manualplugin->enrol_user($instance, $user1->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1380
        $manualplugin->enrol_user($instance, $user2->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1381
        $manualplugin->enrol_user($instance, $user3->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1382
        $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1383
 
1384
        $this->setUser($teacher);
1385
 
1386
        // Search for users in a course with enrolled users.
1387
        $result = core_enrol_external::search_users($course1->id, 'user', true, 0, 30);
1388
        $this->assertCount(4, $result);
1389
 
1390
        $this->expectException('moodle_exception');
1391
        // Search for users in a course without any enrolled users, shouldn't return anything.
1392
        $result = core_enrol_external::search_users($course2->id, 'user', true, 0, 30);
1393
        $this->assertCount(0, $result);
1394
 
1395
        // Search for invalid first name.
1396
        $result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30);
1397
        $this->assertCount(0, $result);
1398
 
1399
        // Test pagination, it should return only 3 users.
1400
        $result = core_enrol_external::search_users($course1->id, 'user', true, 0, 3);
1401
        $this->assertCount(3, $result);
1402
 
1403
        // Test pagination, it should return only 3 users.
1404
        $result = core_enrol_external::search_users($course1->id, 'user 1', true, 0, 1);
1405
        $result = $result[0];
1406
        $this->assertEquals($user1->id, $result['id']);
1407
        $this->assertEquals($user1->email, $result['email']);
1408
        $this->assertEquals(fullname($user1), $result['fullname']);
1409
 
1410
        $this->setUser($user1);
1411
 
1412
        // Search for users in a course with enrolled users.
1413
        $result = core_enrol_external::search_users($course1->id, 'user', true, 0, 30);
1414
        $this->assertCount(4, $result);
1415
 
1416
        $this->expectException('moodle_exception');
1417
        // Search for users in a course without any enrolled users, shouldn't return anything.
1418
        $result = core_enrol_external::search_users($course2->id, 'user', true, 0, 30);
1419
        $this->assertCount(0, $result);
1420
 
1421
        // Search for invalid first name.
1422
        $result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30);
1423
        $this->assertCount(0, $result);
1424
    }
1425
 
1426
    /**
1427
     * Test for core_enrol_external::search_users() when group mode is active.
1428
     * @covers ::search_users
1429
     */
1430
    public function test_search_users_groupmode() {
1431
        global $DB;
1432
 
1433
        $this->resetAfterTest();
1434
        $datagen = $this->getDataGenerator();
1435
 
1436
        $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1437
        $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'teacher'], MUST_EXIST);
1438
 
1439
        $course = $datagen->create_course();
1440
 
1441
        $student1 = $datagen->create_and_enrol($course);
1442
        $student2 = $datagen->create_and_enrol($course);
1443
        $student3 = $datagen->create_and_enrol($course);
1444
        $teacher1 = $datagen->create_and_enrol($course, 'teacher');
1445
        $teacher2 = $datagen->create_and_enrol($course, 'teacher');
1446
        $teacher3 = $datagen->create_and_enrol($course, 'teacher');
1447
        $teacher4 = $datagen->create_and_enrol($course, 'editingteacher');
1448
 
1449
        // Create 2 groups.
1450
        $group1 = $datagen->create_group(['courseid' => $course->id]);
1451
        $group2 = $datagen->create_group(['courseid' => $course->id]);
1452
 
1453
        // Add the users to the groups.
1454
        $datagen->create_group_member(['groupid' => $group1->id, 'userid' => $student1->id]);
1455
        $datagen->create_group_member(['groupid' => $group2->id, 'userid' => $student2->id]);
1456
        $datagen->create_group_member(['groupid' => $group2->id, 'userid' => $student3->id]);
1457
        $datagen->create_group_member(['groupid' => $group1->id, 'userid' => $teacher1->id]);
1458
        $datagen->create_group_member(['groupid' => $group2->id, 'userid' => $teacher1->id]);
1459
        $datagen->create_group_member(['groupid' => $group1->id, 'userid' => $teacher2->id]);
1460
 
1461
        // Create the forum.
1462
        $record = new stdClass();
1463
        $record->introformat = FORMAT_HTML;
1464
        $record->course = $course->id;
1465
        $forum = self::getDataGenerator()->create_module('forum', $record, ['groupmode' => SEPARATEGROUPS]);
1466
        $contextid = $DB->get_field('context', 'id', ['instanceid' => $forum->cmid, 'contextlevel' => CONTEXT_MODULE]);
1467
 
1468
        $this->setUser($teacher1);
1469
        $result = core_enrol_external::search_users($course->id, 'user', true, 0, 30, $contextid);
1470
        $this->assertCount(5, $result);
1471
 
1472
        $this->setUser($teacher2);
1473
        $result = core_enrol_external::search_users($course->id, 'user', true, 0, 30, $contextid);
1474
        $this->assertCount(3, $result);
1475
 
1476
        $this->setUser($teacher3);
1477
        $result = core_enrol_external::search_users($course->id, 'user', true, 0, 30, $contextid);
1478
        $this->assertCount(0, $result);
1479
 
1480
        $this->setUser($teacher4);
1481
        $result = core_enrol_external::search_users($course->id, 'user', true, 0, 30, $contextid);
1482
        $this->assertCount(7, $result);
1483
 
1484
        // Now change the group mode to no groups.
1485
        set_coursemodule_groupmode($forum->cmid, NOGROUPS);
1486
        $this->setUser($teacher1);
1487
        $result = core_enrol_external::search_users($course->id, 'user', true, 0, 30, $contextid);
1488
        $this->assertCount(7, $result);
1489
    }
1490
 
1491
    /**
1492
     * Tests the get_potential_users external function (not too much detail because the back-end
1493
     * is covered in another test).
1494
     */
1495
    public function test_get_potential_users(): void {
1496
        $this->resetAfterTest();
1497
 
1498
        // Create a couple of custom profile fields, one of which is in user identity.
1499
        $generator = $this->getDataGenerator();
1500
        $generator->create_custom_profile_field(['datatype' => 'text',
1501
                'shortname' => 'researchtopic', 'name' => 'Research topic']);
1502
        $generator->create_custom_profile_field(['datatype' => 'text',
1503
                'shortname' => 'specialid', 'name' => 'Special id']);
1504
        set_config('showuseridentity', 'department,profile_field_specialid');
1505
 
1506
        // Create a course.
1507
        $course = $generator->create_course();
1508
 
1509
        // Get enrol id for manual enrol plugin.
1510
        foreach (enrol_get_instances($course->id, true) as $instance) {
1511
            if ($instance->enrol === 'manual') {
1512
                $enrolid = $instance->id;
1513
            }
1514
        }
1515
 
1516
        // Create a couple of test users.
1517
        $user1 = $generator->create_user(['firstname' => 'Eigh', 'lastname' => 'User',
1518
                'department' => 'Amphibians', 'profile_field_specialid' => 'Q123',
1519
                'profile_field_researchtopic' => 'Frogs']);
1520
        $user2 = $generator->create_user(['firstname' => 'Anne', 'lastname' => 'Other',
1521
                'department' => 'Amphibians', 'profile_field_specialid' => 'Q456',
1522
                'profile_field_researchtopic' => 'Toads']);
1523
 
1524
        // Do this as admin user.
1525
        $this->setAdminUser();
1526
 
1527
        // Get potential users and extract the 2 we care about.
1528
        $result = core_enrol_external::get_potential_users($course->id, $enrolid, '', false, 0, 10);
1529
        $result1 = $this->extract_user_from_result($result, $user1->id);
1530
        $result2 = $this->extract_user_from_result($result, $user2->id);
1531
 
1532
        // Check the fields are the expected ones.
1533
        $this->assertEquals(['id', 'fullname', 'customfields',
1534
                'profileimageurl', 'profileimageurlsmall', 'department'], array_keys($result1));
1535
        $this->assertEquals('Eigh User', $result1['fullname']);
1536
        $this->assertEquals('Amphibians', $result1['department']);
1537
 
1538
        // Check the custom fields ONLY include the user identity one.
1539
        $fieldvalues = [];
1540
        foreach ($result1['customfields'] as $customfield) {
1541
            $fieldvalues[$customfield['shortname']] = $customfield['value'];
1542
        }
1543
        $this->assertEquals(['specialid'], array_keys($fieldvalues));
1544
        $this->AssertEquals('Q123', $fieldvalues['specialid']);
1545
 
1546
        // Just check user 2 is the right user.
1547
        $this->assertEquals('Anne Other', $result2['fullname']);
1548
    }
1549
 
1550
    /**
1551
     * Utility function to get one user out of the get_potential_users result.
1552
     *
1553
     * @param array $result Result array
1554
     * @param int $userid User id
1555
     * @return array Data for that user
1556
     */
1557
    protected function extract_user_from_result(array $result, int $userid): array {
1558
        foreach ($result as $item) {
1559
            if ($item['id'] == $userid) {
1560
                return $item;
1561
            }
1562
        }
1563
        $this->fail('User not in result: ' . $userid);
1564
    }
1565
}