1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * User external PHPunit tests
19 *
20 * @package    core_user
21 * @category   external
22 * @copyright  2012 Jerome Mouneyrac
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 * @since Moodle 2.4
25 */
26
27defined('MOODLE_INTERNAL') || die();
28
29global $CFG;
30
31require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32require_once($CFG->dirroot . '/user/externallib.php');
33require_once($CFG->dirroot . '/files/externallib.php');
34
35class core_user_externallib_testcase extends externallib_advanced_testcase {
36
37    /**
38     * Test get_users
39     */
40    public function test_get_users() {
41        global $USER, $CFG;
42
43        $this->resetAfterTest(true);
44
45        $course = self::getDataGenerator()->create_course();
46
47        $user1 = array(
48            'username' => 'usernametest1',
49            'idnumber' => 'idnumbertest1',
50            'firstname' => 'First Name User Test 1',
51            'lastname' => 'Last Name User Test 1',
52            'email' => 'usertest1@example.com',
53            'address' => '2 Test Street Perth 6000 WA',
54            'phone1' => '01010101010',
55            'phone2' => '02020203',
56            'icq' => 'testuser1',
57            'skype' => 'testuser1',
58            'yahoo' => 'testuser1',
59            'aim' => 'testuser1',
60            'msn' => 'testuser1',
61            'department' => 'Department of user 1',
62            'institution' => 'Institution of user 1',
63            'description' => 'This is a description for user 1',
64            'descriptionformat' => FORMAT_MOODLE,
65            'city' => 'Perth',
66            'url' => 'http://moodle.org',
67            'country' => 'AU'
68            );
69
70        $user1 = self::getDataGenerator()->create_user($user1);
71        set_config('usetags', 1);
72        require_once($CFG->dirroot . '/user/editlib.php');
73        $user1->interests = array('Cinema', 'Tennis', 'Dance', 'Guitar', 'Cooking');
74        useredit_update_interests($user1, $user1->interests);
75
76        $user2 = self::getDataGenerator()->create_user(
77                array('username' => 'usernametest2', 'idnumber' => 'idnumbertest2'));
78
79        $generatedusers = array();
80        $generatedusers[$user1->id] = $user1;
81        $generatedusers[$user2->id] = $user2;
82
83        $context = context_course::instance($course->id);
84        $roleid = $this->assignUserCapability('moodle/user:viewdetails', $context->id);
85
86        // Enrol the users in the course.
87        $this->getDataGenerator()->enrol_user($user1->id, $course->id, $roleid);
88        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleid);
89        $this->getDataGenerator()->enrol_user($USER->id, $course->id, $roleid);
90
91        // call as admin and receive all possible fields.
92        $this->setAdminUser();
93
94        $searchparams = array(
95            array('key' => 'invalidkey', 'value' => 'invalidkey'),
96            array('key' => 'email', 'value' => $user1->email),
97            array('key' => 'firstname', 'value' => $user1->firstname));
98
99        // Call the external function.
100        $result = core_user_external::get_users($searchparams);
101
102        // We need to execute the return values cleaning process to simulate the web service server
103        $result = external_api::clean_returnvalue(core_user_external::get_users_returns(), $result);
104
105        // Check we retrieve the good total number of enrolled users + no error on capability.
106        $expectedreturnedusers = 1;
107        $returnedusers = $result['users'];
108        $this->assertEquals($expectedreturnedusers, count($returnedusers));
109
110        foreach($returnedusers as $returneduser) {
111            $generateduser = ($returneduser['id'] == $USER->id) ?
112                                $USER : $generatedusers[$returneduser['id']];
113            $this->assertEquals($generateduser->username, $returneduser['username']);
114            if (!empty($generateduser->idnumber)) {
115                $this->assertEquals($generateduser->idnumber, $returneduser['idnumber']);
116            }
117            $this->assertEquals($generateduser->firstname, $returneduser['firstname']);
118            $this->assertEquals($generateduser->lastname, $returneduser['lastname']);
119            if ($generateduser->email != $USER->email) { // Don't check the tmp modified $USER email.
120                $this->assertEquals($generateduser->email, $returneduser['email']);
121            }
122            if (!empty($generateduser->address)) {
123                $this->assertEquals($generateduser->address, $returneduser['address']);
124            }
125            if (!empty($generateduser->phone1)) {
126                $this->assertEquals($generateduser->phone1, $returneduser['phone1']);
127            }
128            if (!empty($generateduser->phone2)) {
129                $this->assertEquals($generateduser->phone2, $returneduser['phone2']);
130            }
131            if (!empty($generateduser->icq)) {
132                $this->assertEquals($generateduser->icq, $returneduser['icq']);
133            }
134            if (!empty($generateduser->skype)) {
135                $this->assertEquals($generateduser->skype, $returneduser['skype']);
136            }
137            if (!empty($generateduser->yahoo)) {
138                $this->assertEquals($generateduser->yahoo, $returneduser['yahoo']);
139            }
140            if (!empty($generateduser->aim)) {
141                $this->assertEquals($generateduser->aim, $returneduser['aim']);
142            }
143            if (!empty($generateduser->msn)) {
144                $this->assertEquals($generateduser->msn, $returneduser['msn']);
145            }
146            if (!empty($generateduser->department)) {
147                $this->assertEquals($generateduser->department, $returneduser['department']);
148            }
149            if (!empty($generateduser->institution)) {
150                $this->assertEquals($generateduser->institution, $returneduser['institution']);
151            }
152            if (!empty($generateduser->description)) {
153                $this->assertEquals($generateduser->description, $returneduser['description']);
154            }
155            if (!empty($generateduser->descriptionformat)) {
156                $this->assertEquals(FORMAT_HTML, $returneduser['descriptionformat']);
157            }
158            if (!empty($generateduser->city)) {
159                $this->assertEquals($generateduser->city, $returneduser['city']);
160            }
161            if (!empty($generateduser->country)) {
162                $this->assertEquals($generateduser->country, $returneduser['country']);
163            }
164            if (!empty($generateduser->url)) {
165                $this->assertEquals($generateduser->url, $returneduser['url']);
166            }
167            if (!empty($CFG->usetags) and !empty($generateduser->interests)) {
168                $this->assertEquals(implode(', ', $generateduser->interests), $returneduser['interests']);
169            }
170        }
171
172        // Test the invalid key warning.
173        $warnings = $result['warnings'];
174        $this->assertEquals(count($warnings), 1);
175        $warning = array_pop($warnings);
176        $this->assertEquals($warning['item'], 'invalidkey');
177        $this->assertEquals($warning['warningcode'], 'invalidfieldparameter');
178
179        // Test sending twice the same search field.
180        try {
181            $searchparams = array(
182            array('key' => 'firstname', 'value' => 'Canard'),
183            array('key' => 'email', 'value' => $user1->email),
184            array('key' => 'firstname', 'value' => $user1->firstname));
185
186            // Call the external function.
187            $result = core_user_external::get_users($searchparams);
188            $this->fail('Expecting \'keyalreadyset\' moodle_exception to be thrown.');
189        } catch (moodle_exception $e) {
190            $this->assertEquals('keyalreadyset', $e->errorcode);
191        } catch (Exception $e) {
192            $this->fail('Expecting \'keyalreadyset\' moodle_exception to be thrown.');
193        }
194    }
195
196    /**
197     * Test get_users_by_field
198     */
199    public function test_get_users_by_field() {
200        global $USER, $CFG;
201
202        $this->resetAfterTest(true);
203
204        $course = self::getDataGenerator()->create_course();
205        $user1 = array(
206            'username' => 'usernametest1',
207            'idnumber' => 'idnumbertest1',
208            'firstname' => 'First Name User Test 1',
209            'lastname' => 'Last Name User Test 1',
210            'email' => 'usertest1@example.com',
211            'address' => '2 Test Street Perth 6000 WA',
212            'phone1' => '01010101010',
213            'phone2' => '02020203',
214            'icq' => 'testuser1',
215            'skype' => 'testuser1',
216            'yahoo' => 'testuser1',
217            'aim' => 'testuser1',
218            'msn' => 'testuser1',
219            'department' => 'Department of user 1',
220            'institution' => 'Institution of user 1',
221            'description' => 'This is a description for user 1',
222            'descriptionformat' => FORMAT_MOODLE,
223            'city' => 'Perth',
224            'url' => 'http://moodle.org',
225            'country' => 'AU',
226        );
227        $user1 = self::getDataGenerator()->create_user($user1);
228        if (!empty($CFG->usetags)) {
229            require_once($CFG->dirroot . '/user/editlib.php');
230            $user1->interests = array('Cinema', 'Tennis', 'Dance', 'Guitar', 'Cooking');
231            useredit_update_interests($user1, $user1->interests);
232        }
233        $user2 = self::getDataGenerator()->create_user(
234                array('username' => 'usernametest2', 'idnumber' => 'idnumbertest2'));
235
236        $generatedusers = array();
237        $generatedusers[$user1->id] = $user1;
238        $generatedusers[$user2->id] = $user2;
239
240        $context = context_course::instance($course->id);
241        $roleid = $this->assignUserCapability('moodle/user:viewdetails', $context->id);
242
243        // Enrol the users in the course.
244        $this->getDataGenerator()->enrol_user($user1->id, $course->id, $roleid, 'manual');
245        $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleid, 'manual');
246        $this->getDataGenerator()->enrol_user($USER->id, $course->id, $roleid, 'manual');
247
248        // call as admin and receive all possible fields.
249        $this->setAdminUser();
250
251        $fieldstosearch = array('id', 'idnumber', 'username', 'email');
252
253        foreach ($fieldstosearch as $fieldtosearch) {
254
255            // Call the external function.
256            $returnedusers = core_user_external::get_users_by_field($fieldtosearch,
257                        array($USER->{$fieldtosearch}, $user1->{$fieldtosearch}, $user2->{$fieldtosearch}));
258            $returnedusers = external_api::clean_returnvalue(core_user_external::get_users_by_field_returns(), $returnedusers);
259
260            // Expected result differ following the searched field
261            // Admin user in the PHPunit framework doesn't have an idnumber.
262            if ($fieldtosearch == 'idnumber') {
263                $expectedreturnedusers = 2;
264            } else {
265                $expectedreturnedusers = 3;
266            }
267
268            // Check we retrieve the good total number of enrolled users + no error on capability.
269            $this->assertEquals($expectedreturnedusers, count($returnedusers));
270
271            foreach($returnedusers as $returneduser) {
272                $generateduser = ($returneduser['id'] == $USER->id) ?
273                                    $USER : $generatedusers[$returneduser['id']];
274                $this->assertEquals($generateduser->username, $returneduser['username']);
275                if (!empty($generateduser->idnumber)) {
276                    $this->assertEquals($generateduser->idnumber, $returneduser['idnumber']);
277                }
278                $this->assertEquals($generateduser->firstname, $returneduser['firstname']);
279                $this->assertEquals($generateduser->lastname, $returneduser['lastname']);
280                if ($generateduser->email != $USER->email) { //don't check the tmp modified $USER email
281                    $this->assertEquals($generateduser->email, $returneduser['email']);
282                }
283                if (!empty($generateduser->address)) {
284                    $this->assertEquals($generateduser->address, $returneduser['address']);
285                }
286                if (!empty($generateduser->phone1)) {
287                    $this->assertEquals($generateduser->phone1, $returneduser['phone1']);
288                }
289                if (!empty($generateduser->phone2)) {
290                    $this->assertEquals($generateduser->phone2, $returneduser['phone2']);
291                }
292                if (!empty($generateduser->icq)) {
293                    $this->assertEquals($generateduser->icq, $returneduser['icq']);
294                }
295                if (!empty($generateduser->skype)) {
296                    $this->assertEquals($generateduser->skype, $returneduser['skype']);
297                }
298                if (!empty($generateduser->yahoo)) {
299                    $this->assertEquals($generateduser->yahoo, $returneduser['yahoo']);
300                }
301                if (!empty($generateduser->aim)) {
302                    $this->assertEquals($generateduser->aim, $returneduser['aim']);
303                }
304                if (!empty($generateduser->msn)) {
305                    $this->assertEquals($generateduser->msn, $returneduser['msn']);
306                }
307                if (!empty($generateduser->department)) {
308                    $this->assertEquals($generateduser->department, $returneduser['department']);
309                }
310                if (!empty($generateduser->institution)) {
311                    $this->assertEquals($generateduser->institution, $returneduser['institution']);
312                }
313                if (!empty($generateduser->description)) {
314                    $this->assertEquals($generateduser->description, $returneduser['description']);
315                }
316                if (!empty($generateduser->descriptionformat) and isset($returneduser['descriptionformat'])) {
317                    $this->assertEquals($generateduser->descriptionformat, $returneduser['descriptionformat']);
318                }
319                if (!empty($generateduser->city)) {
320                    $this->assertEquals($generateduser->city, $returneduser['city']);
321                }
322                if (!empty($generateduser->country)) {
323                    $this->assertEquals($generateduser->country, $returneduser['country']);
324                }
325                if (!empty($generateduser->url)) {
326                    $this->assertEquals($generateduser->url, $returneduser['url']);
327                }
328                if (!empty($CFG->usetags) and !empty($generateduser->interests)) {
329                    $this->assertEquals(implode(', ', $generateduser->interests), $returneduser['interests']);
330                }
331                // Default language and no theme were used for the user.
332                $this->assertEquals($CFG->lang, $returneduser['lang']);
333                $this->assertEmpty($returneduser['theme']);
334            }
335        }
336
337        // Test that no result are returned for search by username if we are not admin
338        $this->setGuestUser();
339
340        // Call the external function.
341        $returnedusers = core_user_external::get_users_by_field('username',
342                    array($USER->username, $user1->username, $user2->username));
343        $returnedusers = external_api::clean_returnvalue(core_user_external::get_users_by_field_returns(), $returnedusers);
344
345        // Only the own $USER username should be returned
346        $this->assertEquals(1, count($returnedusers));
347
348        // And finally test as one of the enrolled users.
349        $this->setUser($user1);
350
351        // Call the external function.
352        $returnedusers = core_user_external::get_users_by_field('username',
353            array($USER->username, $user1->username, $user2->username));
354        $returnedusers = external_api::clean_returnvalue(core_user_external::get_users_by_field_returns(), $returnedusers);
355
356        // Only the own $USER username should be returned still.
357        $this->assertEquals(1, count($returnedusers));
358    }
359
360    public function get_course_user_profiles_setup($capability) {
361        global $USER, $CFG;
362
363        $this->resetAfterTest(true);
364
365        $return = new stdClass();
366
367        // Create the course and fetch its context.
368        $return->course = self::getDataGenerator()->create_course();
369        $return->user1 = array(
370            'username' => 'usernametest1',
371            'idnumber' => 'idnumbertest1',
372            'firstname' => 'First Name User Test 1',
373            'lastname' => 'Last Name User Test 1',
374            'email' => 'usertest1@example.com',
375            'address' => '2 Test Street Perth 6000 WA',
376            'phone1' => '01010101010',
377            'phone2' => '02020203',
378            'icq' => 'testuser1',
379            'skype' => 'testuser1',
380            'yahoo' => 'testuser1',
381            'aim' => 'testuser1',
382            'msn' => 'testuser1',
383            'department' => 'Department of user 1',
384            'institution' => 'Institution of user 1',
385            'description' => 'This is a description for user 1',
386            'descriptionformat' => FORMAT_MOODLE,
387            'city' => 'Perth',
388            'url' => 'http://moodle.org',
389            'country' => 'AU'
390        );
391        $return->user1 = self::getDataGenerator()->create_user($return->user1);
392        if (!empty($CFG->usetags)) {
393            require_once($CFG->dirroot . '/user/editlib.php');
394            $return->user1->interests = array('Cinema', 'Tennis', 'Dance', 'Guitar', 'Cooking');
395            useredit_update_interests($return->user1, $return->user1->interests);
396        }
397        $return->user2 = self::getDataGenerator()->create_user();
398
399        $context = context_course::instance($return->course->id);
400        $return->roleid = $this->assignUserCapability($capability, $context->id);
401
402        // Enrol the users in the course.
403        $this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
404        $this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
405        $this->getDataGenerator()->enrol_user($USER->id, $return->course->id, $return->roleid, 'manual');
406
407        return $return;
408    }
409
410    /**
411     * Test get_course_user_profiles
412     */
413    public function test_get_course_user_profiles() {
414        global $USER, $CFG;
415
416        $this->resetAfterTest(true);
417
418        $data = $this->get_course_user_profiles_setup('moodle/user:viewdetails');
419
420        // Call the external function.
421        $enrolledusers = core_user_external::get_course_user_profiles(array(
422                    array('userid' => $USER->id, 'courseid' => $data->course->id)));
423
424        // We need to execute the return values cleaning process to simulate the web service server.
425        $enrolledusers = external_api::clean_returnvalue(core_user_external::get_course_user_profiles_returns(), $enrolledusers);
426
427        // Check we retrieve the good total number of enrolled users + no error on capability.
428        $this->assertEquals(1, count($enrolledusers));
429    }
430
431    public function test_get_user_course_profile_as_admin() {
432        global $USER, $CFG;
433
434        global $USER, $CFG;
435
436        $this->resetAfterTest(true);
437
438        $data = $this->get_course_user_profiles_setup('moodle/user:viewdetails');
439
440        // Do the same call as admin to receive all possible fields.
441        $this->setAdminUser();
442        $USER->email = "admin@example.com";
443
444        // Call the external function.
445        $enrolledusers = core_user_external::get_course_user_profiles(array(
446            array('userid' => $data->user1->id, 'courseid' => $data->course->id)));
447
448        // We need to execute the return values cleaning process to simulate the web service server.
449        $enrolledusers = external_api::clean_returnvalue(core_user_external::get_course_user_profiles_returns(), $enrolledusers);
450
451        foreach($enrolledusers as $enrolleduser) {
452            if ($enrolleduser['username'] == $data->user1->username) {
453                $this->assertEquals($data->user1->idnumber, $enrolleduser['idnumber']);
454                $this->assertEquals($data->user1->firstname, $enrolleduser['firstname']);
455                $this->assertEquals($data->user1->lastname, $enrolleduser['lastname']);
456                $this->assertEquals($data->user1->email, $enrolleduser['email']);
457                $this->assertEquals($data->user1->address, $enrolleduser['address']);
458                $this->assertEquals($data->user1->phone1, $enrolleduser['phone1']);
459                $this->assertEquals($data->user1->phone2, $enrolleduser['phone2']);
460                $this->assertEquals($data->user1->icq, $enrolleduser['icq']);
461                $this->assertEquals($data->user1->skype, $enrolleduser['skype']);
462                $this->assertEquals($data->user1->yahoo, $enrolleduser['yahoo']);
463                $this->assertEquals($data->user1->aim, $enrolleduser['aim']);
464                $this->assertEquals($data->user1->msn, $enrolleduser['msn']);
465                $this->assertEquals($data->user1->department, $enrolleduser['department']);
466                $this->assertEquals($data->user1->institution, $enrolleduser['institution']);
467                $this->assertEquals($data->user1->description, $enrolleduser['description']);
468                $this->assertEquals(FORMAT_HTML, $enrolleduser['descriptionformat']);
469                $this->assertEquals($data->user1->city, $enrolleduser['city']);
470                $this->assertEquals($data->user1->country, $enrolleduser['country']);
471                $this->assertEquals($data->user1->url, $enrolleduser['url']);
472                if (!empty($CFG->usetags)) {
473                    $this->assertEquals(implode(', ', $data->user1->interests), $enrolleduser['interests']);
474                }
475            }
476        }
477    }
478
479    /**
480     * Test create_users
481     */
482    public function test_create_users() {
483        global $DB;
484
485        $this->resetAfterTest(true);
486
487        $user1 = array(
488            'username' => 'usernametest1',
489            'password' => 'Moodle2012!',
490            'idnumber' => 'idnumbertest1',
491            'firstname' => 'First Name User Test 1',
492            'lastname' => 'Last Name User Test 1',
493            'middlename' => 'Middle Name User Test 1',
494            'lastnamephonetic' => '最後のお名前のテスト一号',
495            'firstnamephonetic' => 'お名前のテスト一号',
496            'alternatename' => 'Alternate Name User Test 1',
497            'email' => 'usertest1@example.com',
498            'description' => 'This is a description for user 1',
499            'city' => 'Perth',
500            'country' => 'AU',
501            'preferences' => [[
502                    'type' => 'htmleditor',
503                    'value' => 'atto'
504                ], [
505                    'type' => 'invalidpreference',
506                    'value' => 'abcd'
507                ]
508            ],
509            'department' => 'College of Science',
510            'institution' => 'National Institute of Physics',
511            'phone1' => '01 2345 6789',
512            'maildisplay' => 1,
513            'interests' => 'badminton, basketball, cooking,  '
514        );
515
516        // User with an authentication method done externally.
517        $user2 = array(
518            'username' => 'usernametest2',
519            'firstname' => 'First Name User Test 2',
520            'lastname' => 'Last Name User Test 2',
521            'email' => 'usertest2@example.com',
522            'auth' => 'oauth2'
523        );
524
525        $context = context_system::instance();
526        $roleid = $this->assignUserCapability('moodle/user:create', $context->id);
527        $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
528
529        // Call the external function.
530        $createdusers = core_user_external::create_users(array($user1, $user2));
531
532        // We need to execute the return values cleaning process to simulate the web service server.
533        $createdusers = external_api::clean_returnvalue(core_user_external::create_users_returns(), $createdusers);
534
535        // Check we retrieve the good total number of created users + no error on capability.
536        $this->assertCount(2, $createdusers);
537
538        foreach($createdusers as $createduser) {
539            $dbuser = $DB->get_record('user', array('id' => $createduser['id']));
540
541            if ($createduser['username'] === $user1['username']) {
542                $usertotest = $user1;
543                $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
544                $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
545                // Confirm user interests have been saved.
546                $interests = core_tag_tag::get_item_tags_array('core', 'user', $createduser['id'],
547                        core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
548                // There should be 3 user interests.
549                $this->assertCount(3, $interests);
550
551            } else if ($createduser['username'] === $user2['username']) {
552                $usertotest = $user2;
553            }
554
555            foreach ($dbuser as $property => $value) {
556                if ($property === 'password') {
557                    if ($usertotest === $user2) {
558                        // External auth mechanisms don't store password in the user table.
559                        $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $value);
560                    } else {
561                        // Skip hashed passwords.
562                        continue;
563                    }
564                }
565                // Confirm that the values match.
566                if (isset($usertotest[$property])) {
567                    $this->assertEquals($usertotest[$property], $value);
568                }
569            }
570        }
571
572        // Call without required capability
573        $this->unassignUserCapability('moodle/user:create', $context->id, $roleid);
574        $this->expectException('required_capability_exception');
575        core_user_external::create_users(array($user1));
576    }
577
578    /**
579     * Test create_users with password and createpassword parameter not set.
580     */
581    public function test_create_users_empty_password() {
582        $this->resetAfterTest();
583        $this->setAdminUser();
584
585        $user = [
586            'username' => 'usernametest1',
587            'firstname' => 'First Name User Test 1',
588            'lastname' => 'Last Name User Test 1',
589            'email' => 'usertest1@example.com',
590        ];
591
592        // This should throw an exception because either password or createpassword param must be passed for auth_manual.
593        $this->expectException(invalid_parameter_exception::class);
594        core_user_external::create_users([$user]);
595    }
596
597    /**
598     * Data provider for \core_user_externallib_testcase::test_create_users_with_same_emails().
599     */
600    public function create_users_provider_with_same_emails() {
601        return [
602            'Same emails allowed, same case' => [
603                1, false
604            ],
605            'Same emails allowed, different case' => [
606                1, true
607            ],
608            'Same emails disallowed, same case' => [
609                0, false
610            ],
611            'Same emails disallowed, different case' => [
612                0, true
613            ],
614        ];
615    }
616
617    /**
618     * Test for \core_user_external::create_users() when user using the same email addresses are being created.
619     *
620     * @dataProvider create_users_provider_with_same_emails
621     * @param int $sameemailallowed The value to set for $CFG->allowaccountssameemail.
622     * @param boolean $differentcase Whether to user a different case for the other user.
623     */
624    public function test_create_users_with_same_emails($sameemailallowed, $differentcase) {
625        global $DB;
626
627        $this->resetAfterTest();
628        $this->setAdminUser();
629
630        // Allow multiple users with the same email address.
631        set_config('allowaccountssameemail', $sameemailallowed);
632        $users = [
633            [
634                'username' => 's1',
635                'firstname' => 'Johnny',
636                'lastname' => 'Bravo',
637                'email' => 's1@example.com',
638                'password' => 'Passw0rd!'
639            ],
640            [
641                'username' => 's2',
642                'firstname' => 'John',
643                'lastname' => 'Doe',
644                'email' => $differentcase ? 'S1@EXAMPLE.COM' : 's1@example.com',
645                'password' => 'Passw0rd!'
646            ],
647        ];
648
649        if (!$sameemailallowed) {
650            // This should throw an exception when $CFG->allowaccountssameemail is empty.
651            $this->expectException(invalid_parameter_exception::class);
652        }
653
654        // Create our users.
655        core_user_external::create_users($users);
656
657        // Confirm that the users have been created.
658        list($insql, $params) = $DB->get_in_or_equal(['s1', 's2']);
659        $this->assertEquals(2, $DB->count_records_select('user', 'username ' . $insql, $params));
660    }
661
662    /**
663     * Test create_users with invalid parameters
664     *
665     * @dataProvider data_create_users_invalid_parameter
666     * @param array $data User data to attempt to register.
667     * @param string $expectmessage Expected exception message.
668     */
669    public function test_create_users_invalid_parameter(array $data, $expectmessage) {
670        global $USER, $CFG, $DB;
671
672        $this->resetAfterTest(true);
673        $this->assignUserCapability('moodle/user:create', SYSCONTEXTID);
674
675        $this->expectException('invalid_parameter_exception');
676        $this->expectExceptionMessage($expectmessage);
677
678        core_user_external::create_users(array($data));
679    }
680
681    /**
682     * Data provider for {@link self::test_create_users_invalid_parameter()}.
683     *
684     * @return array
685     */
686    public function data_create_users_invalid_parameter() {
687        return [
688            'blank_username' => [
689                'data' => [
690                    'username' => '',
691                    'firstname' => 'Foo',
692                    'lastname' => 'Bar',
693                    'email' => 'foobar@example.com',
694                    'createpassword' => 1,
695                ],
696                'expectmessage' => 'The field username cannot be blank',
697            ],
698            'blank_firtname' => [
699                'data' => [
700                    'username' => 'foobar',
701                    'firstname' => "\t \n",
702                    'lastname' => 'Bar',
703                    'email' => 'foobar@example.com',
704                    'createpassword' => 1,
705                ],
706                'expectmessage' => 'The field firstname cannot be blank',
707            ],
708            'blank_lastname' => [
709                'data' => [
710                    'username' => 'foobar',
711                    'firstname' => '0',
712                    'lastname' => '   ',
713                    'email' => 'foobar@example.com',
714                    'createpassword' => 1,
715                ],
716                'expectmessage' => 'The field lastname cannot be blank',
717            ],
718            'invalid_email' => [
719                'data' => [
720                    'username' => 'foobar',
721                    'firstname' => 'Foo',
722                    'lastname' => 'Bar',
723                    'email' => '@foobar',
724                    'createpassword' => 1,
725                ],
726                'expectmessage' => 'Email address is invalid',
727            ],
728            'missing_password' => [
729                'data' => [
730                    'username' => 'foobar',
731                    'firstname' => 'Foo',
732                    'lastname' => 'Bar',
733                    'email' => 'foobar@example.com',
734                ],
735                'expectmessage' => 'Invalid password: you must provide a password, or set createpassword',
736            ],
737        ];
738    }
739
740    /**
741     * Test delete_users
742     */
743    public function test_delete_users() {
744        global $USER, $CFG, $DB;
745
746        $this->resetAfterTest(true);
747
748        $user1 = self::getDataGenerator()->create_user();
749        $user2 = self::getDataGenerator()->create_user();
750
751        // Check the users were correctly created.
752        $this->assertEquals(2, $DB->count_records_select('user', 'deleted = 0 AND (id = :userid1 OR id = :userid2)',
753                array('userid1' => $user1->id, 'userid2' => $user2->id)));
754
755        $context = context_system::instance();
756        $roleid = $this->assignUserCapability('moodle/user:delete', $context->id);
757
758        // Call the external function.
759        core_user_external::delete_users(array($user1->id, $user2->id));
760
761        // Check we retrieve no users + no error on capability.
762        $this->assertEquals(0, $DB->count_records_select('user', 'deleted = 0 AND (id = :userid1 OR id = :userid2)',
763                array('userid1' => $user1->id, 'userid2' => $user2->id)));
764
765        // Call without required capability.
766        $this->unassignUserCapability('moodle/user:delete', $context->id, $roleid);
767        $this->expectException('required_capability_exception');
768        core_user_external::delete_users(array($user1->id, $user2->id));
769    }
770
771    /**
772     * Test update_users
773     */
774    public function test_update_users() {
775        global $USER, $CFG, $DB;
776
777        $this->resetAfterTest(true);
778
779        $wsuser = self::getDataGenerator()->create_user();
780        self::setUser($wsuser);
781
782        $context = context_user::instance($USER->id);
783        $contextid = $context->id;
784        $filename = "reddot.png";
785        $filecontent = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38"
786            . "GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
787
788        // Call the files api to create a file.
789        $draftfile = core_files_external::upload($contextid, 'user', 'draft', 0, '/',
790                $filename, $filecontent, null, null);
791        $draftfile = external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
792
793        $draftid = $draftfile['itemid'];
794
795        $user1 = self::getDataGenerator()->create_user();
796
797        $user1 = array(
798            'id' => $user1->id,
799            'username' => 'usernametest1',
800            'password' => 'Moodle2012!',
801            'idnumber' => 'idnumbertest1',
802            'firstname' => 'First Name User Test 1',
803            'lastname' => 'Last Name User Test 1',
804            'middlename' => 'Middle Name User Test 1',
805            'lastnamephonetic' => '最後のお名前のテスト一号',
806            'firstnamephonetic' => 'お名前のテスト一号',
807            'alternatename' => 'Alternate Name User Test 1',
808            'email' => 'usertest1@example.com',
809            'description' => 'This is a description for user 1',
810            'city' => 'Perth',
811            'userpicture' => $draftid,
812            'country' => 'AU',
813            'preferences' => [[
814                    'type' => 'htmleditor',
815                    'value' => 'atto'
816                ], [
817                    'type' => 'invialidpreference',
818                    'value' => 'abcd'
819                ]
820            ],
821            'department' => 'College of Science',
822            'institution' => 'National Institute of Physics',
823            'phone1' => '01 2345 6789',
824            'maildisplay' => 1,
825            'interests' => 'badminton, basketball, cooking,  '
826        );
827
828        $context = context_system::instance();
829        $roleid = $this->assignUserCapability('moodle/user:update', $context->id);
830        $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
831
832        // Check we can't update deleted users, guest users, site admin.
833        $user2 = $user3 = $user4 = $user1;
834        $user2['id'] = $CFG->siteguest;
835
836        $siteadmins = explode(',', $CFG->siteadmins);
837        $user3['id'] = array_shift($siteadmins);
838
839        $userdeleted = self::getDataGenerator()->create_user();
840        $user4['id'] = $userdeleted->id;
841        user_delete_user($userdeleted);
842
843        // Call the external function.
844        core_user_external::update_users(array($user1, $user2, $user3, $user4));
845
846        $dbuser2 = $DB->get_record('user', array('id' => $user2['id']));
847        $this->assertNotEquals($dbuser2->username, $user2['username']);
848        $dbuser3 = $DB->get_record('user', array('id' => $user3['id']));
849        $this->assertNotEquals($dbuser3->username, $user3['username']);
850        $dbuser4 = $DB->get_record('user', array('id' => $user4['id']));
851        $this->assertNotEquals($dbuser4->username, $user4['username']);
852
853        $dbuser = $DB->get_record('user', array('id' => $user1['id']));
854        $this->assertEquals($dbuser->username, $user1['username']);
855        $this->assertEquals($dbuser->idnumber, $user1['idnumber']);
856        $this->assertEquals($dbuser->firstname, $user1['firstname']);
857        $this->assertEquals($dbuser->lastname, $user1['lastname']);
858        $this->assertEquals($dbuser->email, $user1['email']);
859        $this->assertEquals($dbuser->description, $user1['description']);
860        $this->assertEquals($dbuser->city, $user1['city']);
861        $this->assertEquals($dbuser->country, $user1['country']);
862        $this->assertNotEquals(0, $dbuser->picture, 'Picture must be set to the new icon itemid for this user');
863        $this->assertEquals($dbuser->department, $user1['department']);
864        $this->assertEquals($dbuser->institution, $user1['institution']);
865        $this->assertEquals($dbuser->phone1, $user1['phone1']);
866        $this->assertEquals($dbuser->maildisplay, $user1['maildisplay']);
867        $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
868        $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
869
870        // Confirm user interests have been saved.
871        $interests = core_tag_tag::get_item_tags_array('core', 'user', $user1['id'], core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
872        // There should be 3 user interests.
873        $this->assertCount(3, $interests);
874
875        // Confirm no picture change when parameter is not supplied.
876        unset($user1['userpicture']);
877        core_user_external::update_users(array($user1));
878        $dbusernopic = $DB->get_record('user', array('id' => $user1['id']));
879        $this->assertEquals($dbuser->picture, $dbusernopic->picture, 'Picture not change without the parameter.');
880
881        // Confirm delete of picture deletes the picture from the user record.
882        $user1['userpicture'] = 0;
883        core_user_external::update_users(array($user1));
884        $dbuserdelpic = $DB->get_record('user', array('id' => $user1['id']));
885        $this->assertEquals(0, $dbuserdelpic->picture, 'Picture must be deleted when sent as 0.');
886
887
888        // Call without required capability.
889        $this->unassignUserCapability('moodle/user:update', $context->id, $roleid);
890        $this->expectException('required_capability_exception');
891        core_user_external::update_users(array($user1));
892    }
893
894    /**
895     * Data provider for testing \core_user_external::update_users() for users with same emails
896     *
897     * @return array
898     */
899    public function users_with_same_emails() {
900        return [
901            'Same emails not allowed: Update name using exactly the same email' => [
902                0, 'John', 's1@example.com', 'Johnny', 's1@example.com', false, true
903            ],
904            'Same emails not allowed: Update using someone else\'s email' => [
905                0, 'John', 's1@example.com', 'Johnny', 's2@example.com', true, false
906            ],
907            'Same emails allowed: Update using someone else\'s email' => [
908                1, 'John', 's1@example.com', 'Johnny', 's2@example.com', true, true
909            ],
910            'Same emails not allowed: Update using same email but with different case' => [
911                0, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', false, true
912            ],
913            'Same emails not allowed: Update using another user\'s email similar to user but with different case' => [
914                0, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', true, false
915            ],
916            'Same emails allowed: Update using another user\'s email similar to user but with different case' => [
917                1, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', true, true
918            ],
919        ];
920    }
921
922    /**
923     * Test update_users using similar emails with varying cases.
924     *
925     * @dataProvider users_with_same_emails
926     * @param boolean $allowsameemail The value to set for $CFG->allowaccountssameemail.
927     * @param string $currentname The user's current name.
928     * @param string $currentemail The user's current email.
929     * @param string $newname The user's new name.
930     * @param string $newemail The user's new email.
931     * @param boolean $withanotheruser Whether to create another user that has the same email as the target user's new email.
932     * @param boolean $successexpected Whether we expect that the target user's email/name will be updated.
933     */
934    public function test_update_users_emails_with_different_cases($allowsameemail, $currentname, $currentemail,
935                                                                  $newname, $newemail, $withanotheruser, $successexpected) {
936        global $DB;
937
938        $this->resetAfterTest();
939        $this->setAdminUser();
940
941        // Set the value for $CFG->allowaccountssameemail.
942        set_config('allowaccountssameemail', $allowsameemail);
943
944        $generator = self::getDataGenerator();
945
946        // Create the user that we wish to update.
947        $usertoupdate = $generator->create_user(['email' => $currentemail, 'firstname' => $currentname]);
948
949        if ($withanotheruser) {
950            // Create another user that has the same email as the new email that we'd like to update for our target user.
951            $generator->create_user(['email' => $newemail]);
952        }
953
954        // Build the user update parameters.
955        $updateparams = [
956            'id' => $usertoupdate->id,
957            'email' => $newemail,
958            'firstname' => $newname
959        ];
960        // Let's try to update the user's information.
961        core_user_external::update_users([$updateparams]);
962
963        // Fetch the updated user record.
964        $userrecord = $DB->get_record('user', ['id' => $usertoupdate->id], 'id, email, firstname');
965
966        // If we expect the update to succeed, then the email/name would have been changed.
967        if ($successexpected) {
968            $expectedemail = $newemail;
969            $expectedname = $newname;
970        } else {
971            $expectedemail = $currentemail;
972            $expectedname = $currentname;
973        }
974        // Confirm that our expectations are met.
975        $this->assertEquals($expectedemail, $userrecord->email);
976        $this->assertEquals($expectedname, $userrecord->firstname);
977    }
978
979    /**
980     * Test add_user_private_files
981     */
982    public function test_add_user_private_files() {
983        global $USER, $CFG, $DB;
984
985        $this->resetAfterTest(true);
986
987        $context = context_system::instance();
988        $roleid = $this->assignUserCapability('moodle/user:manageownfiles', $context->id);
989
990        $context = context_user::instance($USER->id);
991        $contextid = $context->id;
992        $component = "user";
993        $filearea = "draft";
994        $itemid = 0;
995        $filepath = "/";
996        $filename = "Simple.txt";
997        $filecontent = base64_encode("Let us create a nice simple file");
998        $contextlevel = null;
999        $instanceid = null;
1000        $browser = get_file_browser();
1001
1002        // Call the files api to create a file.
1003        $draftfile = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
1004                                                 $filename, $filecontent, $contextlevel, $instanceid);
1005        $draftfile = external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
1006
1007        $draftid = $draftfile['itemid'];
1008        // Make sure the file was created.
1009        $file = $browser->get_file_info($context, $component, $filearea, $draftid, $filepath, $filename);
1010        $this->assertNotEmpty($file);
1011
1012        // Make sure the file does not exist in the user private files.
1013        $file = $browser->get_file_info($context, $component, 'private', 0, $filepath, $filename);
1014        $this->assertEmpty($file);
1015
1016        // Call the external function.
1017        core_user_external::add_user_private_files($draftid);
1018
1019        // Make sure the file was added to the user private files.
1020        $file = $browser->get_file_info($context, $component, 'private', 0, $filepath, $filename);
1021        $this->assertNotEmpty($file);
1022    }
1023
1024    /**
1025     * Test add user device
1026     */
1027    public function test_add_user_device() {
1028        global $USER, $CFG, $DB;
1029
1030        $this->resetAfterTest(true);
1031
1032        $device = array(
1033                'appid' => 'com.moodle.moodlemobile',
1034                'name' => 'occam',
1035                'model' => 'Nexus 4',
1036                'platform' => 'Android',
1037                'version' => '4.2.2',
1038                'pushid' => 'apushdkasdfj4835',
1039                'uuid' => 'asdnfl348qlksfaasef859'
1040                );
1041
1042        // Call the external function.
1043        core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1044                                            $device['version'], $device['pushid'], $device['uuid']);
1045
1046        $created = $DB->get_record('user_devices', array('pushid' => $device['pushid']));
1047        $created = (array) $created;
1048
1049        $this->assertEquals($device, array_intersect_key((array)$created, $device));
1050
1051        // Test reuse the same pushid value.
1052        $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1053                                                        $device['version'], $device['pushid'], $device['uuid']);
1054        // We need to execute the return values cleaning process to simulate the web service server.
1055        $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1056        $this->assertCount(1, $warnings);
1057
1058        // Test update an existing device.
1059        $device['pushid'] = 'different than before';
1060        $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1061                                                        $device['version'], $device['pushid'], $device['uuid']);
1062        $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1063
1064        $this->assertEquals(1, $DB->count_records('user_devices'));
1065        $updated = $DB->get_record('user_devices', array('pushid' => $device['pushid']));
1066        $this->assertEquals($device, array_intersect_key((array)$updated, $device));
1067
1068        // Test creating a new device just changing the uuid.
1069        $device['uuid'] = 'newuidforthesameuser';
1070        $device['pushid'] = 'new different than before';
1071        $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1072                                                        $device['version'], $device['pushid'], $device['uuid']);
1073        $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1074        $this->assertEquals(2, $DB->count_records('user_devices'));
1075    }
1076
1077    /**
1078     * Test remove user device
1079     */
1080    public function test_remove_user_device() {
1081        global $USER, $CFG, $DB;
1082
1083        $this->resetAfterTest(true);
1084
1085        $device = array(
1086                'appid' => 'com.moodle.moodlemobile',
1087                'name' => 'occam',
1088                'model' => 'Nexus 4',
1089                'platform' => 'Android',
1090                'version' => '4.2.2',
1091                'pushid' => 'apushdkasdfj4835',
1092                'uuid' => 'ABCDE3723ksdfhasfaasef859'
1093                );
1094
1095        // A device with the same properties except the appid and pushid.
1096        $device2 = $device;
1097        $device2['pushid'] = "0987654321";
1098        $device2['appid'] = "other.app.com";
1099
1100        $this->setAdminUser();
1101        // Create a user device using the external API function.
1102        core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1103                                            $device['version'], $device['pushid'], $device['uuid']);
1104
1105        // Create the same device but for a different app.
1106        core_user_external::add_user_device($device2['appid'], $device2['name'], $device2['model'], $device2['platform'],
1107                                            $device2['version'], $device2['pushid'], $device2['uuid']);
1108
1109        // Try to remove a device that does not exist.
1110        $result = core_user_external::remove_user_device('1234567890');
1111        $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1112        $this->assertFalse($result['removed']);
1113        $this->assertCount(1, $result['warnings']);
1114
1115        // Try to remove a device that does not exist for an existing app.
1116        $result = core_user_external::remove_user_device('1234567890', $device['appid']);
1117        $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1118        $this->assertFalse($result['removed']);
1119        $this->assertCount(1, $result['warnings']);
1120
1121        // Remove an existing device for an existing app. This will remove one of the two devices.
1122        $result = core_user_external::remove_user_device($device['uuid'], $device['appid']);
1123        $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1124        $this->assertTrue($result['removed']);
1125
1126        // Remove all the devices. This must remove the remaining device.
1127        $result = core_user_external::remove_user_device($device['uuid']);
1128        $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1129        $this->assertTrue($result['removed']);
1130    }
1131
1132    /**
1133     * Test get_user_preferences
1134     */
1135    public function test_get_user_preferences() {
1136        $this->resetAfterTest(true);
1137
1138        $user = self::getDataGenerator()->create_user();
1139        set_user_preference('calendar_maxevents', 1, $user);
1140        set_user_preference('some_random_text', 'text', $user);
1141
1142        $this->setUser($user);
1143
1144        $result = core_user_external::get_user_preferences();
1145        $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1146        $this->assertCount(0, $result['warnings']);
1147        // Expect 3, _lastloaded is always returned.
1148        $this->assertCount(3, $result['preferences']);
1149
1150        foreach ($result['preferences'] as $pref) {
1151            if ($pref['name'] === '_lastloaded') {
1152                continue;
1153            }
1154            // Check we receive the expected preferences.
1155            $this->assertEquals(get_user_preferences($pref['name']), $pref['value']);
1156        }
1157
1158        // Retrieve just one preference.
1159        $result = core_user_external::get_user_preferences('some_random_text');
1160        $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1161        $this->assertCount(0, $result['warnings']);
1162        $this->assertCount(1, $result['preferences']);
1163        $this->assertEquals('text', $result['preferences'][0]['value']);
1164
1165        // Retrieve non-existent preference.
1166        $result = core_user_external::get_user_preferences('non_existent');
1167        $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1168        $this->assertCount(0, $result['warnings']);
1169        $this->assertCount(1, $result['preferences']);
1170        $this->assertEquals(null, $result['preferences'][0]['value']);
1171
1172        // Check that as admin we can retrieve all the preferences for any user.
1173        $this->setAdminUser();
1174        $result = core_user_external::get_user_preferences('', $user->id);
1175        $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1176        $this->assertCount(0, $result['warnings']);
1177        $this->assertCount(3, $result['preferences']);
1178
1179        foreach ($result['preferences'] as $pref) {
1180            if ($pref['name'] === '_lastloaded') {
1181                continue;
1182            }
1183            // Check we receive the expected preferences.
1184            $this->assertEquals(get_user_preferences($pref['name'], null, $user), $pref['value']);
1185        }
1186
1187        // Check that as a non admin user we cannot retrieve other users preferences.
1188        $anotheruser = self::getDataGenerator()->create_user();
1189        $this->setUser($anotheruser);
1190
1191        $this->expectException('required_capability_exception');
1192        $result = core_user_external::get_user_preferences('', $user->id);
1193    }
1194
1195    /**
1196     * Test update_picture
1197     */
1198    public function test_update_picture() {
1199        global $DB, $USER;
1200
1201        $this->resetAfterTest(true);
1202
1203        $user = self::getDataGenerator()->create_user();
1204        self::setUser($user);
1205
1206        $context = context_user::instance($USER->id);
1207        $contextid = $context->id;
1208        $filename = "reddot.png";
1209        $filecontent = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38"
1210            . "GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
1211
1212        // Call the files api to create a file.
1213        $draftfile = core_files_external::upload($contextid, 'user', 'draft', 0, '/', $filename, $filecontent, null, null);
1214        $draftid = $draftfile['itemid'];
1215
1216        // Change user profile image.
1217        $result = core_user_external::update_picture($draftid);
1218        $result = external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1219        $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1220        // The new revision is in the url for the user.
1221        $this->assertContains($picture, $result['profileimageurl']);
1222        // Check expected URL for serving the image.
1223        $this->assertContains("/$contextid/user/icon", $result['profileimageurl']);
1224
1225        // Delete image.
1226        $result = core_user_external::update_picture(0, true);
1227        $result = external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1228        $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1229        // No picture.
1230        $this->assertEquals(0, $picture);
1231
1232        // Add again the user profile image (as admin).
1233        $this->setAdminUser();
1234
1235        $context = context_user::instance($USER->id);
1236        $admincontextid = $context->id;
1237        $draftfile = core_files_external::upload($admincontextid, 'user', 'draft', 0, '/', $filename, $filecontent, null, null);
1238        $draftid = $draftfile['itemid'];
1239
1240        $result = core_user_external::update_picture($draftid, false, $user->id);
1241        $result = external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1242        // The new revision is in the url for the user.
1243        $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1244        $this->assertContains($picture, $result['profileimageurl']);
1245        $this->assertContains("/$contextid/user/icon", $result['profileimageurl']);
1246    }
1247
1248    /**
1249     * Test update_picture disabled
1250     */
1251    public function test_update_picture_disabled() {
1252        global $CFG;
1253        $this->resetAfterTest(true);
1254        $CFG->disableuserimages = true;
1255
1256        $this->setAdminUser();
1257        $this->expectException('moodle_exception');
1258        core_user_external::update_picture(0);
1259    }
1260
1261    /**
1262     * Test set_user_preferences
1263     */
1264    public function test_set_user_preferences_save() {
1265        global $DB;
1266        $this->resetAfterTest(true);
1267
1268        $user1 = self::getDataGenerator()->create_user();
1269        $user2 = self::getDataGenerator()->create_user();
1270
1271        // Save users preferences.
1272        $this->setAdminUser();
1273        $preferences = array(
1274            array(
1275                'name' => 'htmleditor',
1276                'value' => 'atto',
1277                'userid' => $user1->id,
1278            ),
1279            array(
1280                'name' => 'htmleditor',
1281                'value' => 'tinymce',
1282                'userid' => $user2->id,
1283            )
1284        );
1285
1286        $result = core_user_external::set_user_preferences($preferences);
1287        $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1288        $this->assertCount(0, $result['warnings']);
1289        $this->assertCount(2, $result['saved']);
1290
1291        // Get preference from DB to avoid cache.
1292        $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
1293            array('userid' => $user1->id, 'name' => 'htmleditor')));
1294        $this->assertEquals('tinymce', $DB->get_field('user_preferences', 'value',
1295            array('userid' => $user2->id, 'name' => 'htmleditor')));
1296    }
1297
1298    /**
1299     * Test set_user_preferences
1300     */
1301    public function test_set_user_preferences_save_invalid_pref() {
1302        global $DB;
1303        $this->resetAfterTest(true);
1304
1305        $user1 = self::getDataGenerator()->create_user();
1306
1307        // Save users preferences.
1308        $this->setAdminUser();
1309        $preferences = array(
1310            array(
1311                'name' => 'some_random_pref',
1312                'value' => 'abc',
1313                'userid' => $user1->id,
1314            ),
1315        );
1316
1317        $result = core_user_external::set_user_preferences($preferences);
1318        $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1319        $this->assertCount(1, $result['warnings']);
1320        $this->assertCount(0, $result['saved']);
1321        $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
1322
1323        // Nothing was written to DB.
1324        $this->assertEmpty($DB->count_records('user_preferences', array('name' => 'some_random_pref')));
1325    }
1326
1327    /**
1328     * Test set_user_preferences for an invalid user
1329     */
1330    public function test_set_user_preferences_invalid_user() {
1331        $this->resetAfterTest(true);
1332
1333        $this->setAdminUser();
1334        $preferences = array(
1335            array(
1336                'name' => 'calendar_maxevents',
1337                'value' => 4,
1338                'userid' => -2
1339            )
1340        );
1341
1342        $result = core_user_external::set_user_preferences($preferences);
1343        $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1344        $this->assertCount(1, $result['warnings']);
1345        $this->assertCount(0, $result['saved']);
1346        $this->assertEquals('invaliduser', $result['warnings'][0]['warningcode']);
1347        $this->assertEquals(-2, $result['warnings'][0]['itemid']);
1348    }
1349
1350    /**
1351     * Test set_user_preferences using an invalid preference
1352     */
1353    public function test_set_user_preferences_invalid_preference() {
1354        global $USER, $DB;
1355
1356        $this->resetAfterTest(true);
1357        // Create a very long value.
1358        $this->setAdminUser();
1359        $preferences = array(
1360            array(
1361                'name' => 'calendar_maxevents',
1362                'value' => str_repeat('a', 1334),
1363                'userid' => $USER->id
1364            )
1365        );
1366
1367        $result = core_user_external::set_user_preferences($preferences);
1368        $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1369        $this->assertCount(0, $result['warnings']);
1370        $this->assertCount(1, $result['saved']);
1371        // Cleaned valud of the preference was saved.
1372        $this->assertEquals(1, $DB->get_field('user_preferences', 'value',
1373            array('userid' => $USER->id, 'name' => 'calendar_maxevents')));
1374    }
1375
1376    /**
1377     * Test set_user_preferences for other user not being admin
1378     */
1379    public function test_set_user_preferences_capability() {
1380        $this->resetAfterTest(true);
1381
1382        $user1 = self::getDataGenerator()->create_user();
1383        $user2 = self::getDataGenerator()->create_user();
1384
1385        $this->setUser($user1);
1386        $preferences = array(
1387            array(
1388                'name' => 'calendar_maxevents',
1389                'value' => 4,
1390                'userid' => $user2->id
1391            )
1392        );
1393
1394        $result = core_user_external::set_user_preferences($preferences);
1395
1396        $this->assertCount(1, $result['warnings']);
1397        $this->assertCount(0, $result['saved']);
1398        $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
1399        $this->assertEquals($user2->id, $result['warnings'][0]['itemid']);
1400    }
1401
1402    /**
1403     * Test update_user_preferences unsetting an existing preference.
1404     */
1405    public function test_update_user_preferences_unset() {
1406        global $DB;
1407        $this->resetAfterTest(true);
1408
1409        $user = self::getDataGenerator()->create_user();
1410
1411        // Save users preferences.
1412        $this->setAdminUser();
1413        $preferences = array(
1414            array(
1415                'name' => 'htmleditor',
1416                'value' => 'atto',
1417                'userid' => $user->id,
1418            )
1419        );
1420
1421        $result = core_user_external::set_user_preferences($preferences);
1422        $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1423        $this->assertCount(0, $result['warnings']);
1424        $this->assertCount(1, $result['saved']);
1425
1426        // Get preference from DB to avoid cache.
1427        $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
1428            array('userid' => $user->id, 'name' => 'htmleditor')));
1429
1430        // Now, unset.
1431        $result = core_user_external::update_user_preferences($user->id, null, array(array('type' => 'htmleditor')));
1432
1433        $this->assertEquals(0, $DB->count_records('user_preferences', array('userid' => $user->id, 'name' => 'htmleditor')));
1434    }
1435
1436    /**
1437     * Test agree_site_policy
1438     */
1439    public function test_agree_site_policy() {
1440        global $CFG, $DB, $USER;
1441        $this->resetAfterTest(true);
1442
1443        $user = self::getDataGenerator()->create_user();
1444        $this->setUser($user);
1445
1446        // Site policy not set.
1447        $result = core_user_external::agree_site_policy();
1448        $result = external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1449        $this->assertFalse($result['status']);
1450        $this->assertCount(1, $result['warnings']);
1451        $this->assertEquals('nositepolicy', $result['warnings'][0]['warningcode']);
1452
1453        // Set a policy issue.
1454        $CFG->sitepolicy = 'https://moodle.org';
1455        $this->assertEquals(0, $USER->policyagreed);
1456
1457        $result = core_user_external::agree_site_policy();
1458        $result = external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1459        $this->assertTrue($result['status']);
1460        $this->assertCount(0, $result['warnings']);
1461        $this->assertEquals(1, $USER->policyagreed);
1462        $this->assertEquals(1, $DB->get_field('user', 'policyagreed', array('id' => $USER->id)));
1463
1464        // Try again, we should get a warning.
1465        $result = core_user_external::agree_site_policy();
1466        $result = external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1467        $this->assertFalse($result['status']);
1468        $this->assertCount(1, $result['warnings']);
1469        $this->assertEquals('alreadyagreed', $result['warnings'][0]['warningcode']);
1470
1471        // Set something to make require_login throws an exception.
1472        $otheruser = self::getDataGenerator()->create_user();
1473        $this->setUser($otheruser);
1474
1475        $DB->set_field('user', 'lastname', '', array('id' => $USER->id));
1476        $USER->lastname = '';
1477        try {
1478            $result = core_user_external::agree_site_policy();
1479            $this->fail('Expecting \'usernotfullysetup\' moodle_exception to be thrown');
1480        } catch (moodle_exception $e) {
1481            $this->assertEquals('usernotfullysetup', $e->errorcode);
1482        } catch (Exception $e) {
1483            $this->fail('Expecting \'usernotfullysetup\' moodle_exception to be thrown.');
1484        }
1485    }
1486
1487    /**
1488     * Test get_private_files_info
1489     */
1490    public function test_get_private_files_info() {
1491
1492        $this->resetAfterTest(true);
1493        $user = self::getDataGenerator()->create_user();
1494        $this->setUser($user);
1495        $usercontext = context_user::instance($user->id);
1496
1497        $filerecord = array(
1498            'contextid' => $usercontext->id,
1499            'component' => 'user',
1500            'filearea'  => 'private',
1501            'itemid'    => 0,
1502            'filepath'  => '/',
1503            'filename'  => 'thefile',
1504        );
1505
1506        $fs = get_file_storage();
1507        $file = $fs->create_file_from_string($filerecord, 'abc');
1508
1509        // Get my private files information.
1510        $result = core_user_external::get_private_files_info();
1511        $result = external_api::clean_returnvalue(core_user_external::get_private_files_info_returns(), $result);
1512        $this->assertEquals(1, $result['filecount']);
1513        $this->assertEquals($file->get_filesize(), $result['filesize']);
1514        $this->assertEquals(0, $result['foldercount']);
1515        $this->assertEquals($file->get_filesize(), $result['filesizewithoutreferences']);
1516
1517        // As admin, get user information.
1518        $this->setAdminUser();
1519        $result = core_user_external::get_private_files_info($user->id);
1520        $result = external_api::clean_returnvalue(core_user_external::get_private_files_info_returns(), $result);
1521        $this->assertEquals(1, $result['filecount']);
1522        $this->assertEquals($file->get_filesize(), $result['filesize']);
1523        $this->assertEquals(0, $result['foldercount']);
1524        $this->assertEquals($file->get_filesize(), $result['filesizewithoutreferences']);
1525    }
1526
1527    /**
1528     * Test get_private_files_info missing permissions.
1529     */
1530    public function test_get_private_files_info_missing_permissions() {
1531
1532        $this->resetAfterTest(true);
1533        $user1 = self::getDataGenerator()->create_user();
1534        $user2 = self::getDataGenerator()->create_user();
1535        $this->setUser($user1);
1536
1537        $this->expectException('required_capability_exception');
1538        // Try to retrieve other user private files info.
1539        core_user_external::get_private_files_info($user2->id);
1540    }
1541}
1542