MDL-52781 core_user: improve core_user::fill_properties_cache()
authorSimey Lameze <simey@moodle.com>
Mon, 11 Apr 2016 04:08:47 +0000 (12:08 +0800)
committerSimey Lameze <simey@moodle.com>
Thu, 21 Apr 2016 07:24:35 +0000 (15:24 +0800)
lang/en/error.php
lib/classes/user.php
lib/db/upgrade.php
lib/tests/user_test.php

index 092ec78..5400fd7 100644 (file)
@@ -352,6 +352,7 @@ $string['invalidurl'] = 'Invalid URL';
 $string['invaliduser'] = 'Invalid user';
 $string['invaliduserid'] = 'Invalid user id';
 $string['invaliduserfield'] = 'Invalid user field: {$a}';
 $string['invaliduser'] = 'Invalid user';
 $string['invaliduserid'] = 'Invalid user id';
 $string['invaliduserfield'] = 'Invalid user field: {$a}';
+$string['invaliduserdata'] = 'Invalid user data: {$a}';
 $string['invalidusername'] = 'The given username contains invalid characters';
 $string['invalidxmlfile'] = '"{$a}" is not a valid XML file';
 $string['iplookupfailed'] = 'Cannot find geo information about this IP address {$a}';
 $string['invalidusername'] = 'The given username contains invalid characters';
 $string['invalidxmlfile'] = '"{$a}" is not a valid XML file';
 $string['iplookupfailed'] = 'Cannot find geo information about this IP address {$a}';
index 07c1faf..e902ab3 100644 (file)
@@ -281,10 +281,21 @@ class core_user {
     /**
      * Definition of user profile fields and the expected parameter type for data validation.
      *
     /**
      * Definition of user profile fields and the expected parameter type for data validation.
      *
+     * array(
+     *     'property_name' => array(       // The user property to be checked. Should match the field on the user table.
+     *          'null' => NULL_ALLOWED,    // Defaults to NULL_NOT_ALLOWED. Takes NULL_NOT_ALLOWED or NULL_ALLOWED.
+     *          'type' => PARAM_TYPE,      // Expected parameter type of the user field.
+     *          'choices' => array(1, 2..) // An array of accepted values of the user field.
+     *          'default' => $CFG->setting // An default value for the field.
+     *     )
+     * )
+     *
+     * The fields choices and default are optional.
+     *
      * @return void
      */
     protected static function fill_properties_cache() {
      * @return void
      */
     protected static function fill_properties_cache() {
-
+        global $CFG;
         if (self::$propertiescache !== null) {
             return;
         }
         if (self::$propertiescache !== null) {
             return;
         }
@@ -292,60 +303,70 @@ class core_user {
         // Array of user fields properties and expected parameters.
         // Every new field on the user table should be added here otherwise it won't be validated.
         $fields = array();
         // Array of user fields properties and expected parameters.
         // Every new field on the user table should be added here otherwise it won't be validated.
         $fields = array();
-        $fields['id'] = array('type' => PARAM_INT);
-        $fields['auth'] = array('type' => PARAM_NOTAGS);
-        $fields['confirmed'] = array('type' => PARAM_BOOL);
-        $fields['policyagreed'] = array('type' => PARAM_BOOL);
-        $fields['deleted'] = array('type' => PARAM_BOOL);
-        $fields['suspended'] = array('type' => PARAM_BOOL);
-        $fields['mnethostid'] = array('type' => PARAM_BOOL);
-        $fields['username'] = array('type' => PARAM_USERNAME);
-        $fields['password'] = array('type' => PARAM_NOTAGS);
-        $fields['idnumber'] = array('type' => PARAM_NOTAGS);
-        $fields['firstname'] = array('type' => PARAM_NOTAGS);
-        $fields['lastname'] = array('type' => PARAM_NOTAGS);
-        $fields['surname'] = array('type' => PARAM_NOTAGS);
-        $fields['email'] = array('type' => PARAM_RAW_TRIMMED);
-        $fields['emailstop'] = array('type' => PARAM_INT);
-        $fields['icq'] = array('type' => PARAM_NOTAGS);
-        $fields['skype'] = array('type' => PARAM_NOTAGS);
-        $fields['aim'] = array('type' => PARAM_NOTAGS);
-        $fields['yahoo'] = array('type' => PARAM_NOTAGS);
-        $fields['msn'] = array('type' => PARAM_NOTAGS);
-        $fields['phone1'] = array('type' => PARAM_NOTAGS);
-        $fields['phone2'] = array('type' => PARAM_NOTAGS);
-        $fields['institution'] = array('type' => PARAM_TEXT);
-        $fields['department'] = array('type' => PARAM_TEXT);
-        $fields['address'] = array('type' => PARAM_TEXT);
-        $fields['city'] = array('type' => PARAM_TEXT);
-        $fields['country'] = array('type' => PARAM_TEXT);
-        $fields['lang'] = array('type' => PARAM_TEXT);
-        $fields['calendartype'] = array('type' => PARAM_NOTAGS);
-        $fields['theme'] = array('type' => PARAM_NOTAGS);
-        $fields['timezones'] = array('type' => PARAM_TEXT);
-        $fields['firstaccess'] = array('type' => PARAM_INT);
-        $fields['lastaccess'] = array('type' => PARAM_INT);
-        $fields['lastlogin'] = array('type' => PARAM_INT);
-        $fields['currentlogin'] = array('type' => PARAM_INT);
-        $fields['lastip'] = array('type' => PARAM_NOTAGS);
-        $fields['secret'] = array('type' => PARAM_TEXT);
-        $fields['picture'] = array('type' => PARAM_INT);
-        $fields['url'] = array('type' => PARAM_URL);
-        $fields['description'] = array('type' => PARAM_CLEANHTML);
-        $fields['descriptionformat'] = array('type' => PARAM_INT);
-        $fields['mailformat'] = array('type' => PARAM_INT);
-        $fields['maildigest'] = array('type' => PARAM_INT);
-        $fields['maildisplay'] = array('type' => PARAM_INT);
-        $fields['autosubscribe'] = array('type' => PARAM_INT);
-        $fields['trackforums'] = array('type' => PARAM_INT);
-        $fields['timecreated'] = array('type' => PARAM_INT);
-        $fields['timemodified'] = array('type' => PARAM_INT);
-        $fields['trustbitmask'] = array('type' => PARAM_INT);
-        $fields['imagealt'] = array('type' => PARAM_TEXT);
-        $fields['lastnamephonetic'] = array('type' => PARAM_NOTAGS);
-        $fields['firstnamephonetic'] = array('type' => PARAM_NOTAGS);
-        $fields['middlename'] = array('type' => PARAM_NOTAGS);
-        $fields['alternatename'] = array('type' => PARAM_NOTAGS);
+        $fields['id'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['auth'] = array('type' => PARAM_AUTH, 'null' => NULL_NOT_ALLOWED);
+        $fields['confirmed'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
+        $fields['policyagreed'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
+        $fields['deleted'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
+        $fields['suspended'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
+        $fields['mnethostid'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['username'] = array('type' => PARAM_USERNAME, 'null' => NULL_NOT_ALLOWED);
+        $fields['password'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED);
+        $fields['idnumber'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED);
+        $fields['firstname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['lastname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['surname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['email'] = array('type' => PARAM_RAW_TRIMMED, 'null' => NULL_NOT_ALLOWED);
+        $fields['emailstop'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['icq'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['skype'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
+        $fields['aim'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['yahoo'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['msn'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['phone1'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['phone2'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['institution'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED);
+        $fields['department'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED);
+        $fields['address'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED);
+        $fields['city'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->defaultcity);
+        $fields['country'] = array('type' => PARAM_ALPHA, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->country,
+                'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_countries(true, true)));
+        $fields['lang'] = array('type' => PARAM_LANG, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->lang,
+                'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_languages()));
+        $fields['calendartype'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->calendartype,
+                'choices' => array_merge(array('' => ''), \core_calendar\type_factory::get_list_of_calendar_types()));
+        $fields['theme'] = array('type' => PARAM_THEME, 'null' => NULL_NOT_ALLOWED,
+                'default' => theme_config::DEFAULT_THEME, 'choices' => array_merge(array('' => ''), get_list_of_themes()));
+        $fields['timezone'] = array('type' => PARAM_TIMEZONE, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->timezone,
+                'choices' => core_date::get_list_of_timezones(null, true));
+        $fields['firstaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['lastaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['lastlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['currentlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['lastip'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
+        $fields['secret'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED);
+        $fields['picture'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['url'] = array('type' => PARAM_URL, 'null' => NULL_NOT_ALLOWED);
+        $fields['description'] = array('type' => PARAM_RAW, 'null' => NULL_ALLOWED);
+        $fields['descriptionformat'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['mailformat'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
+                'default' => $CFG->defaultpreference_mailformat);
+        $fields['maildigest'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
+                'default' => $CFG->defaultpreference_maildigest);
+        $fields['maildisplay'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
+                'default' => $CFG->defaultpreference_maildisplay);
+        $fields['autosubscribe'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
+                'default' => $CFG->defaultpreference_autosubscribe);
+        $fields['trackforums'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
+                'default' => $CFG->defaultpreference_trackforums);
+        $fields['timecreated'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['timemodified'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['trustbitmask'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
+        $fields['imagealt'] = array('type' => PARAM_TEXT, 'null' => NULL_ALLOWED);
+        $fields['lastnamephonetic'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
+        $fields['firstnamephonetic'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
+        $fields['middlename'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
+        $fields['alternatename'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
 
         self::$propertiescache = $fields;
     }
 
         self::$propertiescache = $fields;
     }
@@ -368,6 +389,38 @@ class core_user {
         return self::$propertiescache[$property];
     }
 
         return self::$propertiescache[$property];
     }
 
+    /**
+     * Validate user data.
+     *
+     * This method just validates each user field and return an array of errors. It doesn't clean the data,
+     * the methods clean() and clean_field() should be used for this purpose.
+     *
+     * @param stdClass|array $data user data object or array to be validated.
+     * @return array|true $errors array of errors found on the user object, true if the validation passed.
+     */
+    public static function validate($data) {
+        // Get all user profile fields definition.
+        self::fill_properties_cache();
+
+        foreach ($data as $property => $value) {
+            try {
+                if (isset(self::$propertiescache[$property])) {
+                    validate_param($value, self::$propertiescache[$property]['type'], self::$propertiescache[$property]['null']);
+                }
+                // Check that the value is part of a list of allowed values.
+                if (!empty(self::$propertiescache[$property]['choices']) &&
+                        !isset(self::$propertiescache[$property]['choices'][$data->$property]) &&
+                        !array_key_exists($data->$property, self::$propertiescache[$property]['choices'])) {
+                    throw new invalid_parameter_exception($value);
+                }
+            } catch (invalid_parameter_exception $e) {
+                $errors[$property] = $e->getMessage();
+            }
+        }
+
+        return empty($errors) ? true : $errors;
+    }
+
     /**
      * Clean the properties cache.
      *
     /**
      * Clean the properties cache.
      *
@@ -377,4 +430,149 @@ class core_user {
     public static function reset_caches() {
         self::$propertiescache = null;
     }
     public static function reset_caches() {
         self::$propertiescache = null;
     }
+
+    /**
+     * Clean the user data.
+     *
+     * @param stdClass|array $user the user data to be validated against properties definition.
+     * @return stdClass $user the cleaned user data.
+     */
+    public static function clean_data($user) {
+        if (empty($user)) {
+            return $user;
+        }
+
+        foreach ($user as $field => $value) {
+            // Get the property parameter type and do the cleaning.
+            try {
+                if (isset(self::$propertiescache[$field]['choices'])) {
+                    if (!array_key_exists($value, self::$propertiescache[$field]['choices'])) {
+                        if (isset(self::$propertiescache[$field]['default'])) {
+                            $user->$field = self::$propertiescache[$field]['default'];
+                        } else {
+                            $user->$field = '';
+                        }
+                    }
+                } else {
+                    $user->$field = core_user::clean_field($value, $field);
+                }
+            } catch (coding_exception $e) {
+                debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
+            }
+        }
+
+        return $user;
+    }
+
+    /**
+     * Clean a specific user field.
+     *
+     * @param string $data the user field data to be cleaned.
+     * @param string $field the user field name on the property definition cache.
+     * @return string the cleaned user data.
+     */
+    public static function clean_field($data, $field) {
+        if (empty($data) || empty($field)) {
+            return $data;
+        }
+
+        try {
+            $type = core_user::get_property_type($field);
+
+            if (isset(self::$propertiescache[$field]['choices'])) {
+                if (!array_key_exists($data, self::$propertiescache[$field]['choices'])) {
+                    if (isset(self::$propertiescache[$field]['default'])) {
+                        $data = self::$propertiescache[$field]['default'];
+                    } else {
+                        $data = '';
+                    }
+                }
+            } else {
+                $data = clean_param($data, $type);
+            }
+        } catch (coding_exception $e) {
+            debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
+        }
+
+        return $data;
+    }
+
+    /**
+     * Get the parameter type of the property.
+     *
+     * @param string $property property name to be retrieved.
+     * @throws coding_exception if the requested property name is invalid.
+     * @return int the property parameter type.
+     */
+    public static function get_property_type($property) {
+
+        self::fill_properties_cache();
+
+        if (!array_key_exists($property, self::$propertiescache)) {
+            throw new coding_exception('Invalid property requested: ' . $property);
+        }
+
+        return self::$propertiescache[$property]['type'];
+    }
+
+    /**
+     * Discover if the property is NULL_ALLOWED or NULL_NOT_ALLOWED.
+     *
+     * @param string $property property name to be retrieved.
+     * @throws coding_exception if the requested property name is invalid.
+     * @return bool true if the property is NULL_ALLOWED, false otherwise.
+     */
+    public static function get_property_null($property) {
+
+        self::fill_properties_cache();
+
+        if (!array_key_exists($property, self::$propertiescache)) {
+            throw new coding_exception('Invalid property requested: ' . $property);
+        }
+
+        return self::$propertiescache[$property]['null'];
+    }
+
+    /**
+     * Get the choices of the property.
+     *
+     * This is a helper method to validate a value against a list of acceptable choices.
+     * For instance: country, timezone, language, themes and etc.
+     *
+     * @param string $property property name to be retrieved.
+     * @throws coding_exception if the requested property name is invalid or if it does not has a list of choices.
+     * @return array the property parameter type.
+     */
+    public static function get_property_choices($property) {
+
+        self::fill_properties_cache();
+
+        if (!array_key_exists($property, self::$propertiescache) && !array_key_exists('choices',
+                self::$propertiescache[$property])) {
+
+            throw new coding_exception('Invalid property requested, or the property does not has a list of choices.');
+        }
+
+        return self::$propertiescache[$property]['choices'];
+    }
+
+    /**
+     * Get the property default.
+     *
+     * This method gets the default value of a field (if exists).
+     *
+     * @param string $property property name to be retrieved.
+     * @throws coding_exception if the requested property name is invalid or if it does not has a default value.
+     * @return string the property default value.
+     */
+    public static function get_property_default($property) {
+
+        self::fill_properties_cache();
+
+        if (!array_key_exists($property, self::$propertiescache) || !isset(self::$propertiescache[$property]['default'])) {
+            throw new coding_exception('Invalid property requested, or the property does not has a default value.');
+        }
+
+        return self::$propertiescache[$property]['default'];
+    }
 }
 }
index fc8ba43..18f671f 100644 (file)
@@ -1464,5 +1464,11 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2016030400.01);
     }
 
         upgrade_main_savepoint(true, 2016030400.01);
     }
 
+    if ($oldversion < 2016040700.01) {
+        // Update all countries to upper case.
+        $DB->execute("UPDATE {user} SET country = UPPER(country)");
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2016040700.01);
+    }
     return true;
 }
     return true;
 }
index c6ddbac..ac7fdce 100644 (file)
@@ -209,4 +209,207 @@ class core_user_testcase extends advanced_testcase {
             $this->assertRegExp('/Invalid property requested./', $e->getMessage());
         }
     }
             $this->assertRegExp('/Invalid property requested./', $e->getMessage());
         }
     }
+
+    /**
+     * Test validate() method.
+     */
+    public function test_validate() {
+
+        // Create user with just with username and firstname.
+        $record = array('username' => 's10', 'firstname' => 'Bebe Stevens');
+        $validation = core_user::validate((object)$record);
+
+        // Validate the user, should return true as the user data is correct.
+        $this->assertTrue($validation);
+
+        // Create user with incorrect data (invalid country and theme).
+        $record = array('username' => 's1', 'firstname' => 'Eric Cartman', 'country' => 'UU', 'theme' => 'beise');
+
+        // Should return an array with 2 errors.
+        $validation = core_user::validate((object)$record);
+        $this->assertArrayHasKey('country', $validation);
+        $this->assertArrayHasKey('theme', $validation);
+        $this->assertCount(2, $validation);
+
+        // Create user with malicious data (xss).
+        $record = array('username' => 's3', 'firstname' => 'Kyle<script>alert(1);<script> Broflovski');
+
+        // Should return an array with 1 error.
+        $validation = core_user::validate((object)$record);
+        $this->assertCount(1, $validation);
+        $this->assertArrayHasKey('firstname', $validation);
+    }
+
+    /**
+     * Test clean_data() method.
+     */
+    public function test_clean_data() {
+        $this->resetAfterTest(false);
+
+        $user = new stdClass();
+        $user->firstname = 'John <script>alert(1)</script> Doe';
+        $user->username = 'john%#&~%*_doe';
+        $user->email = ' john@testing.com ';
+        $user->deleted = 'no';
+        $user->description = '<b>A description <script>alert(123);</script>about myself.</b>';
+        $usercleaned = core_user::clean_data($user);
+
+        // Expected results.
+        $this->assertEquals('John alert(1) Doe', $usercleaned->firstname);
+        $this->assertEquals('john@testing.com', $usercleaned->email);
+        $this->assertEquals(0, $usercleaned->deleted);
+        $this->assertEquals('<b>A description <script>alert(123);</script>about myself.</b>', $user->description);
+        $this->assertEquals('john_doe', $user->username);
+
+        // Try to clean an invalid property (userfullname).
+        $user->userfullname = 'John Doe';
+        core_user::clean_data($user);
+        $this->assertDebuggingCalled("The property 'userfullname' could not be cleaned.");
+    }
+
+    /**
+     * Test clean_field() method.
+     */
+    public function test_clean_field() {
+
+        // Create a 'malicious' user object/
+        $user = new stdClass();
+        $user->firstname = 'John <script>alert(1)</script> Doe';
+        $user->username = 'john%#&~%*_doe';
+        $user->email = ' john@testing.com ';
+        $user->deleted = 'no';
+        $user->description = '<b>A description <script>alert(123);</script>about myself.</b>';
+        $user->userfullname = 'John Doe';
+
+        // Expected results.
+        $this->assertEquals('John alert(1) Doe', core_user::clean_field($user->firstname, 'firstname'));
+        $this->assertEquals('john_doe', core_user::clean_field($user->username, 'username'));
+        $this->assertEquals('john@testing.com', core_user::clean_field($user->email, 'email'));
+        $this->assertEquals(0, core_user::clean_field($user->deleted, 'deleted'));
+        $this->assertEquals('<b>A description <script>alert(123);</script>about myself.</b>', core_user::clean_field($user->description, 'description'));
+
+        // Try to clean an invalid property (fullname).
+        core_user::clean_field($user->userfullname, 'fullname');
+        $this->assertDebuggingCalled("The property 'fullname' could not be cleaned.");
+    }
+
+    /**
+     * Test get_property_type() method.
+     */
+    public function test_get_property_type() {
+
+        // Fetch valid properties and verify if the type is correct.
+        $type = core_user::get_property_type('username');
+        $this->assertEquals(PARAM_USERNAME, $type);
+        $type = core_user::get_property_type('email');
+        $this->assertEquals(PARAM_RAW_TRIMMED, $type);
+        $type = core_user::get_property_type('timezone');
+        $this->assertEquals(PARAM_TIMEZONE, $type);
+
+        // Try to fetch type of a non-existent properties.
+        $nonexistingproperty = 'userfullname';
+        $this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
+        core_user::get_property_type($nonexistingproperty);
+        $nonexistingproperty = 'mobilenumber';
+        $this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
+        core_user::get_property_type($nonexistingproperty);
+    }
+
+    /**
+     * Test get_property_null() method.
+     */
+    public function test_get_property_null() {
+        // Fetch valid properties and verify if it is NULL_ALLOWED or NULL_NOT_ALLOWED.
+        $property = core_user::get_property_null('username');
+        $this->assertEquals(NULL_NOT_ALLOWED, $property);
+        $property = core_user::get_property_null('password');
+        $this->assertEquals(NULL_NOT_ALLOWED, $property);
+        $property = core_user::get_property_null('imagealt');
+        $this->assertEquals(NULL_ALLOWED, $property);
+        $property = core_user::get_property_null('middlename');
+        $this->assertEquals(NULL_ALLOWED, $property);
+
+        // Try to fetch type of a non-existent properties.
+        $nonexistingproperty = 'lastnamefonetic';
+        $this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
+        core_user::get_property_null($nonexistingproperty);
+        $nonexistingproperty = 'midlename';
+        $this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
+        core_user::get_property_null($nonexistingproperty);
+    }
+
+    /**
+     * Test get_property_choices() method.
+     */
+    public function test_get_property_choices() {
+
+        // Test against country property choices.
+        $choices = core_user::get_property_choices('country');
+        $this->assertArrayHasKey('AU', $choices);
+        $this->assertArrayHasKey('BR', $choices);
+        $this->assertArrayNotHasKey('WW', $choices);
+        $this->assertArrayNotHasKey('TX', $choices);
+
+        // Test against lang property choices.
+        $choices = core_user::get_property_choices('lang');
+        $this->assertArrayHasKey('en', $choices);
+        $this->assertArrayHasKey('ko', $choices);
+        $this->assertArrayHasKey('ru', $choices);
+        $this->assertArrayNotHasKey('ww', $choices);
+        $this->assertArrayNotHasKey('yy', $choices);
+
+        // Test against theme property choices.
+        $choices = core_user::get_property_choices('theme');
+        $this->assertArrayHasKey('base', $choices);
+        $this->assertArrayHasKey('clean', $choices);
+        $this->assertArrayNotHasKey('unknowntheme', $choices);
+        $this->assertArrayNotHasKey('wrongtheme', $choices);
+
+        // Test against timezone property choices.
+        $choices = core_user::get_property_choices('timezone');
+        $this->assertArrayHasKey('America/Sao_Paulo', $choices);
+        $this->assertArrayHasKey('Australia/Perth', $choices);
+        $this->assertArrayHasKey('99', $choices);
+        $this->assertArrayHasKey('UTC', $choices);
+        $this->assertArrayNotHasKey('North Korea', $choices);
+        $this->assertArrayNotHasKey('New york', $choices);
+
+        // Try to fetch type of a non-existent properties.
+        $nonexistingproperty = 'language';
+        $this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
+        core_user::get_property_null($nonexistingproperty);
+        $nonexistingproperty = 'coutries';
+        $this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
+        core_user::get_property_null($nonexistingproperty);
+    }
+
+    /**
+     * Test get_property_default().
+     */
+    public function test_get_property_default() {
+        global $CFG;
+
+        $country = core_user::get_property_default('country');
+        $this->assertEquals($CFG->country, $country);
+        set_config('country', 'AU');
+        core_user::reset_caches();
+        $country = core_user::get_property_default('country');
+        $this->assertEquals($CFG->country, $country);
+
+        $lang = core_user::get_property_default('lang');
+        $this->assertEquals($CFG->lang, $lang);
+        set_config('lang', 'en');
+        $lang = core_user::get_property_default('lang');
+        $this->assertEquals($CFG->lang, $lang);
+
+        $timezone = core_user::get_property_default('timezone');
+        $this->assertEquals($CFG->timezone, $timezone);
+        set_config('timezone', 99);
+        core_user::reset_caches();
+        $timezone = core_user::get_property_default('timezone');
+        $this->assertEquals(99, $timezone);
+
+        $this->setExpectedException('coding_exception', 'Invalid property requested, or the property does not has a default value.');
+        core_user::get_property_default('firstname');
+    }
 }
 }