user save() has some crufty code which needs cleaning. I clean what I can in this patch and leave the API changes for Drupal7. This patch

- Uses drupal_write_record() for writing to users table
- Updates user_fields() to use schema API instead of its own way to discover the fields in the table
- Fixes a buglet during registration and user edit where we insert form_build_id into users.data. thats an internal value of no interest to a user.

This patch adds a serialize param to users.data in hook_schema so you have to refresh your Schema to test (use devel's clear cache or empty cache table or start with a fresh Drupal).

There is no performance impact from this patch - just code cleanup/updating. The queries that get issued are unchanged.

This patch brings us closer to being able to drop in a data API as specified at http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/object_driver/ or http://drupal.org/node/79684.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dries’s picture

I did a code review and the code looks good, Moshe. One remark: user_fields() is only called from one location. We should be able to nuke that function now.

(I haven't tested this patch yet.)

moshe weitzman’s picture

FileSize
8.84 KB

This version gets rid of user_fields().

chx’s picture

I cleaned up a bit more cruft around the ifs that set/unset $data parts.

moshe weitzman’s picture

Thanks chx.

In order to test this, i suggest the following:
- register a new user
- login as that user
- submit profile page edit but don't change password
- logout
- login as same user (checks that password was indeed unchanged)
- edit profile and this time change password.
- logout
- login with new pass and assure you get in.

moshe weitzman’s picture

FileSize
10.01 KB

Add to that test plan:
- submit a profile edit when the profile field appears on its own tab (i.e. not on the tab with username/pass).

Once I did that I found a need to handle the case when no changes are needed during a call to drupal_write_record(). Thats why the attached patch modifies that function slightly.

DrupalTestbedBot tested moshe weitzman's patch (http://drupal.org/files/issues/mw_90.patch), the patch passed. For more information visit http://testing.drupal.org/node/117

Dries’s picture

I've tested this patch and it works. The quality of the code comments needs to improve though. For example for ext. auth are should be written in full so that it is accessible for less savvy developers. That line is _critical_ for security so let's not take that lightly. Thanks.

Is the + if (!empty($data)) { check in user_save() still required now?

moshe weitzman’s picture

FileSize
10.78 KB

I rewrote that comment and made drupal_write_record a bit more robust so that we don't have to check f (!empty($data)) as Dries suggests.

Note that all traces of distributed auth will be removed from user_save() with this patch - http://drupal.org/node/181578. I will reroll that one after this one lands.

Anyone testing this patch should remember to wipe schema cache before executing the test plan above.

moshe weitzman’s picture

FileSize
11.02 KB

rerolled since one hunk failed. i rechecked the test plan and it works fine.

bjaspan’s picture

I reviewed this patch and it works fine. Besides Moshe's test plan, I also tested the serialized data field: add a value, preserve a value if it is unset, remove a value if it is set to NULL. It probably would have taken less time to write simpletests than to test it manually.

I made some changes:

1. Instead of setting $user_fields = array_keys($user_table['fields']) and then testing in_array($key, $user_fields), I set $user_fields = $user_table['fields'] and then test empty($user_fields[$key]). This turns O(n^2) to O(n lg n) for no extra work.

2. I improved the docs for user_save() and moved a comment for coding standards.

A new patch with these changes is attached.

I also note that this patch introduces an API change: Previously, in hook_user('update'), $edit['pass'] was always set but was '' if the user did not enter a password. Now, $edit['pass'] is unset is the user enters no password. This triggered an E_ALL error in persistent_login.module which is how I noticed. I think the new behavior is fine but should be documented in hook_user and perhaps in the D5 > D6 module upgrade notes.

If Moshe reviews and approves my in_array() to empty() and comment change, this is RTBC. Whether or not this constitutes a "bugfix" that gets into D6 is a separate issue.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

barry's changes are good. RTBC

moshe weitzman’s picture

Status: Reviewed & tested by the community » Needs work

Actually, the new comment on user_save() has some funky language in it. In fact, I can't figure out whats intended. Since thats my only concern, i ask Barry to re-word that and then RTBC.

moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
12.35 KB

I fixed up that comment.

bjaspan’s picture

Sorry for not doing this myself, I was out of town all week at a client site. Just got back last night.

Dries’s picture

This looks a bit like a hack. I can see why it's necessary but it ain't pretty. Maybe there is a creative workaround that looks slightly prettier? ;-)

+  if (empty($fields)) {
+    // No changes requested. 
+    // If we began with an array, convert back so we don't surprise the caller.
+    if ($array) {
+      $object = (array)$object;
     }
+    return;

This looks really odd and not something we had in the old code, I believe. Is this an inherent drawback of the new mechanism?

+  else {
+    // Avoid overwriting an existing password with a blank password.
+    unset($array['pass']);
+  }
bjaspan’s picture

Regarding unset($array['pass']), the current code does this:

      if ($key == 'pass' && !empty($value)) {
        $query .= "$key = '%s', ";
        $v[] = md5($value);
      }

If no key of the array is 'pass' *or* if pass is empty, the query never sets that column. drupal_write_record() does the former ("don't set columns whose keys are unset in the passed object") but cannot assume that empty-string values also should not be set (because often they should be).

So, yes, this unset is effectively something we have in the current code in a different form, and requiring it is not a drawback of drupal_write_record(). It is just an example of having to normalize a data structure to comply with a slightly more general-purpose API function.

Dries’s picture

Thanks for clarifying, Barry! You're absolutely correct.

moshe weitzman’s picture

It is true that drupal_write_record() does some awkward type switching. But we try to be a convenient API function for all callers. The only solution I can see is to standardize on arrays or objects for our major objects and fapi. Since thats feasible in this patch, and neither Barry nor I nor Dries sees a better solution, I ask that we consider committing with this known awkwardness.

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Needs work

$ patch -p0 < mw_105.patch
patching file includes/common.inc
Hunk #1 succeeded at 3291 (offset 126 lines).
patching file modules/profile/profile.module
Hunk #1 FAILED at 344.
1 out of 1 hunk FAILED -- saving rejects to file modules/profile/profile.module.rej
patching file modules/user/user.install
patching file modules/user/user.module
Hunk #4 succeeded at 2081 (offset 3 lines).
patching file modules/user/user.pages.inc

moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
12.37 KB

rerolled

Gábor Hojtsy’s picture

In all honesty, I am not sure about what to do with this patch. We have seen several pass by reference (eg. in node revision saving), default value setting, database compatibility etc. issues around drupal_write_record() implementations elsewhere, and needed to work on fixing all those. As far as I see this code change is a nice cleanup, but does not fix a bug or introduce anything new. Changing the user saving implementation this late does not sound the best idea, although I wholeheartedly agree that the current implementation looks ugly, and it should be fixed when safe. Let us know if you think otherwise.

bjaspan’s picture

I agree that this is not mandatory. We're way past where the line for D6 should have been drawn...

Gábor Hojtsy’s picture

Version: 6.x-dev » 7.x-dev

Thanks for understanding. Moving to D7. Keeping status, so it is committed early.

pwolanin’s picture

looks like a couple small parts of this should be for D6 - like

@@ -247,6 +247,7 @@ function user_schema() {
         'type' => 'text',
         'not null' => FALSE,
         'size' => 'big',
+        'serialize' => TRUE,
moshe weitzman’s picture

yes, there are some bug fixes here too. hope someone will extract them.

leoman730’s picture

Hey guys,

I need some info on this silly question, often time i see attachment with extension .patch. I know the intense of this file is to fix issue. My question is how can I use this file and apply it to the module. Could anyone provide me some steps on how to use it? Many thanks.

keith.smith’s picture

leoman730: See http://drupal.org/patch/apply for more information.

hswong3i’s picture

catch judge that #211496 is duplicated with this issue, but sorry that I don't really think so: #211496 is introduce because of incorrect data type matching before we build the queries. I seems this as something more critical that refactor overall user_save() handling for elegant. BTW, I keep a message here as a reference of that bug, and so let's solve it after D6 ;-)

moshe weitzman’s picture

FileSize
12.1 KB

Here is a reroll to fix a little decay. I resisted the temptation to expand this patch; nothing has changed so we preserve the RTBC status.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD. Thanks.

pwolanin’s picture

Version: 7.x-dev » 6.x-dev
Status: Fixed » Patch (to be ported)

As I noted above, there are a few small pieces of this patch that should be in 6.x

Freso’s picture

I think it would be good if someone who knew what to extract for 6.x (pwolanin? moshe?) either ported those changes to 6.x or were more specific so someone (me, perhaps) could easily tell what to port and what to discard.

pwolanin’s picture

@Freso - looks to me like this might qualify as 6.x bug fixes to be extracted from the patch:

all the changes to includes/common.inc
the change to modules/user/user.install
all changes to modules/user/user.pages.inc
the change to function user_register_submit() in modules/user/user.module

Freso’s picture

Status: Patch (to be ported) » Needs review
FileSize
4.84 KB

Alright then. Here's a straight port of the things pwolanin picked out in #33. It is exactly the same code changes as those in #29, as that patch applied cleanly to DRUPAL-6 (with some offset).

I haven't tested this though, as I must admit I haven't even read the issue through to be sure what needs to be tested. I just thought I'd put some work towards cleaning out the 6.x "to be ported" queue. :)

moshe weitzman’s picture

Assigned: moshe weitzman » Unassigned
pwolanin’s picture

Looks like parts of this last patch already made it into core for drupal_write_record() - is this issue too old/duplciate now?

pwolanin’s picture

Status: Needs review » Needs work

Status: Needs work » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.