From 63effdfa6c7bfda16ad79042bbe7ae3f74965dbb Mon Sep 17 00:00:00 2001
From: Albert Gasset <agasset@cvaconsulting.com>
Date: Fri, 15 Jan 2016 09:35:48 +0100
Subject: [PATCH] MDL-40613 auth_ldap: sync custom profile fields

---
 auth/db/auth.php     |  61 ---------------------------
 auth/ldap/auth.php   | 114 +++++++++++++++++----------------------------------
 lib/authlib.php      |  84 +++++++++++++++++++++++++++++++++++++
 user/profile/lib.php |  37 +++++++++++++----
 4 files changed, 151 insertions(+), 145 deletions(-)

diff --git a/auth/db/auth.php b/auth/db/auth.php
index 147ef604cb64..3aa112e69fc9 100644
--- a/auth/db/auth.php
+++ b/auth/db/auth.php
@@ -521,67 +521,6 @@ function get_userinfo_asobj($username) {
         return $user;
     }
 
-    /**
-     * will update a local user record from an external source.
-     * is a lighter version of the one in moodlelib -- won't do
-     * expensive ops such as enrolment.
-     *
-     * If you don't pass $updatekeys, there is a performance hit and
-     * values removed from DB won't be removed from moodle.
-     *
-     * @param string $username username
-     * @param bool $updatekeys
-     * @return stdClass
-     */
-    function update_user_record($username, $updatekeys=false) {
-        global $CFG, $DB;
-
-        //just in case check text case
-        $username = trim(core_text::strtolower($username));
-
-        // get the current user record
-        $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
-        if (empty($user)) { // trouble
-            error_log("Cannot update non-existent user: $username");
-            print_error('auth_dbusernotexist','auth_db',$username);
-            die;
-        }
-
-        // Ensure userid is not overwritten.
-        $userid = $user->id;
-        $needsupdate = false;
-
-        $updateuser = new stdClass();
-        $updateuser->id = $userid;
-        if ($newinfo = $this->get_userinfo($username)) {
-            $newinfo = truncate_userinfo($newinfo);
-
-            if (empty($updatekeys)) { // All keys? This does not support removing values.
-                $updatekeys = array_keys($newinfo);
-            }
-
-            foreach ($updatekeys as $key) {
-                if (isset($newinfo[$key])) {
-                    $value = $newinfo[$key];
-                } else {
-                    $value = '';
-                }
-
-                if (!empty($this->config->{'field_updatelocal_' . $key})) {
-                    if (isset($user->{$key}) and $user->{$key} != $value) { // Only update if it's changed.
-                        $needsupdate = true;
-                        $updateuser->$key = $value;
-                    }
-                }
-            }
-        }
-        if ($needsupdate) {
-            require_once($CFG->dirroot . '/user/lib.php');
-            user_update_user($updateuser);
-        }
-        return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
-    }
-
     /**
      * Called when the user record is updated.
      * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
diff --git a/auth/ldap/auth.php b/auth/ldap/auth.php
index 9d3a4ad97021..28fa3d115f47 100644
--- a/auth/ldap/auth.php
+++ b/auth/ldap/auth.php
@@ -970,6 +970,16 @@ function sync_users($do_updates=true) {
                     set_user_preference('auth_forcepasswordchange', 1, $id);
                 }
 
+                // Save custom profile fields.
+                $euser->profile = array();
+                foreach ($user as $key => $value) {
+                    if (preg_match('/^profile_field_(.*)$/', $key, $match)) {
+                        $field = $match[1];
+                        $euser->profile[$field] = $user->$key;
+                    }
+                }
+                profile_save_custom_fields($euser);
+
                 // Add course creators if needed
                 if ($creatorrole !== false and $this->iscreator($user->username)) {
                     role_assign($creatorrole->id, $id, $sitecontext->id, $this->roleauth);
@@ -988,70 +998,6 @@ function sync_users($do_updates=true) {
         return true;
     }
 
-    /**
-     * Update a local user record from an external source.
-     * This is a lighter version of the one in moodlelib -- won't do
-     * expensive ops such as enrolment.
-     *
-     * If you don't pass $updatekeys, there is a performance hit and
-     * values removed from LDAP won't be removed from moodle.
-     *
-     * @param string $username username
-     * @param boolean $updatekeys true to update the local record with the external LDAP values.
-     * @param bool $triggerevent set false if user_updated event should not be triggered.
-     *             This will not affect user_password_updated event triggering.
-     * @return stdClass|bool updated user record or false if there is no new info to update.
-     */
-    function update_user_record($username, $updatekeys = false, $triggerevent = false) {
-        global $CFG, $DB;
-
-        // Just in case check text case
-        $username = trim(core_text::strtolower($username));
-
-        // Get the current user record
-        $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
-        if (empty($user)) { // trouble
-            error_log($this->errorlogtag.get_string('auth_dbusernotexist', 'auth_db', '', $username));
-            print_error('auth_dbusernotexist', 'auth_db', '', $username);
-            die;
-        }
-
-        // Protect the userid from being overwritten
-        $userid = $user->id;
-
-        if ($newinfo = $this->get_userinfo($username)) {
-            $newinfo = truncate_userinfo($newinfo);
-
-            if (empty($updatekeys)) { // all keys? this does not support removing values
-                $updatekeys = array_keys($newinfo);
-            }
-
-            if (!empty($updatekeys)) {
-                $newuser = new stdClass();
-                $newuser->id = $userid;
-
-                foreach ($updatekeys as $key) {
-                    if (isset($newinfo[$key])) {
-                        $value = $newinfo[$key];
-                    } else {
-                        $value = '';
-                    }
-
-                    if (!empty($this->config->{'field_updatelocal_' . $key})) {
-                        // Only update if it's changed.
-                        if ($user->{$key} != $value) {
-                            $newuser->$key = $value;
-                        }
-                    }
-                }
-                user_update_user($newuser, false, $triggerevent);
-            }
-        } else {
-            return false;
-        }
-        return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
-    }
-
     /**
      * Bulk insert in SQL's temp table
      */
@@ -1199,6 +1145,14 @@ function user_update($olduser, $newuser) {
             return false;
         }
 
+        // Load old custom fields.
+        profile_load_custom_fields($olduser, false);
+
+        $fields = array();
+        foreach (profile_get_custom_fields(false) as $field) {
+            $fields[$field->shortname] = $field;
+        }
+
         $success = true;
         $user_info_result = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
         if ($user_info_result) {
@@ -1217,19 +1171,25 @@ function user_update($olduser, $newuser) {
             $user_entry = array_change_key_case($user_entry[0], CASE_LOWER);
 
             foreach ($attrmap as $key => $ldapkeys) {
-                $profilefield = '';
-                // Only process if the moodle field ($key) has changed and we
-                // are set to update LDAP with it
-                $customprofilefield = 'profile_field_' . $key;
-                if (isset($olduser->$key) and isset($newuser->$key)
-                    and ($olduser->$key !== $newuser->$key)) {
-                    $profilefield = $key;
-                } else if (isset($olduser->$customprofilefield) && isset($newuser->$customprofilefield)
-                    && $olduser->$customprofilefield !== $newuser->$customprofilefield) {
-                    $profilefield = $customprofilefield;
+                if (preg_match('/^profile_field_(.*)$/', $key, $match)) {
+                    // Custom field.
+                    $fieldname = $match[1];
+                    if (isset($fields[$fieldname])) {
+                        $class = 'profile_field_' . $fields[$fieldname]->datatype;
+                        $formfield = new $class($fields[$fieldname]->id, $olduser->id);
+                        $oldvalue = isset($olduser->profile[$fieldname]) ? $olduser->profile[$fieldname] : null;
+                    } else {
+                        $oldvalue = null;
+                    }
+                    $newvalue = $formfield->edit_save_data_preprocess($newuser->{$formfield->inputname}, new stdClass);
+                } else {
+                    // Standard field.
+                    $oldvalue = isset($olduser->$key) ? $olduser->$key : null;
+                    $newvalue = isset($newuser->$key) ? $newuser->$key : null;
                 }
 
-                if (!empty($profilefield) && !empty($this->config->{'field_updateremote_' . $key})) {
+                // Only process if the moodle field has changed and we are set to update LDAP with it.
+                if ($newvalue !== null and $newvalue !== $oldvalue and !empty($this->config->{'field_updateremote_' . $key})) {
                     // For ldap values that could be in more than one
                     // ldap key, we will do our best to match
                     // where they came from
@@ -1242,9 +1202,9 @@ function user_update($olduser, $newuser) {
                         $ambiguous = false;
                     }
 
-                    $nuvalue = core_text::convert($newuser->$profilefield, 'utf-8', $this->config->ldapencoding);
+                    $nuvalue = core_text::convert($newvalue, 'utf-8', $this->config->ldapencoding);
                     empty($nuvalue) ? $nuvalue = array() : $nuvalue;
-                    $ouvalue = core_text::convert($olduser->$profilefield, 'utf-8', $this->config->ldapencoding);
+                    $ouvalue = core_text::convert($oldvalue, 'utf-8', $this->config->ldapencoding);
 
                     foreach ($ldapkeys as $ldapkey) {
                         $ldapkey   = $ldapkey;
diff --git a/lib/authlib.php b/lib/authlib.php
index 9c17d2784add..424fa65481c5 100644
--- a/lib/authlib.php
+++ b/lib/authlib.php
@@ -611,6 +611,90 @@ public function get_custom_user_profile_fields() {
      */
     public function postlogout_hook($user) {
     }
+
+    /**
+     * Update a local user record from an external source.
+     * This is a lighter version of the one in moodlelib -- won't do
+     * expensive ops such as enrolment.
+     *
+     * @param string $username username
+     * @param array $updatekeys fields to update, false updates all fields.
+     * @param bool $triggerevent set false if user_updated event should not be triggered.
+     *             This will not affect user_password_updated event triggering.
+     * @return stdClass|bool updated user record or false if there is no new info to update.
+     */
+    protected function update_user_record($username, $updatekeys = false, $triggerevent = false) {
+        global $CFG, $DB;
+
+        require_once($CFG->dirroot.'/user/profile/lib.php');
+
+        // Just in case check text case.
+        $username = trim(core_text::strtolower($username));
+
+        // Get the current user record.
+        $user = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id));
+        if (empty($user)) { // Trouble.
+            error_log($this->errorlogtag.get_string('auth_dbusernotexist', 'auth_db', '', $username));
+            print_error('auth_dbusernotexist', 'auth_db', '', $username);
+            die;
+        }
+
+        // Load all custom fields into $user->profile.
+        profile_load_custom_fields($user, false);
+
+        // Protect the userid from being overwritten.
+        $userid = $user->id;
+
+        $needsupdate = false;
+
+        if ($newinfo = $this->get_userinfo($username)) {
+            $newinfo = truncate_userinfo($newinfo);
+
+            if (empty($updatekeys)) { // All keys? this does not support removing values.
+                $updatekeys = array_keys($newinfo);
+            }
+
+            if (!empty($updatekeys)) {
+                $newuser = new stdClass();
+                $newuser->id = $userid;
+                $newuser->profile = array();
+
+                foreach ($updatekeys as $key) {
+                    if (isset($newinfo[$key])) {
+                        $value = $newinfo[$key];
+                    } else {
+                        $value = '';
+                    }
+
+                    if (!empty($this->config->{'field_updatelocal_' . $key})) {
+                        if (preg_match('/^profile_field_(.*)$/', $key, $match)) {
+                            // Custom field.
+                            $field = $match[1];
+                            $currentvalue = isset($user->profile[$field]) ? $user->profile[$field] : null;
+                            $newuser->profile[$field] = $value;
+                        } else {
+                            // Standard field.
+                            $currentvalue = isset($user->$key) ? $user->$key : null;
+                            $newuser->$key = $value;
+                        }
+
+                        // Only update if it's changed.
+                        if ($currentvalue !== $value) {
+                            $needsupdate = true;
+                        }
+                    }
+                }
+            }
+
+            if ($needsupdate) {
+                user_update_user($newuser, false, $triggerevent);
+                profile_save_custom_fields($newuser);
+                return $DB->get_record('user', array('id' => $userid, 'deleted' => 0));
+            }
+        }
+
+        return false;
+    }
 }
 
 /**
diff --git a/user/profile/lib.php b/user/profile/lib.php
index 41470bbbd4ed..c08f522b4d71 100644
--- a/user/profile/lib.php
+++ b/user/profile/lib.php
@@ -561,9 +561,10 @@ function profile_signup_fields($mform) {
 /**
  * Returns an object with the custom profile fields set for the given user
  * @param integer $userid
+ * @param bool $onlyinuserobject True if you only want the ones in $USER
  * @return stdClass
  */
-function profile_user_record($userid) {
+function profile_user_record($userid, $onlyinuserobject = true) {
     global $CFG, $DB;
 
     $usercustomfields = new stdClass();
@@ -573,7 +574,7 @@ function profile_user_record($userid) {
             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
             $newfield = 'profile_field_'.$field->datatype;
             $formfield = new $newfield($field->id, $userid);
-            if ($formfield->is_user_object_data()) {
+            if (!$onlyinuserobject or $formfield->is_user_object_data()) {
                 $usercustomfields->{$field->shortname} = $formfield->data;
             }
         }
@@ -621,14 +622,36 @@ function profile_get_custom_fields($onlyinuserobject = false) {
 /**
  * Load custom profile fields into user object
  *
- * Please note originally in 1.9 we were using the custom field names directly,
- * but it was causing unexpected collisions when adding new fields to user table,
- * so instead we now use 'profile_' prefix.
+ * @param stdClass $user user object
+ * @param bool $onlyinuserobject True if you only want the ones in $USER
+ */
+function profile_load_custom_fields($user, $onlyinuserobject = true) {
+    $user->profile = (array)profile_user_record($user->id, $onlyinuserobject);
+}
+
+/**
+ * Save custom profile fields in user object
  *
  * @param stdClass $user user object
  */
-function profile_load_custom_fields($user) {
-    $user->profile = (array)profile_user_record($user->id);
+function profile_save_custom_fields($user) {
+    global $DB;
+
+    if ($fields = $DB->get_records('user_info_field')) {
+        foreach ($fields as $field) {
+            if (isset($user->profile[$field->shortname])) {
+                $conditions = array('fieldid' => $field->id, 'userid' => $user->id);
+                $id = $DB->get_field('user_info_data', 'id', $conditions);
+                $data = $user->profile[$field->shortname];
+                if ($id) {
+                    $DB->set_field('user_info_data', 'data', $data, array('id' => $id));
+                } else {
+                    $record = array('fieldid' => $field->id, 'userid' => $user->id, 'data' => $data);
+                    $DB->insert_record('user_info_data', $record);
+                }
+            }
+        }
+    }
 }
 
 /**
