Merge branch 'MDL-69869-master' of git://github.com/ilya-catalyst/moodle
[moodle.git] / enrol / tests / externallib_test.php
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/>.
17 defined('MOODLE_INTERNAL') || die();
19 global $CFG;
21 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
22 require_once($CFG->dirroot . '/enrol/externallib.php');
24 /**
25  * Enrol external PHPunit tests
26  *
27  * @package    core_enrol
28  * @category   external
29  * @copyright  2012 Jerome Mouneyrac
30  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31  * @since Moodle 2.4
32  */
33 class core_enrol_externallib_testcase extends externallib_advanced_testcase {
35     /**
36      * dataProvider for test_get_enrolled_users_visibility().
37      */
38     public function get_enrolled_users_visibility_provider() {
39         return array(
40             'Course without groups, default behavior (not filtering by cap, group, active)' =>
41             array(
42                 'settings' => array(
43                     'coursegroupmode' => NOGROUPS,
44                     'withcapability' => null,
45                     'groupid' => null,
46                     'onlyactive' => false,
47                     'allowedcaps' => array(),
48                 ),
49                 'results' => array( // Everybody can view everybody.
50                     'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
51                     'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
52                     'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
53                     'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
54                     'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
55                 ),
56             ),
58             'Course with visible groups, default behavior (not filtering by cap, group, active)' =>
59             array(
60                 'settings' => array(
61                     'coursegroupmode' => VISIBLEGROUPS,
62                     'withcapability' => null,
63                     'groupid' => null,
64                     'onlyactive' => false,
65                     'allowedcaps' => array(),
66                 ),
67                 'results' => array( // Everybody can view everybody.
68                     'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
69                     'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
70                     'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
71                     'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
72                     'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
73                 ),
74             ),
76             'Course with separate groups, default behavior (not filtering by cap, group, active)' =>
77             array(
78                 'settings' => array(
79                     'coursegroupmode' => SEPARATEGROUPS,
80                     'withcapability' => null,
81                     'groupid' => null,
82                     'onlyactive' => false,
83                     'allowedcaps' => array(),
84                 ),
85                 'results' => array( // Only users from own groups are visible.
86                     'user0' => array('canview' => array()), // Poor guy, cannot see anybody, himself included.
87                     'user1' => array('canview' => array('user1', 'userall')),
88                     'user2' => array('canview' => array('user2', 'user2su', 'userall')),
89                     'user31' => array('canview' => array('user31', 'user32', 'userall')),
90                     'userall' => array('canview' => array('user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
91                 ),
92             ),
94             'Course with separate groups, default behavior (not filtering but having moodle/site:accessallgroups)' =>
95             array(
96                 'settings' => array(
97                     'coursegroupmode' => VISIBLEGROUPS,
98                     'withcapability' => null,
99                     'groupid' => null,
100                     'onlyactive' => false,
101                     'allowedcaps' => array('moodle/site:accessallgroups'),
102                 ),
103                 'results' => array( // Everybody can view everybody.
104                     'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
105                     'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
106                     'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
107                     'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
108                     'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
109                 ),
110             ),
112             'Course with separate groups, filtering onlyactive (missing moodle/course:enrolreview)' =>
113             array(
114                 'settings' => array(
115                     'coursegroupmode' => SEPARATEGROUPS,
116                     'withcapability' => null,
117                     'groupid' => null,
118                     'onlyactive' => true,
119                     'allowedcaps' => array(),
120                 ),
121                 'results' => array( // returns exception, cannot view anybody without the cap.
122                     'user2' => array('exception' => array(
123                         'type' => 'required_capability_exception',
124                         'message' => 'Review course enrolments')),
125                     'userall' => array('exception' => array(
126                         'type' => 'required_capability_exception',
127                         'message' => 'Review course enrolments')),
128                 ),
129             ),
131             'Course with separate groups, filtering onlyactive (having moodle/course:enrolreview)' =>
132             array(
133                 'settings' => array(
134                     'coursegroupmode' => SEPARATEGROUPS,
135                     'withcapability' => null,
136                     'groupid' => null,
137                     'onlyactive' => true,
138                     'allowedcaps' => array('moodle/course:enrolreview'),
139                 ),
140                 'results' => array( // Suspended are not returned.
141                     'user2' => array('canview' => array('user2', 'userall')),
142                     'user31' => array('canview' => array('user31', 'user32', 'userall')),
143                     'userall' => array('canview' => array('user1', 'user2', 'user31', 'user32', 'userall')),
144                 ),
145             ),
147             'Course with separate groups, filtering by groupid (not having moodle/site:accessallgroups)' =>
148             array(
149                 'settings' => array(
150                     'coursegroupmode' => SEPARATEGROUPS,
151                     'withcapability' => null,
152                     'groupid' => 'group2',
153                     'onlyactive' => false,
154                     'allowedcaps' => array(),
155                 ),
156                 'results' => array( // Only group 2 members and only for members. Exception for non-members.
157                     'user0' => array('exception' => array(
158                         'type' => 'required_capability_exception',
159                         'message' => 'Access all groups')),
160                     'user1' => array('exception' => array(
161                         'type' => 'required_capability_exception',
162                         'message' => 'Access all groups')),
163                     'user2' => array('canview' => array('user2', 'user2su', 'userall')),
164                     'userall' => array('canview' => array('user2', 'user2su', 'userall')),
165                 ),
166             ),
168             'Course with separate groups, filtering by groupid (having moodle/site:accessallgroups)' =>
169             array(
170                 'settings' => array(
171                     'coursegroupmode' => SEPARATEGROUPS,
172                     'withcapability' => null,
173                     'groupid' => 'group2',
174                     'onlyactive' => false,
175                     'allowedcaps' => array('moodle/site:accessallgroups'),
176                 ),
177                 'results' => array( // All users with 'moodle/site:accessallgroups' can view group 2
178                     'user0' => array('canview' => array('user2', 'user2su', 'userall')),
179                     'user1' => array('canview' => array('user2', 'user2su', 'userall')),
180                     'user2' => array('canview' => array('user2', 'user2su', 'userall')),
181                     'userall' => array('canview' => array('user2', 'user2su', 'userall')),
182                 ),
183             ),
185             'Course with separate groups, filtering by withcapability (not having moodle/role:review)' =>
186             array(
187                 'settings' => array(
188                     'coursegroupmode' => SEPARATEGROUPS,
189                     'withcapability' => 'moodle/course:bulkmessaging',
190                     'groupid' => null,
191                     'onlyactive' => false,
192                     'allowedcaps' => array(),
193                 ),
194                 'results' => array( // No user has 'moodle/role:review' so exception.
195                     'user0' => array('exception' => array(
196                         'type' => 'required_capability_exception',
197                         'message' => 'Review permissions for others')),
198                     'user1' => array('exception' => array(
199                         'type' => 'required_capability_exception',
200                         'message' => 'Review permissions for others')),
201                     'user2' => array('exception' => array(
202                         'type' => 'required_capability_exception',
203                         'message' => 'Review permissions for others')),
204                     'userall' => array('exception' => array(
205                         'type' => 'required_capability_exception',
206                         'message' => 'Review permissions for others')),
207                 ),
208             ),
210             'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
211             array(
212                 'settings' => array(
213                     'coursegroupmode' => SEPARATEGROUPS,
214                     'withcapability' => 'moodle/course:bulkmessaging',
215                     'groupid' => null,
216                     'onlyactive' => false,
217                     'allowedcaps' => array('moodle/role:review'),
218                 ),
219                 'results' => array( // No user has withcapability, but all have 'moodle/role:review'. Empties.
220                     'user0' => array('canview' => array()),
221                     'user1' => array('canview' => array()),
222                     'user2' => array('canview' => array()),
223                     'userall' => array('canview' => array()),
224                 ),
225             ),
227             'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
228             array(
229                 'settings' => array(
230                     'coursegroupmode' => SEPARATEGROUPS,
231                     'withcapability' => 'moodle/course:bulkmessaging',
232                     'groupid' => null,
233                     'onlyactive' => false,
234                     'allowedcaps' => array('moodle/role:review', 'moodle/course:bulkmessaging'),
235                 ),
236                 'results' => array( // Users (previous) have withcapability, and all have 'moodle/role:review'.
237                     'user0' => array('canview' => array()),
238                     'user1' => array('canview' => array('user1')),
239                     'user2' => array('canview' => array('user2')),
240                     'userall' => array('canview' => array('user1', 'user2', 'userall')),
241                 ),
242             ),
243         );
244     }
246     /**
247      * Verify get_enrolled_users() returned users are the expected in every situation.
248      *
249      * @dataProvider get_enrolled_users_visibility_provider
250      */
251     public function test_get_enrolled_users_visibility($settings, $results) {
253         global $USER;
255         $this->resetAfterTest();
257         // Create the course and the users.
258         $course = $this->getDataGenerator()->create_course(array('groupmode' => $settings['coursegroupmode']));
259         $coursecontext = context_course::instance($course->id);
260         $user0 = $this->getDataGenerator()->create_user(array('username' => 'user0'));     // A user without group.
261         $user1 = $this->getDataGenerator()->create_user(array('username' => 'user1'));     // User for group 1.
262         $user2 = $this->getDataGenerator()->create_user(array('username' => 'user2'));     // Two users for group 2.
263         $user2su = $this->getDataGenerator()->create_user(array('username' => 'user2su')); // (one suspended).
264         $user31 = $this->getDataGenerator()->create_user(array('username' => 'user31'));   // Two users for group 3.
265         $user32 = $this->getDataGenerator()->create_user(array('username' => 'user32'));   // (both enabled).
266         $userall = $this->getDataGenerator()->create_user(array('username' => 'userall')); // A user in all groups.
268         // Create utility array of created users, to produce better assertion messages.
269         $createdusers = array();
270         foreach (array($user0, $user1, $user2, $user2su, $user31, $user32, $userall) as $createduser) {
271             $createdusers[$createduser->id] = $createduser->username;
272         }
274         // Enrol the users in the course.
275         $this->getDataGenerator()->enrol_user($user0->id, $course->id);
276         $this->getDataGenerator()->enrol_user($user1->id, $course->id);
277         $this->getDataGenerator()->enrol_user($user2->id, $course->id);
278         $this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
279         $this->getDataGenerator()->enrol_user($user31->id, $course->id);
280         $this->getDataGenerator()->enrol_user($user32->id, $course->id);
281         $this->getDataGenerator()->enrol_user($userall->id, $course->id);
283         // Create 3 groups.
284         $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
285         $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
286         $group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
288         // Add the users to the groups.
289         $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
290         $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
291         $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2su->id));
292         $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user31->id));
293         $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user32->id));
294         $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $userall->id));
295         $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id));
296         $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id));
298         // Create a role to add the allowedcaps. Users will have this role assigned.
299         $roleid = $this->getDataGenerator()->create_role();
300         // Allow the specified capabilities.
301         if (!empty($settings['allowedcaps'])) {
302             foreach ($settings['allowedcaps'] as $capability) {
303                 assign_capability($capability, CAP_ALLOW, $roleid, $coursecontext);
304             }
305         }
307         // For each of the users, configure everything, perform the call, and assert results.
308         foreach ($results as $user => $expectations) {
309             // Convert canview expectations into a nice array of ids for easier handling.
310             $canview = array();
311             $exception = null;
312             // Analyse the expectations.
313             if (isset($expectations['canview'])) {
314                 foreach ($expectations['canview'] as $canviewuser) {
315                     $canview[] = $createdusers[${$canviewuser}->id];
316                 }
317             } else if (isset($expectations['exception'])) {
318                 $exception = $expectations['exception'];
319                 $this->expectException($exception['type']);
320                 $this->expectExceptionMessage($exception['message']);
321             } else {
322                 // Failed, only canview and exception are supported.
323                 $this->markTestIncomplete('Incomplete, only canview and exception are supported');
324             }
325             // Switch to the user and assign the role.
326             $this->setUser(${$user});
327             role_assign($roleid, $USER->id, $coursecontext);
329             // Convert groupid to proper id.
330             $groupid = 0;
331             if (isset($settings['groupid'])) {
332                 $groupid = ${$settings['groupid']}->id;
333             }
335             // Call to the function.
336             $options = array(
337                 array('name' => 'withcapability', 'value' => $settings['withcapability']),
338                 array('name' => 'groupid', 'value' => $groupid),
339                 array('name' => 'onlyactive', 'value' => $settings['onlyactive']),
340                 array('name' => 'userfields', 'value' => 'id')
341             );
342             $enrolledusers = core_enrol_external::get_enrolled_users($course->id, $options);
344             // We need to execute the return values cleaning process to simulate the web service server.
345             $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
347             // We are only interested in ids to check visibility.
348             $viewed = array();
349             // Verify the user canview the expected users.
350             foreach ($enrolledusers as $enrolleduser) {
351                 $viewed[] = $createdusers[$enrolleduser['id']];
352             }
353             // Verify viewed matches canview expectation (using canonicalize to ignore ordering).
354             $this->assertEqualsCanonicalizing($canview, $viewed, "Problem checking visible users for '{$createdusers[$USER->id]}'");
355         }
356     }
358     /**
359      * Verify get_enrolled_users() returned users according to their status.
360      */
361     public function test_get_enrolled_users_active_suspended() {
362         global $USER;
364         $this->resetAfterTest();
366         // Create the course and the users.
367         $course = $this->getDataGenerator()->create_course();
368         $coursecontext = context_course::instance($course->id);
369         $user0 = $this->getDataGenerator()->create_user(['username' => 'user0active']);
370         $user1 = $this->getDataGenerator()->create_user(['username' => 'user1active']);
371         $user2 = $this->getDataGenerator()->create_user(['username' => 'user2active']);
372         $user2su = $this->getDataGenerator()->create_user(['username' => 'user2suspended']); // Suspended user.
373         $user3 = $this->getDataGenerator()->create_user(['username' => 'user3active']);
374         $user3su = $this->getDataGenerator()->create_user(['username' => 'user3suspended']); // Suspended user.
376         // Enrol the users in the course.
377         $this->getDataGenerator()->enrol_user($user0->id, $course->id);
378         $this->getDataGenerator()->enrol_user($user1->id, $course->id);
379         $this->getDataGenerator()->enrol_user($user2->id, $course->id);
380         $this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
381         $this->getDataGenerator()->enrol_user($user3->id, $course->id);
382         $this->getDataGenerator()->enrol_user($user3su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
384         // Create a role to add the allowedcaps. Users will have this role assigned.
385         $roleid = $this->getDataGenerator()->create_role();
386         // Allow the specified capabilities.
387         assign_capability('moodle/course:enrolreview', CAP_ALLOW, $roleid, $coursecontext);
388         assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $roleid, $coursecontext);
390         // Switch to the user and assign the role.
391         $this->setUser($user0);
392         role_assign($roleid, $USER->id, $coursecontext);
394         // Suspended users.
395         $options = [
396             ['name' => 'onlysuspended', 'value' => true],
397             ['name' => 'userfields', 'value' => 'id,username']
398         ];
399         $suspendedusers = core_enrol_external::get_enrolled_users($course->id, $options);
401         // We need to execute the return values cleaning process to simulate the web service server.
402         $suspendedusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $suspendedusers);
403         $this->assertCount(2, $suspendedusers);
405         foreach ($suspendedusers as $suspendeduser) {
406             $this->assertStringContainsString('suspended', $suspendeduser['username']);
407         }
409         // Active users.
410         $options = [
411             ['name' => 'onlyactive', 'value' => true],
412             ['name' => 'userfields', 'value' => 'id,username']
413         ];
414         $activeusers = core_enrol_external::get_enrolled_users($course->id, $options);
416         // We need to execute the return values cleaning process to simulate the web service server.
417         $activeusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $activeusers);
418         $this->assertCount(4, $activeusers);
420         foreach ($activeusers as $activeuser) {
421             $this->assertStringContainsString('active', $activeuser['username']);
422         }
424         // All enrolled users.
425         $options = [
426             ['name' => 'userfields', 'value' => 'id,username']
427         ];
428         $allusers = core_enrol_external::get_enrolled_users($course->id, $options);
430         // We need to execute the return values cleaning process to simulate the web service server.
431         $allusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $allusers);
432         $this->assertCount(6, $allusers);
434         // Active and suspended. Test exception is thrown.
435         $options = [
436             ['name' => 'onlyactive', 'value' => true],
437             ['name' => 'onlysuspended', 'value' => true],
438             ['name' => 'userfields', 'value' => 'id,username']
439         ];
440         $this->expectException('coding_exception');
441         $message = 'Coding error detected, it must be fixed by a programmer: Both onlyactive ' .
442                         'and onlysuspended are set, this is probably not what you want!';
443         $this->expectExceptionMessage($message);
444         core_enrol_external::get_enrolled_users($course->id, $options);
445     }
447     /**
448      * Test get_users_courses
449      */
450     public function test_get_users_courses() {
451         global $CFG, $DB;
452         require_once($CFG->dirroot . '/completion/criteria/completion_criteria_self.php');
454         $this->resetAfterTest(true);
455         $CFG->enablecompletion = 1;
457         $timenow = time();
458         $coursedata1 = array(
459             'fullname'         => '<b>Course 1</b>',                // Adding tags here to check that external_format_string works.
460             'shortname'         => '<b>Course 1</b>',               // Adding tags here to check that external_format_string works.
461             'summary'          => 'Lightwork Course 1 description',
462             'summaryformat'    => FORMAT_MOODLE,
463             'lang'             => 'en',
464             'enablecompletion' => true,
465             'showgrades'       => true,
466             'startdate'        => $timenow,
467             'enddate'          => $timenow + WEEKSECS,
468             'marker'           => 1
469         );
471         $coursedata2 = array(
472             'lang'             => 'kk', // Check invalid language pack.
473         );
475         $course1 = self::getDataGenerator()->create_course($coursedata1);
476         $course2 = self::getDataGenerator()->create_course($coursedata2);
477         $courses = array($course1, $course2);
478         $contexts = array ($course1->id => context_course::instance($course1->id),
479             $course2->id => context_course::instance($course2->id));
481         $student = $this->getDataGenerator()->create_user();
482         $otherstudent = $this->getDataGenerator()->create_user();
483         $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
484         $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentroleid);
485         $this->getDataGenerator()->enrol_user($otherstudent->id, $course1->id, $studentroleid);
486         $this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentroleid);
488         // Force last access.
489         $timenow = time();
490         $lastaccess = array(
491             'userid' => $student->id,
492             'courseid' => $course1->id,
493             'timeaccess' => $timenow
494         );
495         $DB->insert_record('user_lastaccess', $lastaccess);
497         // Force completion, setting at least one criteria.
498         require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
499         $criteriadata = new stdClass();
500         $criteriadata->id = $course1->id;
501         // Self completion.
502         $criteriadata->criteria_self = 1;
504         $criterion = new completion_criteria_self();
505         $criterion->update_config($criteriadata);
507         $ccompletion = new completion_completion(array('course' => $course1->id, 'userid' => $student->id));
508         $ccompletion->mark_complete();
510         // Set course hidden and favourited.
511         set_user_preference('block_myoverview_hidden_course_' . $course1->id, 1, $student);
512         $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($student->id));
513         $ufservice->create_favourite('core_course', 'courses', $course1->id, \context_system::instance());
515         $this->setUser($student);
516         // Call the external function.
517         $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
519         // We need to execute the return values cleaning process to simulate the web service server.
520         $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
522         // Check we retrieve the good total number of enrolled users.
523         $this->assertEquals(2, count($enrolledincourses));
525         // We need to format summary and summaryformat before to compare them with those values returned by the webservice.
526         list($course1->summary, $course1->summaryformat) =
527              external_format_text($course1->summary, $course1->summaryformat, $contexts[$course1->id]->id, 'course', 'summary', 0);
529         // Check there are no differences between $course1 properties and course values returned by the webservice
530         // only for those fields listed in the $coursedata1 array.
531         $course1->fullname = external_format_string($course1->fullname, $contexts[$course1->id]->id);
532         $course1->shortname = external_format_string($course1->shortname, $contexts[$course1->id]->id);
533         foreach ($enrolledincourses as $courseenrol) {
534             if ($courseenrol['id'] == $course1->id) {
535                 foreach ($coursedata1 as $fieldname => $value) {
536                     $this->assertEquals($courseenrol[$fieldname], $course1->$fieldname);
537                 }
538                 // Text extra fields.
539                 $this->assertEquals($course1->fullname, $courseenrol['displayname']);
540                 $this->assertEquals([], $courseenrol['overviewfiles']);
541                 $this->assertEquals($timenow, $courseenrol['lastaccess']);
542                 $this->assertEquals(100.0, $courseenrol['progress']);
543                 $this->assertEquals(true, $courseenrol['completed']);
544                 $this->assertTrue($courseenrol['completionhascriteria']);
545                 $this->assertTrue($courseenrol['completionusertracked']);
546                 $this->assertTrue($courseenrol['hidden']);
547                 $this->assertTrue($courseenrol['isfavourite']);
548                 $this->assertEquals(2, $courseenrol['enrolledusercount']);
549             } else {
550                 // Check language pack. Should be empty since an incorrect one was used when creating the course.
551                 $this->assertEmpty($courseenrol['lang']);
552                 $this->assertEquals($course2->fullname, $courseenrol['displayname']);
553                 $this->assertEquals([], $courseenrol['overviewfiles']);
554                 $this->assertEquals(0, $courseenrol['lastaccess']);
555                 $this->assertEquals(0, $courseenrol['progress']);
556                 $this->assertEquals(false, $courseenrol['completed']);
557                 $this->assertFalse($courseenrol['completionhascriteria']);
558                 $this->assertFalse($courseenrol['completionusertracked']);
559                 $this->assertFalse($courseenrol['hidden']);
560                 $this->assertFalse($courseenrol['isfavourite']);
561                 $this->assertEquals(1, $courseenrol['enrolledusercount']);
562             }
563         }
565         // Check that returnusercount works correctly.
566         $enrolledincourses = core_enrol_external::get_users_courses($student->id, false);
567         $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
568         foreach ($enrolledincourses as $courseenrol) {
569             $this->assertFalse(isset($courseenrol['enrolledusercount']));
570         }
572         // Now check that admin users can see all the info.
573         $this->setAdminUser();
575         $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
576         $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
577         $this->assertEquals(2, count($enrolledincourses));
578         foreach ($enrolledincourses as $courseenrol) {
579             if ($courseenrol['id'] == $course1->id) {
580                 $this->assertEquals($timenow, $courseenrol['lastaccess']);
581                 $this->assertEquals(100.0, $courseenrol['progress']);
582                 $this->assertTrue($courseenrol['completionhascriteria']);
583                 $this->assertTrue($courseenrol['completionusertracked']);
584                 $this->assertFalse($courseenrol['isfavourite']);    // This always false.
585                 $this->assertFalse($courseenrol['hidden']); // This always false.
586             } else {
587                 $this->assertEquals(0, $courseenrol['progress']);
588                 $this->assertFalse($courseenrol['completionhascriteria']);
589                 $this->assertFalse($courseenrol['completionusertracked']);
590                 $this->assertFalse($courseenrol['isfavourite']);    // This always false.
591                 $this->assertFalse($courseenrol['hidden']); // This always false.
592             }
593         }
595         // Check other users can't see private info.
596         $this->setUser($otherstudent);
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(1, count($enrolledincourses));
602         $this->assertEquals($timenow, $enrolledincourses[0]['lastaccess']); // I can see this, not hidden.
603         $this->assertEquals(null, $enrolledincourses[0]['progress']);   // I can't see this, private.
605         // Change some global profile visibility fields.
606         $CFG->hiddenuserfields = 'lastaccess';
607         $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
608         $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
610         $this->assertEquals(0, $enrolledincourses[0]['lastaccess']); // I can't see this, hidden by global setting.
611     }
613     /**
614      * Test get_users_courses with mathjax in the name.
615      */
616     public function test_get_users_courses_with_mathjax() {
617         global $DB;
619         $this->resetAfterTest(true);
621         // Enable MathJax filter in content and headings.
622         $this->configure_filters([
623             ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
624         ]);
626         // Create a course with MathJax in the name and summary.
627         $coursedata = [
628             'fullname'         => 'Course 1 $$(a+b)=2$$',
629             'shortname'         => 'Course 1 $$(a+b)=2$$',
630             'summary'          => 'Lightwork Course 1 description $$(a+b)=2$$',
631             'summaryformat'    => FORMAT_HTML,
632         ];
634         $course = self::getDataGenerator()->create_course($coursedata);
635         $context = context_course::instance($course->id);
637         // Enrol a student in the course.
638         $student = $this->getDataGenerator()->create_user();
639         $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student']);
640         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentroleid);
642         $this->setUser($student);
644         // Call the external function.
645         $enrolledincourses = core_enrol_external::get_users_courses($student->id, true);
647         // We need to execute the return values cleaning process to simulate the web service server.
648         $enrolledincourses = external_api::clean_returnvalue(core_enrol_external::get_users_courses_returns(), $enrolledincourses);
650         // Check that the amount of courses is the right one.
651         $this->assertCount(1, $enrolledincourses);
653         // Filter the values to compare them with the returned ones.
654         $course->fullname = external_format_string($course->fullname, $context->id);
655         $course->shortname = external_format_string($course->shortname, $context->id);
656         list($course->summary, $course->summaryformat) =
657              external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
659         // Compare the values.
660         $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $enrolledincourses[0]['fullname']);
661         $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $enrolledincourses[0]['shortname']);
662         $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $enrolledincourses[0]['summary']);
663         $this->assertEquals($course->fullname, $enrolledincourses[0]['fullname']);
664         $this->assertEquals($course->shortname, $enrolledincourses[0]['shortname']);
665         $this->assertEquals($course->summary, $enrolledincourses[0]['summary']);
666     }
668     /**
669      * Test get_course_enrolment_methods
670      */
671     public function test_get_course_enrolment_methods() {
672         global $DB;
674         $this->resetAfterTest(true);
676         // Get enrolment plugins.
677         $selfplugin = enrol_get_plugin('self');
678         $this->assertNotEmpty($selfplugin);
679         $manualplugin = enrol_get_plugin('manual');
680         $this->assertNotEmpty($manualplugin);
682         $studentrole = $DB->get_record('role', array('shortname'=>'student'));
683         $this->assertNotEmpty($studentrole);
685         $course1 = self::getDataGenerator()->create_course();
686         $coursedata = new stdClass();
687         $coursedata->visible = 0;
688         $course2 = self::getDataGenerator()->create_course($coursedata);
690         // Add enrolment methods for course.
691         $instanceid1 = $selfplugin->add_instance($course1, array('status' => ENROL_INSTANCE_ENABLED,
692                                                                 'name' => 'Test instance 1',
693                                                                 'customint6' => 1,
694                                                                 'roleid' => $studentrole->id));
695         $instanceid2 = $selfplugin->add_instance($course1, array('status' => ENROL_INSTANCE_DISABLED,
696                                                                 'name' => 'Test instance 2',
697                                                                 'roleid' => $studentrole->id));
699         $instanceid3 = $manualplugin->add_instance($course1, array('status' => ENROL_INSTANCE_ENABLED,
700                                                                 'name' => 'Test instance 3'));
702         $enrolmentmethods = $DB->get_records('enrol', array('courseid' => $course1->id, 'status' => ENROL_INSTANCE_ENABLED));
703         $this->assertCount(2, $enrolmentmethods);
705         $this->setAdminUser();
707         // Check if information is returned.
708         $enrolmentmethods = core_enrol_external::get_course_enrolment_methods($course1->id);
709         $enrolmentmethods = external_api::clean_returnvalue(core_enrol_external::get_course_enrolment_methods_returns(),
710                                                             $enrolmentmethods);
711         // Enrolment information is currently returned by self enrolment plugin, so count == 1.
712         // This should be changed as we implement get_enrol_info() for other enrolment plugins.
713         $this->assertCount(1, $enrolmentmethods);
715         $enrolmentmethod = $enrolmentmethods[0];
716         $this->assertEquals($course1->id, $enrolmentmethod['courseid']);
717         $this->assertEquals('self', $enrolmentmethod['type']);
718         $this->assertTrue($enrolmentmethod['status']);
719         $this->assertFalse(isset($enrolmentmethod['wsfunction']));
721         $instanceid4 = $selfplugin->add_instance($course2, array('status' => ENROL_INSTANCE_ENABLED,
722                                                                 'name' => 'Test instance 4',
723                                                                 'roleid' => $studentrole->id,
724                                                                 'customint6' => 1,
725                                                                 'password' => 'test'));
726         $enrolmentmethods = core_enrol_external::get_course_enrolment_methods($course2->id);
727         $enrolmentmethods = external_api::clean_returnvalue(core_enrol_external::get_course_enrolment_methods_returns(),
728                                                             $enrolmentmethods);
729         $this->assertCount(1, $enrolmentmethods);
731         $enrolmentmethod = $enrolmentmethods[0];
732         $this->assertEquals($course2->id, $enrolmentmethod['courseid']);
733         $this->assertEquals('self', $enrolmentmethod['type']);
734         $this->assertTrue($enrolmentmethod['status']);
735         $this->assertEquals('enrol_self_get_instance_info', $enrolmentmethod['wsfunction']);
737         // Try to retrieve information using a normal user for a hidden course.
738         $user = self::getDataGenerator()->create_user();
739         $this->setUser($user);
740         try {
741             core_enrol_external::get_course_enrolment_methods($course2->id);
742         } catch (moodle_exception $e) {
743             $this->assertEquals('coursehidden', $e->errorcode);
744         }
745     }
747     public function get_enrolled_users_setup($capability) {
748         global $USER;
750         $this->resetAfterTest(true);
752         $return = new stdClass();
754         $return->course = self::getDataGenerator()->create_course();
755         $return->user1 = self::getDataGenerator()->create_user();
756         $return->user2 = self::getDataGenerator()->create_user();
757         $return->user3 = self::getDataGenerator()->create_user();
758         $this->setUser($return->user3);
760         // Set the required capabilities by the external function.
761         $return->context = context_course::instance($return->course->id);
762         $return->roleid = $this->assignUserCapability($capability, $return->context->id);
763         $this->assignUserCapability('moodle/user:viewdetails', $return->context->id, $return->roleid);
765         // Enrol the users in the course.
766         $this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
767         $this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
768         $this->getDataGenerator()->enrol_user($return->user3->id, $return->course->id, $return->roleid, 'manual');
770         return $return;
771     }
773     /**
774      * Test get_enrolled_users from core_enrol_external without additional
775      * parameters.
776      */
777     public function test_get_enrolled_users_without_parameters() {
778         $capability = 'moodle/course:viewparticipants';
779         $data = $this->get_enrolled_users_setup($capability);
781         // Call the external function.
782         $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
784         // We need to execute the return values cleaning process to simulate the web service server.
785         $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
787         // Check the result set.
788         $this->assertEquals(3, count($enrolledusers));
789         $this->assertArrayHasKey('email', $enrolledusers[0]);
790     }
792     /**
793      * Test get_enrolled_users from core_enrol_external with some parameters set.
794      */
795     public function test_get_enrolled_users_with_parameters() {
796         $capability = 'moodle/course:viewparticipants';
797         $data = $this->get_enrolled_users_setup($capability);
799         // Call the function with some parameters set.
800         $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id, array(
801             array('name' => 'limitfrom', 'value' => 2),
802             array('name' => 'limitnumber', 'value' => 1),
803             array('name' => 'userfields', 'value' => 'id')
804         ));
806         // We need to execute the return values cleaning process to simulate the web service server.
807         $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
809         // Check the result set, we should only get the 3rd result, which is $user3.
810         $this->assertCount(1, $enrolledusers);
811         $this->assertEquals($data->user3->id, $enrolledusers[0]['id']);
812         $this->assertArrayHasKey('id', $enrolledusers[0]);
813         $this->assertArrayNotHasKey('email', $enrolledusers[0]);
814     }
817     /**
818      * Test get_enrolled_users last course access.
819      */
820     public function test_get_enrolled_users_including_lastcourseaccess() {
821         global $DB;
822         $capability = 'moodle/course:viewparticipants';
823         $data = $this->get_enrolled_users_setup($capability);
825         // Call the external function.
826         $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
827         // We need to execute the return values cleaning process to simulate the web service server.
828         $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
830         // Check the result set.
831         $this->assertEquals(3, count($enrolledusers));
832         $this->assertArrayHasKey('email', $enrolledusers[0]);
833         $this->assertEquals(0, $enrolledusers[0]['lastcourseaccess']);
834         $this->assertEquals(0, $enrolledusers[1]['lastcourseaccess']);
835         $this->assertNotEquals(0, $enrolledusers[2]['lastcourseaccess']);   // We forced an access to the course via setUser.
837         // Force last access.
838         $timenow = time();
839         $lastaccess = array(
840             'userid' => $enrolledusers[0]['id'],
841             'courseid' => $data->course->id,
842             'timeaccess' => $timenow
843         );
844         $DB->insert_record('user_lastaccess', $lastaccess);
846         $enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
847         $enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
849         // Check the result set.
850         $this->assertEquals(3, count($enrolledusers));
851         $this->assertEquals($timenow, $enrolledusers[0]['lastcourseaccess']);
852         $this->assertEquals(0, $enrolledusers[1]['lastcourseaccess']);
853         $this->assertNotEquals(0, $enrolledusers[2]['lastcourseaccess']);
854     }
856     /**
857      * Test get_enrolled_users from core_enrol_external with capability to
858      * viewparticipants removed.
859      */
860     public function test_get_enrolled_users_without_capability() {
861         $capability = 'moodle/course:viewparticipants';
862         $data = $this->get_enrolled_users_setup($capability);
864         // Call without required capability.
865         $this->unassignUserCapability($capability, $data->context->id, $data->roleid);
866         $this->expectException(moodle_exception::class);
867         $categories = core_enrol_external::get_enrolled_users($data->course->id);
868     }
870     public function get_enrolled_users_with_capability_setup($capability) {
871         global $USER, $DB;
873         $this->resetAfterTest(true);
875         $return = new stdClass();
877         // Create the course and fetch its context.
878         $return->course = self::getDataGenerator()->create_course();
879         $context = context_course::instance($return->course->id);
881         // Create one teacher, and two students.
882         $return->teacher = self::getDataGenerator()->create_user();
883         $return->student1 = self::getDataGenerator()->create_user();
884         $return->student2 = self::getDataGenerator()->create_user();
886         // Create a new student role based on the student archetype but with the capability prohibitted.
887         $fakestudentroleid = create_role('Fake student role', 'fakestudent', 'Fake student role', 'student');
888         assign_capability($capability, CAP_PROHIBIT, $fakestudentroleid, $context->id);
890         // Enrol all of the users in the course.
891         // * 'teacher'  is an editing teacher.
892         // * 'student1' is a standard student.
893         // * 'student2' is a student with the capability prohibitted.
894         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
895         $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
896         $this->getDataGenerator()->enrol_user($return->teacher->id, $return->course->id, $editingteacherroleid);
897         $this->getDataGenerator()->enrol_user($return->student1->id, $return->course->id, $studentroleid);
898         $this->getDataGenerator()->enrol_user($return->student2->id, $return->course->id, $fakestudentroleid);
900         // Log in as the teacher.
901         $this->setUser($return->teacher);
903         // Clear caches.
904         accesslib_clear_all_caches_for_unit_testing();
906         return $return;
907     }
909     /**
910      * Test get_enrolled_users_with_capability without additional paramaters.
911      */
912     public function test_get_enrolled_users_with_capability_without_parameters() {
913         $capability = 'moodle/course:viewparticipants';
914         $data = $this->get_enrolled_users_with_capability_setup($capability);
916         $result = core_enrol_external::get_enrolled_users_with_capability(
917             array(
918                 'coursecapabilities' => array(
919                     'courseid' => $data->course->id,
920                     'capabilities' => array(
921                         $capability,
922                     ),
923                 ),
924             ),
925             array()
926         );
928         // We need to execute the return values cleaning process to simulate the web service server.
929         $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
931         // Check an array containing the expected user for the course capability is returned.
932         $expecteduserlist = $result[0];
933         $this->assertEquals($data->course->id, $expecteduserlist['courseid']);
934         $this->assertEquals($capability, $expecteduserlist['capability']);
935         $this->assertEquals(2, count($expecteduserlist['users']));
936     }
938     /**
939      * Test get_enrolled_users_with_capability
940      */
941     public function test_get_enrolled_users_with_capability_with_parameters () {
942         $capability = 'moodle/course:viewparticipants';
943         $data = $this->get_enrolled_users_with_capability_setup($capability);
945         $result = core_enrol_external::get_enrolled_users_with_capability(
946             array(
947                 'coursecapabilities' => array(
948                     'courseid' => $data->course->id,
949                     'capabilities' => array(
950                         $capability,
951                     ),
952                 ),
953             ),
954             array(
955                 array('name' => 'limitfrom', 'value' => 1),
956                 array('name' => 'limitnumber', 'value' => 1),
957                 array('name' => 'userfields', 'value' => 'id')
958             )
959         );
961         // We need to execute the return values cleaning process to simulate the web service server.
962         $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
964         // Check an array containing the expected user for the course capability is returned.
965         $expecteduserlist = $result[0]['users'];
966         $expecteduser = reset($expecteduserlist);
967         $this->assertEquals(1, count($expecteduserlist));
968         $this->assertEquals($data->student1->id, $expecteduser['id']);
969     }
971     /**
972      * Test get_enrolled_users last course access.
973      */
974     public function test_get_enrolled_users_with_capability_including_lastcourseaccess() {
975         global $DB;
976         $capability = 'moodle/course:viewparticipants';
977         $data = $this->get_enrolled_users_with_capability_setup($capability);
979         $parameters = array(
980             'coursecapabilities' => array(
981                 'courseid' => $data->course->id,
982                 'capabilities' => array(
983                     $capability,
984                 ),
985             ),
986         );
988         $result = core_enrol_external::get_enrolled_users_with_capability($parameters, array());
989         // We need to execute the return values cleaning process to simulate the web service server.
990         $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
992         // Check an array containing the expected user for the course capability is returned.
993         $expecteduserlist = $result[0];
994         $this->assertEquals($data->course->id, $expecteduserlist['courseid']);
995         $this->assertEquals($capability, $expecteduserlist['capability']);
996         $this->assertEquals(2, count($expecteduserlist['users']));
997         // We forced an access to the course via setUser.
998         $this->assertNotEquals(0, $expecteduserlist['users'][0]['lastcourseaccess']);
999         $this->assertEquals(0, $expecteduserlist['users'][1]['lastcourseaccess']);
1001         // Force last access.
1002         $timenow = time();
1003         $lastaccess = array(
1004             'userid' => $expecteduserlist['users'][1]['id'],
1005             'courseid' => $data->course->id,
1006             'timeaccess' => $timenow
1007         );
1008         $DB->insert_record('user_lastaccess', $lastaccess);
1010         $result = core_enrol_external::get_enrolled_users_with_capability($parameters, array());
1011         // We need to execute the return values cleaning process to simulate the web service server.
1012         $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
1014         // Check the result set.
1015         $expecteduserlist = $result[0];
1016         $this->assertEquals(2, count($expecteduserlist['users']));
1017         $this->assertNotEquals(0, $expecteduserlist['users'][0]['lastcourseaccess']);
1018         $this->assertEquals($timenow, $expecteduserlist['users'][1]['lastcourseaccess']);
1019     }
1021     /**
1022      * Test for core_enrol_external::edit_user_enrolment().
1023      */
1024     public function test_edit_user_enrolment() {
1025         global $DB;
1027         $this->resetAfterTest(true);
1028         $datagen = $this->getDataGenerator();
1030         /** @var enrol_manual_plugin $manualplugin */
1031         $manualplugin = enrol_get_plugin('manual');
1032         $this->assertNotNull($manualplugin);
1034         $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1035         $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1036         $course = $datagen->create_course();
1037         $user = $datagen->create_user();
1038         $teacher = $datagen->create_user();
1040         $instanceid = null;
1041         $instances = enrol_get_instances($course->id, true);
1042         foreach ($instances as $inst) {
1043             if ($inst->enrol == 'manual') {
1044                 $instanceid = (int)$inst->id;
1045                 break;
1046             }
1047         }
1048         if (empty($instanceid)) {
1049             $instanceid = $manualplugin->add_default_instance($course);
1050             if (empty($instanceid)) {
1051                 $instanceid = $manualplugin->add_instance($course);
1052             }
1053         }
1054         $this->assertNotNull($instanceid);
1056         $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1057         $manualplugin->enrol_user($instance, $user->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1058         $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1059         $ueid = (int)$DB->get_field(
1060             'user_enrolments',
1061             'id',
1062             ['enrolid' => $instance->id, 'userid' => $user->id],
1063             MUST_EXIST
1064         );
1066         // Login as teacher.
1067         $this->setUser($teacher);
1069         $now = new DateTime();
1070         $nowtime = $now->getTimestamp();
1072         // Invalid data.
1073         $data = core_enrol_external::edit_user_enrolment($course->id, $ueid, ENROL_USER_ACTIVE, $nowtime, $nowtime);
1074         $data = external_api::clean_returnvalue(core_enrol_external::edit_user_enrolment_returns(), $data);
1075         $this->assertFalse($data['result']);
1076         $this->assertNotEmpty($data['errors']);
1078         // Valid data.
1079         $nextmonth = clone($now);
1080         $nextmonth->add(new DateInterval('P1M'));
1081         $nextmonthtime = $nextmonth->getTimestamp();
1082         $data = core_enrol_external::edit_user_enrolment($course->id, $ueid, ENROL_USER_ACTIVE, $nowtime, $nextmonthtime);
1083         $data = external_api::clean_returnvalue(core_enrol_external::edit_user_enrolment_returns(), $data);
1084         $this->assertTrue($data['result']);
1085         $this->assertEmpty($data['errors']);
1087         // Check updated user enrolment.
1088         $ue = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST);
1089         $this->assertEquals(ENROL_USER_ACTIVE, $ue->status);
1090         $this->assertEquals($nowtime, $ue->timestart);
1091         $this->assertEquals($nextmonthtime, $ue->timeend);
1093         // Suspend user.
1094         $data = core_enrol_external::edit_user_enrolment($course->id, $ueid, ENROL_USER_SUSPENDED);
1095         $data = external_api::clean_returnvalue(core_enrol_external::edit_user_enrolment_returns(), $data);
1096         $this->assertTrue($data['result']);
1097         $this->assertEmpty($data['errors']);
1099         // Check updated user enrolment.
1100         $ue = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST);
1101         $this->assertEquals(ENROL_USER_SUSPENDED, $ue->status);
1102     }
1104     /**
1105      * dataProvider for test_submit_user_enrolment_form().
1106      */
1107     public function submit_user_enrolment_form_provider() {
1108         $now = new DateTime();
1110         $nextmonth = clone($now);
1111         $nextmonth->add(new DateInterval('P1M'));
1113         return [
1114             'Invalid data' => [
1115                 'customdata' => [
1116                     'status' => ENROL_USER_ACTIVE,
1117                     'timestart' => [
1118                         'day' => $now->format('j'),
1119                         'month' => $now->format('n'),
1120                         'year' => $now->format('Y'),
1121                         'hour' => $now->format('G'),
1122                         'minute' => 0,
1123                         'enabled' => 1,
1124                     ],
1125                     'timeend' => [
1126                         'day' => $now->format('j'),
1127                         'month' => $now->format('n'),
1128                         'year' => $now->format('Y'),
1129                         'hour' => $now->format('G'),
1130                         'minute' => 0,
1131                         'enabled' => 1,
1132                     ],
1133                 ],
1134                 'expectedresult' => false,
1135                 'validationerror' => true,
1136             ],
1137             'Valid data' => [
1138                 'customdata' => [
1139                     'status' => ENROL_USER_ACTIVE,
1140                     'timestart' => [
1141                         'day' => $now->format('j'),
1142                         'month' => $now->format('n'),
1143                         'year' => $now->format('Y'),
1144                         'hour' => $now->format('G'),
1145                         'minute' => 0,
1146                         'enabled' => 1,
1147                     ],
1148                     'timeend' => [
1149                         'day' => $nextmonth->format('j'),
1150                         'month' => $nextmonth->format('n'),
1151                         'year' => $nextmonth->format('Y'),
1152                         'hour' => $nextmonth->format('G'),
1153                         'minute' => 0,
1154                         'enabled' => 1,
1155                     ],
1156                 ],
1157                 'expectedresult' => true,
1158                 'validationerror' => false
1159             ],
1160             'Suspend user' => [
1161                 'customdata' => [
1162                     'status' => ENROL_USER_SUSPENDED,
1163                 ],
1164                 'expectedresult' => true,
1165                 'validationerror' => false
1166             ],
1167         ];
1168     }
1170     /**
1171      * @param array $customdata The data we are providing to the webservice.
1172      * @param bool $expectedresult The result we are expecting to receive from the webservice.
1173      * @param bool $validationerror The validationerror we are expecting to receive from the webservice.
1174      * @dataProvider submit_user_enrolment_form_provider
1175      */
1176     public function test_submit_user_enrolment_form($customdata, $expectedresult, $validationerror) {
1177         global $CFG, $DB;
1179         $this->resetAfterTest(true);
1180         $datagen = $this->getDataGenerator();
1182         /** @var enrol_manual_plugin $manualplugin */
1183         $manualplugin = enrol_get_plugin('manual');
1185         $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1186         $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1187         $course = $datagen->create_course();
1188         $user = $datagen->create_user();
1189         $teacher = $datagen->create_user();
1191         $instanceid = null;
1192         $instances = enrol_get_instances($course->id, true);
1193         foreach ($instances as $inst) {
1194             if ($inst->enrol == 'manual') {
1195                 $instanceid = (int)$inst->id;
1196                 break;
1197             }
1198         }
1199         if (empty($instanceid)) {
1200             $instanceid = $manualplugin->add_default_instance($course);
1201             if (empty($instanceid)) {
1202                 $instanceid = $manualplugin->add_instance($course);
1203             }
1204         }
1205         $this->assertNotNull($instanceid);
1207         $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1208         $manualplugin->enrol_user($instance, $user->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1209         $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1210         $ueid = (int) $DB->get_field(
1211                 'user_enrolments',
1212                 'id',
1213                 ['enrolid' => $instance->id, 'userid' => $user->id],
1214                 MUST_EXIST
1215         );
1217         // Login as teacher.
1218         $teacher->ignoresesskey = true;
1219         $this->setUser($teacher);
1221         $formdata = [
1222             'ue'        => $ueid,
1223             'ifilter'   => 0,
1224             'status'    => null,
1225             'timestart' => null,
1226             'duration'  => null,
1227             'timeend'   => null,
1228         ];
1230         $formdata = array_merge($formdata, $customdata);
1232         require_once("$CFG->dirroot/enrol/editenrolment_form.php");
1233         $formdata = enrol_user_enrolment_form::mock_generate_submit_keys($formdata);
1235         $querystring = http_build_query($formdata, '', '&');
1237         $result = external_api::clean_returnvalue(
1238                 core_enrol_external::submit_user_enrolment_form_returns(),
1239                 core_enrol_external::submit_user_enrolment_form($querystring)
1240         );
1242         $this->assertEqualsCanonicalizing(
1243                 ['result' => $expectedresult, 'validationerror' => $validationerror],
1244                 $result);
1246         if ($result['result']) {
1247             $ue = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST);
1248             $this->assertEquals($formdata['status'], $ue->status);
1249         }
1250     }
1252     /**
1253      * Test for core_enrol_external::unenrol_user_enrolment().
1254      */
1255     public function test_unenerol_user_enrolment() {
1256         global $DB;
1258         $this->resetAfterTest(true);
1259         $datagen = $this->getDataGenerator();
1261         /** @var enrol_manual_plugin $manualplugin */
1262         $manualplugin = enrol_get_plugin('manual');
1263         $this->assertNotNull($manualplugin);
1265         $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1266         $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1267         $course = $datagen->create_course();
1268         $user = $datagen->create_user();
1269         $teacher = $datagen->create_user();
1271         $instanceid = null;
1272         $instances = enrol_get_instances($course->id, true);
1273         foreach ($instances as $inst) {
1274             if ($inst->enrol == 'manual') {
1275                 $instanceid = (int)$inst->id;
1276                 break;
1277             }
1278         }
1279         if (empty($instanceid)) {
1280             $instanceid = $manualplugin->add_default_instance($course);
1281             if (empty($instanceid)) {
1282                 $instanceid = $manualplugin->add_instance($course);
1283             }
1284         }
1285         $this->assertNotNull($instanceid);
1287         $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1288         $manualplugin->enrol_user($instance, $user->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1289         $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1290         $ueid = (int)$DB->get_field(
1291             'user_enrolments',
1292             'id',
1293             ['enrolid' => $instance->id, 'userid' => $user->id],
1294             MUST_EXIST
1295         );
1297         // Login as teacher.
1298         $this->setUser($teacher);
1300         // Invalid data by passing invalid ueid.
1301         $data = core_enrol_external::unenrol_user_enrolment(101010);
1302         $data = external_api::clean_returnvalue(core_enrol_external::unenrol_user_enrolment_returns(), $data);
1303         $this->assertFalse($data['result']);
1304         $this->assertNotEmpty($data['errors']);
1306         // Valid data.
1307         $data = core_enrol_external::unenrol_user_enrolment($ueid);
1308         $data = external_api::clean_returnvalue(core_enrol_external::unenrol_user_enrolment_returns(), $data);
1309         $this->assertTrue($data['result']);
1310         $this->assertEmpty($data['errors']);
1312         // Check unenrol user enrolment.
1313         $ue = $DB->count_records('user_enrolments', ['id' => $ueid]);
1314         $this->assertEquals(0, $ue);
1315     }
1317     /**
1318      * Test for core_enrol_external::test_search_users().
1319      */
1320     public function test_search_users() {
1321         global $DB;
1323         $this->resetAfterTest(true);
1324         $datagen = $this->getDataGenerator();
1326         /** @var enrol_manual_plugin $manualplugin */
1327         $manualplugin = enrol_get_plugin('manual');
1328         $this->assertNotNull($manualplugin);
1330         $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
1331         $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
1333         $course1 = $datagen->create_course();
1334         $course2 = $datagen->create_course();
1336         $user1 = $datagen->create_user(['firstname' => 'user 1']);
1337         $user2 = $datagen->create_user(['firstname' => 'user 2']);
1338         $user3 = $datagen->create_user(['firstname' => 'user 3']);
1339         $teacher = $datagen->create_user(['firstname' => 'user 4']);
1341         $instanceid = null;
1342         $instances = enrol_get_instances($course1->id, true);
1343         foreach ($instances as $inst) {
1344             if ($inst->enrol == 'manual') {
1345                 $instanceid = (int)$inst->id;
1346                 break;
1347             }
1348         }
1349         if (empty($instanceid)) {
1350             $instanceid = $manualplugin->add_default_instance($course1);
1351             if (empty($instanceid)) {
1352                 $instanceid = $manualplugin->add_instance($course1);
1353             }
1354         }
1355         $this->assertNotNull($instanceid);
1357         $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
1358         $manualplugin->enrol_user($instance, $user1->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1359         $manualplugin->enrol_user($instance, $user2->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1360         $manualplugin->enrol_user($instance, $user3->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
1361         $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
1363         $this->setUser($teacher);
1365         // Search for users in a course with enrolled users.
1366         $result = core_enrol_external::search_users($course1->id, 'user', true, 0, 30);
1367         $this->assertCount(4, $result);
1369         $this->expectException('moodle_exception');
1370         // Search for users in a course without any enrolled users, shouldn't return anything.
1371         $result = core_enrol_external::search_users($course2->id, 'user', true, 0, 30);
1372         $this->assertCount(0, $result);
1374         // Search for invalid first name.
1375         $result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30);
1376         $this->assertCount(0, $result);
1378         // Test pagination, it should return only 3 users.
1379         $result = core_enrol_external::search_users($course1->id, 'user', true, 0, 3);
1380         $this->assertCount(3, $result);
1382         // Test pagination, it should return only 3 users.
1383         $result = core_enrol_external::search_users($course1->id, 'user 1', true, 0, 1);
1384         $result = $result[0];
1385         $this->assertEquals($user1->id, $result['id']);
1386         $this->assertEquals($user1->email, $result['email']);
1387         $this->assertEquals(fullname($user1), $result['fullname']);
1389         $this->setUser($user1);
1391         // Search for users in a course with enrolled users.
1392         $result = core_enrol_external::search_users($course1->id, 'user', true, 0, 30);
1393         $this->assertCount(4, $result);
1395         $this->expectException('moodle_exception');
1396         // Search for users in a course without any enrolled users, shouldn't return anything.
1397         $result = core_enrol_external::search_users($course2->id, 'user', true, 0, 30);
1398         $this->assertCount(0, $result);
1400         // Search for invalid first name.
1401         $result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30);
1402         $this->assertCount(0, $result);
1403     }