Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_user;
  18  
  19  /**
  20   * Unit tests for user/profile/lib.php.
  21   *
  22   * @package core_user
  23   * @copyright 2014 The Open University
  24   * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  class profilelib_test extends \advanced_testcase {
  27      /**
  28       * Tests profile_get_custom_fields function and checks it is consistent
  29       * with profile_user_record.
  30       */
  31      public function test_get_custom_fields() {
  32          global $CFG;
  33          require_once($CFG->dirroot . '/user/profile/lib.php');
  34  
  35          $this->resetAfterTest();
  36          $user = $this->getDataGenerator()->create_user();
  37  
  38          // Add a custom field of textarea type.
  39          $id1 = $this->getDataGenerator()->create_custom_profile_field([
  40                  'shortname' => 'frogdesc', 'name' => 'Description of frog',
  41                  'datatype' => 'textarea'])->id;
  42  
  43          // Check the field is returned.
  44          $result = profile_get_custom_fields();
  45          $this->assertArrayHasKey($id1, $result);
  46          $this->assertEquals('frogdesc', $result[$id1]->shortname);
  47  
  48          // Textarea types are not included in user data though, so if we
  49          // use the 'only in user data' parameter, there is still nothing.
  50          $this->assertArrayNotHasKey($id1, profile_get_custom_fields(true));
  51  
  52          // Check that profile_user_record returns same (no) fields.
  53          $this->assertObjectNotHasAttribute('frogdesc', profile_user_record($user->id));
  54  
  55          // Check that profile_user_record returns all the fields when requested.
  56          $this->assertObjectHasAttribute('frogdesc', profile_user_record($user->id, false));
  57  
  58          // Add another custom field, this time of normal text type.
  59          $id2 = $this->getDataGenerator()->create_custom_profile_field(array(
  60                  'shortname' => 'frogname', 'name' => 'Name of frog',
  61                  'datatype' => 'text'))->id;
  62  
  63          // Check both are returned using normal option.
  64          $result = profile_get_custom_fields();
  65          $this->assertArrayHasKey($id2, $result);
  66          $this->assertEquals('frogname', $result[$id2]->shortname);
  67  
  68          // And check that only the one is returned the other way.
  69          $this->assertArrayHasKey($id2, profile_get_custom_fields(true));
  70  
  71          // Check profile_user_record returns same field.
  72          $this->assertObjectHasAttribute('frogname', profile_user_record($user->id));
  73  
  74          // Check that profile_user_record returns all the fields when requested.
  75          $this->assertObjectHasAttribute('frogname', profile_user_record($user->id, false));
  76      }
  77  
  78      /**
  79       * Make sure that all profile fields can be initialised without arguments.
  80       */
  81      public function test_default_constructor() {
  82          global $DB, $CFG;
  83          require_once($CFG->dirroot . '/user/profile/definelib.php');
  84          $datatypes = profile_list_datatypes();
  85          foreach ($datatypes as $datatype => $datatypename) {
  86              require_once($CFG->dirroot . '/user/profile/field/' .
  87                  $datatype . '/field.class.php');
  88              $newfield = 'profile_field_' . $datatype;
  89              $formfield = new $newfield();
  90              $this->assertNotNull($formfield);
  91          }
  92      }
  93  
  94      /**
  95       * Test profile_view function
  96       */
  97      public function test_profile_view() {
  98          global $USER;
  99  
 100          $this->resetAfterTest();
 101  
 102          // Course without sections.
 103          $course = $this->getDataGenerator()->create_course();
 104          $context = \context_course::instance($course->id);
 105          $user = $this->getDataGenerator()->create_user();
 106          $usercontext = \context_user::instance($user->id);
 107  
 108          $this->setUser($user);
 109  
 110          // Redirect events to the sink, so we can recover them later.
 111          $sink = $this->redirectEvents();
 112  
 113          profile_view($user, $context, $course);
 114          $events = $sink->get_events();
 115          $event = reset($events);
 116  
 117          // Check the event details are correct.
 118          $this->assertInstanceOf('\core\event\user_profile_viewed', $event);
 119          $this->assertEquals($context, $event->get_context());
 120          $this->assertEquals($user->id, $event->relateduserid);
 121          $this->assertEquals($course->id, $event->other['courseid']);
 122          $this->assertEquals($course->shortname, $event->other['courseshortname']);
 123          $this->assertEquals($course->fullname, $event->other['coursefullname']);
 124  
 125          profile_view($user, $usercontext);
 126          $events = $sink->get_events();
 127          $event = array_pop($events);
 128          $sink->close();
 129  
 130          $this->assertInstanceOf('\core\event\user_profile_viewed', $event);
 131          $this->assertEquals($usercontext, $event->get_context());
 132          $this->assertEquals($user->id, $event->relateduserid);
 133  
 134      }
 135  
 136      /**
 137       * Test that {@link user_not_fully_set_up()} takes required custom fields into account.
 138       */
 139      public function test_profile_has_required_custom_fields_set() {
 140          global $CFG;
 141          require_once($CFG->dirroot.'/mnet/lib.php');
 142  
 143          $this->resetAfterTest();
 144  
 145          // Add a required, visible, unlocked custom field.
 146          $this->getDataGenerator()->create_custom_profile_field(['shortname' => 'house', 'name' => 'House', 'required' => 1,
 147              'visible' => 1, 'locked' => 0, 'datatype' => 'text']);
 148  
 149          // Add an optional, visible, unlocked custom field.
 150          $this->getDataGenerator()->create_custom_profile_field(['shortname' => 'pet', 'name' => 'Pet', 'required' => 0,
 151              'visible' => 1, 'locked' => 0, 'datatype' => 'text']);
 152  
 153          // Add required but invisible custom field.
 154          $this->getDataGenerator()->create_custom_profile_field(['shortname' => 'secretid', 'name' => 'Secret ID',
 155              'required' => 1, 'visible' => 0, 'locked' => 0, 'datatype' => 'text']);
 156  
 157          // Add required but locked custom field.
 158          $this->getDataGenerator()->create_custom_profile_field(['shortname' => 'muggleborn', 'name' => 'Muggle-born',
 159              'required' => 1, 'visible' => 1, 'locked' => 1, 'datatype' => 'checkbox']);
 160  
 161          // Create some student accounts.
 162          $hermione = $this->getDataGenerator()->create_user();
 163          $harry = $this->getDataGenerator()->create_user();
 164          $ron = $this->getDataGenerator()->create_user();
 165          $draco = $this->getDataGenerator()->create_user();
 166  
 167          // Hermione has all available custom fields filled (of course she has).
 168          profile_save_data((object)['id' => $hermione->id, 'profile_field_house' => 'Gryffindor']);
 169          profile_save_data((object)['id' => $hermione->id, 'profile_field_pet' => 'Crookshanks']);
 170  
 171          // Harry has only the optional field filled.
 172          profile_save_data((object)['id' => $harry->id, 'profile_field_pet' => 'Hedwig']);
 173  
 174          // Draco has only the required field filled.
 175          profile_save_data((object)['id' => $draco->id, 'profile_field_house' => 'Slytherin']);
 176  
 177          // Only students with required fields filled should be considered as fully set up in the default (strict) mode.
 178          $this->assertFalse(user_not_fully_set_up($hermione));
 179          $this->assertFalse(user_not_fully_set_up($draco));
 180          $this->assertTrue(user_not_fully_set_up($harry));
 181          $this->assertTrue(user_not_fully_set_up($ron));
 182  
 183          // In the lax mode, students do not need to have required fields filled.
 184          $this->assertFalse(user_not_fully_set_up($hermione, false));
 185          $this->assertFalse(user_not_fully_set_up($draco, false));
 186          $this->assertFalse(user_not_fully_set_up($harry, false));
 187          $this->assertFalse(user_not_fully_set_up($ron, false));
 188  
 189          // Lack of required core field is seen as a problem in either mode.
 190          unset($hermione->email);
 191          $this->assertTrue(user_not_fully_set_up($hermione, true));
 192          $this->assertTrue(user_not_fully_set_up($hermione, false));
 193  
 194          // When confirming remote MNet users, we do not have custom fields available.
 195          $roamingharry = mnet_strip_user($harry, ['firstname', 'lastname', 'email']);
 196          $roaminghermione = mnet_strip_user($hermione, ['firstname', 'lastname', 'email']);
 197  
 198          $this->assertTrue(user_not_fully_set_up($roamingharry, true));
 199          $this->assertFalse(user_not_fully_set_up($roamingharry, false));
 200          $this->assertTrue(user_not_fully_set_up($roaminghermione, true));
 201          $this->assertTrue(user_not_fully_set_up($roaminghermione, false));
 202      }
 203  
 204      /**
 205       * Test that user generator sets the custom profile fields
 206       */
 207      public function test_profile_fields_in_generator() {
 208          global $CFG;
 209          require_once($CFG->dirroot.'/mnet/lib.php');
 210  
 211          $this->resetAfterTest();
 212  
 213          // Add a required, visible, unlocked custom field.
 214          $this->getDataGenerator()->create_custom_profile_field(['shortname' => 'house', 'name' => 'House', 'required' => 1,
 215              'visible' => 1, 'locked' => 0, 'datatype' => 'text', 'defaultdata' => null]);
 216  
 217          // Create some student accounts.
 218          $hermione = $this->getDataGenerator()->create_user(['profile_field_house' => 'Gryffindor']);
 219          $harry = $this->getDataGenerator()->create_user();
 220  
 221          // Only students with required fields filled should be considered as fully set up.
 222          $this->assertFalse(user_not_fully_set_up($hermione));
 223          $this->assertTrue(user_not_fully_set_up($harry));
 224  
 225          // Test that the profile fields were actually set.
 226          $profilefields1 = profile_user_record($hermione->id);
 227          $this->assertEquals('Gryffindor', $profilefields1->house);
 228  
 229          $profilefields2 = profile_user_record($harry->id);
 230          $this->assertObjectHasAttribute('house', $profilefields2);
 231          $this->assertNull($profilefields2->house);
 232      }
 233  
 234      /**
 235       * Tests the profile_get_custom_field_data_by_shortname function when working normally.
 236       */
 237      public function test_profile_get_custom_field_data_by_shortname_normal() {
 238          global $DB, $CFG;
 239          require_once($CFG->dirroot . '/user/profile/lib.php');
 240  
 241          $this->resetAfterTest();
 242  
 243          // Create 3 profile fields.
 244          $generator = $this->getDataGenerator();
 245          $field1 = $generator->create_custom_profile_field(['datatype' => 'text',
 246                  'shortname' => 'speciality', 'name' => 'Speciality',
 247                  'visible' => PROFILE_VISIBLE_ALL]);
 248          $field2 = $generator->create_custom_profile_field(['datatype' => 'menu',
 249                  'shortname' => 'veggie', 'name' => 'Vegetarian',
 250                  'visible' => PROFILE_VISIBLE_PRIVATE]);
 251  
 252          // Get the first field data and check it is correct.
 253          $data = profile_get_custom_field_data_by_shortname('speciality');
 254          $this->assertEquals('Speciality', $data->name);
 255          $this->assertEquals(PROFILE_VISIBLE_ALL, $data->visible);
 256          $this->assertEquals($field1->id, $data->id);
 257  
 258          // Get the second field data, checking there is no database query this time.
 259          $before = $DB->perf_get_queries();
 260          $data = profile_get_custom_field_data_by_shortname('veggie');
 261          $this->assertEquals($before, $DB->perf_get_queries());
 262          $this->assertEquals('Vegetarian', $data->name);
 263          $this->assertEquals(PROFILE_VISIBLE_PRIVATE, $data->visible);
 264          $this->assertEquals($field2->id, $data->id);
 265      }
 266  
 267      /**
 268       * Tests the profile_get_custom_field_data_by_shortname function with a field that doesn't exist.
 269       */
 270      public function test_profile_get_custom_field_data_by_shortname_missing() {
 271          global $CFG;
 272          require_once($CFG->dirroot . '/user/profile/lib.php');
 273  
 274          $this->assertNull(profile_get_custom_field_data_by_shortname('speciality'));
 275      }
 276  
 277      /**
 278       * Data provider for {@see test_profile_get_custom_field_data_by_shortname_case_sensitivity}
 279       *
 280       * @return array[]
 281       */
 282      public function profile_get_custom_field_data_by_shortname_case_sensitivity_provider(): array {
 283          return [
 284              'Matching case, case-sensitive search' => ['hello', 'hello', true, true],
 285              'Matching case, case-insensitive search' => ['hello', 'hello', false, true],
 286              'Non-matching case, case-sensitive search' => ['hello', 'Hello', true, false],
 287              'Non-matching case, case-insensitive search' => ['hello', 'Hello', false, true],
 288              'Non-matching, case-sensitive search' => ['hello', 'hola', true, false],
 289              'Non-matching, case-insensitive search' => ['hello', 'hola', false, false],
 290          ];
 291      }
 292  
 293      /**
 294       * Test retrieving custom field by shortname, specifying case-sensitivity when matching
 295       *
 296       * @param string $shortname
 297       * @param string $shortnamesearch
 298       * @param bool $casesensitive
 299       * @param bool $expectmatch
 300       *
 301       * @dataProvider profile_get_custom_field_data_by_shortname_case_sensitivity_provider
 302       */
 303      public function test_profile_get_custom_field_data_by_shortname_case_sensitivity(
 304          string $shortname,
 305          string $shortnamesearch,
 306          bool $casesensitive,
 307          bool $expectmatch
 308      ): void {
 309          global $CFG;
 310  
 311          require_once("{$CFG->dirroot}/user/profile/lib.php");
 312  
 313          $this->resetAfterTest();
 314  
 315          $this->getDataGenerator()->create_custom_profile_field([
 316              'datatype' => 'text',
 317              'shortname' => $shortname,
 318              'name' => 'My field',
 319          ]);
 320  
 321          $customfield = profile_get_custom_field_data_by_shortname($shortnamesearch, $casesensitive);
 322          if ($expectmatch) {
 323              $this->assertInstanceOf(\stdClass::class, $customfield);
 324              $this->assertEquals('text', $customfield->datatype);
 325              $this->assertEquals($shortname, $customfield->shortname);
 326              $this->assertEquals('My field', $customfield->name);
 327          } else {
 328              $this->assertNull($customfield);
 329          }
 330      }
 331  }