From 869a91b72eaf85a447e8fd1b78aad51a8b5676cc Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Sat, 16 Oct 2004 16:59:59 +0000
Subject: [PATCH] - Patch #11505 by Steven: 'my account' information is not
 saved.

  + Drupal 4.4 stored profile data in the serialized user->data column. Drupal 4.5 stores profile data in tables (but user->data is still available and used for other stuff, like locale or themes).  The update from 4.4 to 4.5 didn't remove the old data from the user->data column properly, because there is no mechanism in user_save to do so (it did try to unset the fields, but this has no effect).

  + On registration, hook_user('insert') is invoked after saving the data column. This means that any module-specific data is put into the data field. We cannot move hook_user('insert') higher up, because before that point, we do not have a complete $user object yet.
---
 database/database.mysql        |  2 +-
 database/database.pgsql        |  2 +-
 database/updates.inc           | 38 +++++++++++++++++---
 modules/profile.module         |  3 +-
 modules/profile/profile.module |  3 +-
 modules/user.module            | 63 ++++++++++++++++++++++++----------
 modules/user/user.module       | 63 ++++++++++++++++++++++++----------
 7 files changed, 129 insertions(+), 45 deletions(-)

diff --git a/database/database.mysql b/database/database.mysql
index 65641a4ecc1d..528ccbaea02a 100644
--- a/database/database.mysql
+++ b/database/database.mysql
@@ -759,7 +759,7 @@ INSERT INTO permission VALUES (1,'access content',0);
 INSERT INTO role (rid, name) VALUES (2, 'authenticated user');
 INSERT INTO permission VALUES (2,'access comments, access content, post comments, post comments without approval',0);
 
-REPLACE variable SET name='update_start', value='s:10:"2004-09-17;"';
+REPLACE variable SET name='update_start', value='s:10:"2004-10-16;"';
 REPLACE variable SET name='theme_default', value='s:10:"bluemarine";';
 
 REPLACE blocks SET module = 'user', delta = '0', status = '1';
diff --git a/database/database.pgsql b/database/database.pgsql
index 6e625f74f094..ac9e31b992f4 100644
--- a/database/database.pgsql
+++ b/database/database.pgsql
@@ -752,7 +752,7 @@ INSERT INTO system VALUES ('modules/taxonomy.module','taxonomy','module','',1,0,
 INSERT INTO system VALUES ('themes/bluemarine/xtemplate.xtmpl','bluemarine','theme','themes/engines/xtemplate/xtemplate.engine',1,0,0);
 INSERT INTO system VALUES ('themes/engines/xtemplate/xtemplate.engine','xtemplate','theme_engine','',1,0,0);
 
-INSERT INTO variable(name,value) VALUES('update_start', 's:10:"2004-09-17";');
+INSERT INTO variable(name,value) VALUES('update_start', 's:10:"2004-10-16";');
 INSERT INTO variable(name,value) VALUES('theme_default','s:10:"bluemarine";');
 INSERT INTO users(uid,name,mail) VALUES(0,'','');
 INSERT INTO users_roles(uid,rid) VALUES(0, 1);
diff --git a/database/updates.inc b/database/updates.inc
index 82940d9ed981..396000c8060f 100644
--- a/database/updates.inc
+++ b/database/updates.inc
@@ -83,7 +83,8 @@
   "2004-08-19" => "update_104",
   "2004-09-14" => "update_105",
   "2004-09-15" => "update_106",
-  "2004-09-17" => "update_107"
+  "2004-09-17" => "update_107",
+  "2004-10-16" => "update_108"
 );
 
 function update_32() {
@@ -946,14 +947,16 @@ function update_80() {
       if ($account->$old) {
         $edit[$new] = $account->$old;
       }
-      unset($account->$old);
+      // Force deletion of old field
+      $edit[$old] = NULL;
     }
 
     // Birthday format change:
     if ($edit['birthday']) {
       $edit['birthday'] = array('day' => $edit['birthday'], 'month' => $account->profile_birthmonth, 'year' => $account->profile_birthyear);
-      unset($account->profile_birthmonth);
-      unset($account->profile_birthyear);
+      // Force deletion of old field
+      $edit['profile_birthmonth'] = NULL;
+      $edit['profile_birthyear'] = NULL;
     }
 
     // Gender specific changes:
@@ -963,13 +966,18 @@ function update_80() {
     // Avatar specific changes:
     if ($account->profile_avatar) {
       $edit['picture'] = $account->profile_avatar;
+      // Force deletion of old field
+      $edit['profile_avatar'] = NULL;
     }
-    unset($account->profile_avatar);
 
     // Save the update record:
     user_save($account, $edit, 'Personal information');
   }
 
+  // This variable is needed to distinguish betweene 4.5-RC sites which ran a faulty
+  // update_80() and 4.5-final sites. See update_108.
+  variable_set('update_80_fix', true);
+
   return $ret;
 }
 
@@ -1873,6 +1881,26 @@ function update_107() {
   return $ret;
 }
 
+function update_108() {
+  // This update is needed for 4.5-RC sites, where profile data was not being
+  // wiped from the user->data column correctly because update_80() was faulty.
+  if (!variable_get('update_80_fix', false)) {
+    // The data field needs to be cleared of profile fields.
+    $result = db_query("SELECT uid FROM {users} WHERE data LIKE '%profile%'");
+    while ($uid = db_fetch_object($result)) {
+      $user = user_load(array('uid' => $uid->uid));
+      $unset = array();
+      foreach ($user as $key => $value) {
+        if (substr($key, 0, 8) == 'profile_') {
+          // Fields with a NULL value are wiped from the data column.
+          $unset[$key] = NULL;
+        }
+      }
+      user_save($user, $unset);
+    }
+  }
+}
+
 function update_sql($sql) {
   $edit = $_POST["edit"];
   $result = db_query($sql);
diff --git a/modules/profile.module b/modules/profile.module
index 276cefa6489b..3bf611a03238 100644
--- a/modules/profile.module
+++ b/modules/profile.module
@@ -162,7 +162,8 @@ function profile_save_profile(&$edit, &$user, $category) {
     }
     db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid);
     db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]);
-    unset($edit[$field->name], $user->{$field->name});
+    // Mark field as handled (prevents saving to user->data).
+    $edit[$field->name] = null;
   }
 }
 
diff --git a/modules/profile/profile.module b/modules/profile/profile.module
index 276cefa6489b..3bf611a03238 100644
--- a/modules/profile/profile.module
+++ b/modules/profile/profile.module
@@ -162,7 +162,8 @@ function profile_save_profile(&$edit, &$user, $category) {
     }
     db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid);
     db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]);
-    unset($edit[$field->name], $user->{$field->name});
+    // Mark field as handled (prevents saving to user->data).
+    $edit[$field->name] = null;
   }
 }
 
diff --git a/modules/user.module b/modules/user.module
index 586344719968..adfcc84251fc 100644
--- a/modules/user.module
+++ b/modules/user.module
@@ -9,13 +9,15 @@
 /**
  * Invokes hook_user() in every module.
  *
- * We cannot use module_invoke() for this, becuse the arguments need to
+ * We cannot use module_invoke() for this, because the arguments need to
  * be passed by reference.
  */
 function user_module_invoke($type, &$array, &$user, $category = NULL) {
   foreach (module_list() as $module) {
     $function = $module .'_user';
-    if (function_exists($function)) $function($type, $array, $user, $category);
+    if (function_exists($function)) {
+      $function($type, $array, $user, $category);
+    }
   }
 }
 
@@ -82,6 +84,18 @@ function user_load($array = array()) {
   return $user;
 }
 
+/**
+ * Save changes to a user account.
+ *
+ * @param $account
+ *   The $user object for the user to modify.
+ *
+ * @param $array
+ *   An array of fields and values to save. For example array('name' => 'My name');
+ *
+ * @param $category
+ *   (optional) The category for storing profile information in.
+ */
 function user_save($account, $array = array(), $category = 'account') {
   // Dynamically compose a SQL query:
   $user_fields = user_fields();
@@ -96,13 +110,18 @@ function user_save($account, $array = array(), $category = 'account') {
       }
       else if (substr($key, 0, 4) !== 'auth') {
         if (in_array($key, $user_fields)) {
-          // escape '%'s:
-          $value = str_replace('%', '%%', $value);
+          // Save standard fields
           $query .= "$key = '%s', ";
           $v[] = $value;
         }
         else {
-          $data[$key] = $value;
+          if ($value === null) {
+            // Setting a field to null deletes it from the data column.
+            unset($data[$key]);
+          }
+          else {
+            $data[$key] = $value;
+          }
         }
       }
     }
@@ -111,7 +130,7 @@ function user_save($account, $array = array(), $category = 'account') {
 
     db_query("UPDATE {users} SET $query changed = %d WHERE uid = %d", array_merge($v, array(time(), $account->uid)));
 
-    // reload user roles if provided
+    // Reload user roles if provided
     if (is_array($array['roles'])) {
       db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
 
@@ -120,6 +139,7 @@ function user_save($account, $array = array(), $category = 'account') {
       }
     }
 
+    // Refresh user object
     $user = user_load(array('uid' => $account->uid));
   }
   else {
@@ -127,6 +147,9 @@ function user_save($account, $array = array(), $category = 'account') {
     $array['changed'] = time();
     $array['uid'] = db_next_id('{users}_uid');
 
+    // Note, we wait with saving the data column to prevent module-handled
+    // fields from being saved there. We cannot invoke hook_user('insert') here
+    // because we don't have a fully initialized user object yet.
     foreach ($array as $key => $value) {
       if ($key == 'pass') {
         $fields[] = check_query($key);
@@ -139,36 +162,40 @@ function user_save($account, $array = array(), $category = 'account') {
           $values[] = $value;
           $s[] = "'%s'";
         }
-        else {
-          $data[$key] = $value;
-        }
       }
     }
-
-    $fields[] = 'data';
-    $values[] = serialize($data);
-    $s[] = "'%s'";
-
     db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
 
     // Reload user roles (delete just to be safe).
     db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
-
     foreach ($array['roles'] as $rid) {
       db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
     }
 
-    $user = user_load(array('name' => $array['name']));
+    // Build the initial user object.
+    $user = user_load(array('uid' => $array['uid']));
+
+    user_module_invoke('insert', $array, $user, $category);
 
-    module_invoke_all('user', 'insert', $array, $user, $category);
+    // Build and save the serialized data field now
+    $data = array();
+    foreach ($array as $key => $value) {
+      if ((substr($key, 0, 4) !== 'auth') && (!in_array($key, $user_fields)) && ($value !== null)) {
+        $data[$key] = $value;
+      }
+    }
+    db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
+
+    // Build the finished user object.
+    $user = user_load(array('uid' => $array['uid']));
   }
 
+  // Save distributed authentication mappings
   foreach ($array as $key => $value) {
     if (substr($key, 0, 4) == 'auth') {
       $authmaps[$key] = $value;
     }
   }
-
   if ($authmaps) {
     user_set_authmaps($user, $authmaps);
   }
diff --git a/modules/user/user.module b/modules/user/user.module
index 586344719968..adfcc84251fc 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -9,13 +9,15 @@
 /**
  * Invokes hook_user() in every module.
  *
- * We cannot use module_invoke() for this, becuse the arguments need to
+ * We cannot use module_invoke() for this, because the arguments need to
  * be passed by reference.
  */
 function user_module_invoke($type, &$array, &$user, $category = NULL) {
   foreach (module_list() as $module) {
     $function = $module .'_user';
-    if (function_exists($function)) $function($type, $array, $user, $category);
+    if (function_exists($function)) {
+      $function($type, $array, $user, $category);
+    }
   }
 }
 
@@ -82,6 +84,18 @@ function user_load($array = array()) {
   return $user;
 }
 
+/**
+ * Save changes to a user account.
+ *
+ * @param $account
+ *   The $user object for the user to modify.
+ *
+ * @param $array
+ *   An array of fields and values to save. For example array('name' => 'My name');
+ *
+ * @param $category
+ *   (optional) The category for storing profile information in.
+ */
 function user_save($account, $array = array(), $category = 'account') {
   // Dynamically compose a SQL query:
   $user_fields = user_fields();
@@ -96,13 +110,18 @@ function user_save($account, $array = array(), $category = 'account') {
       }
       else if (substr($key, 0, 4) !== 'auth') {
         if (in_array($key, $user_fields)) {
-          // escape '%'s:
-          $value = str_replace('%', '%%', $value);
+          // Save standard fields
           $query .= "$key = '%s', ";
           $v[] = $value;
         }
         else {
-          $data[$key] = $value;
+          if ($value === null) {
+            // Setting a field to null deletes it from the data column.
+            unset($data[$key]);
+          }
+          else {
+            $data[$key] = $value;
+          }
         }
       }
     }
@@ -111,7 +130,7 @@ function user_save($account, $array = array(), $category = 'account') {
 
     db_query("UPDATE {users} SET $query changed = %d WHERE uid = %d", array_merge($v, array(time(), $account->uid)));
 
-    // reload user roles if provided
+    // Reload user roles if provided
     if (is_array($array['roles'])) {
       db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
 
@@ -120,6 +139,7 @@ function user_save($account, $array = array(), $category = 'account') {
       }
     }
 
+    // Refresh user object
     $user = user_load(array('uid' => $account->uid));
   }
   else {
@@ -127,6 +147,9 @@ function user_save($account, $array = array(), $category = 'account') {
     $array['changed'] = time();
     $array['uid'] = db_next_id('{users}_uid');
 
+    // Note, we wait with saving the data column to prevent module-handled
+    // fields from being saved there. We cannot invoke hook_user('insert') here
+    // because we don't have a fully initialized user object yet.
     foreach ($array as $key => $value) {
       if ($key == 'pass') {
         $fields[] = check_query($key);
@@ -139,36 +162,40 @@ function user_save($account, $array = array(), $category = 'account') {
           $values[] = $value;
           $s[] = "'%s'";
         }
-        else {
-          $data[$key] = $value;
-        }
       }
     }
-
-    $fields[] = 'data';
-    $values[] = serialize($data);
-    $s[] = "'%s'";
-
     db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
 
     // Reload user roles (delete just to be safe).
     db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
-
     foreach ($array['roles'] as $rid) {
       db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
     }
 
-    $user = user_load(array('name' => $array['name']));
+    // Build the initial user object.
+    $user = user_load(array('uid' => $array['uid']));
+
+    user_module_invoke('insert', $array, $user, $category);
 
-    module_invoke_all('user', 'insert', $array, $user, $category);
+    // Build and save the serialized data field now
+    $data = array();
+    foreach ($array as $key => $value) {
+      if ((substr($key, 0, 4) !== 'auth') && (!in_array($key, $user_fields)) && ($value !== null)) {
+        $data[$key] = $value;
+      }
+    }
+    db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
+
+    // Build the finished user object.
+    $user = user_load(array('uid' => $array['uid']));
   }
 
+  // Save distributed authentication mappings
   foreach ($array as $key => $value) {
     if (substr($key, 0, 4) == 'auth') {
       $authmaps[$key] = $value;
     }
   }
-
   if ($authmaps) {
     user_set_authmaps($user, $authmaps);
   }
-- 
GitLab