Merge lp:~catch-drupal/pressflow/load_cache into lp:pressflow

Proposed by Nathaniel Catchpole
Status: Rejected
Rejected by: David Strauss
Proposed branch: lp:~catch-drupal/pressflow/load_cache
Merge into: lp:pressflow
Diff against target: 821 lines (+739/-1)
8 files modified
modules/node/node.module (+39/-0)
modules/node_load_cache/node_load_cache.info (+5/-0)
modules/node_load_cache/node_load_cache.install (+40/-0)
modules/node_load_cache/node_load_cache.module (+207/-0)
modules/user/user.module (+86/-1)
modules/user_load_cache/user_load_cache.info (+5/-0)
modules/user_load_cache/user_load_cache.install (+31/-0)
modules/user_load_cache/user_load_cache.module (+326/-0)
To merge this branch: bzr merge lp:~catch-drupal/pressflow/load_cache
Reviewer Review Type Date Requested Status
Nathaniel Catchpole (community) Needs Resubmitting
Narayan Newton Needs Fixing
Review via email: mp+39241@code.launchpad.net

Description of the change

This is a port of the tag1 load_cache.patch to pressflow with a few changes:

# The patches to node and user module follow the same pattern as that used for path.inc/path_alias_cache - allow a module to implement the function if it exists, otherwise just do what core does.

# Due to this all the caching logic has been moved to node_load_cache.module and user_load_cache.module respectively, these are added to /modules

This has only had light testing and isn't in production yet. However it'll be getting a lot more testing over the next few weeks.

To post a comment you must log in.
98. By Nathaniel <catch@catch-toshiba>

Merge in pressflow trunk.

99. By Nathaniel <catch@catch-toshiba>

Update from launchpad.

100. By Nathaniel <catch@catch-toshiba>

Clear cache for translations when module is in a translation set.

Revision history for this message
Narayan Newton (nnewton-drupal) wrote :

First pass of this looks good and very similar to what we did with the path alias cache. I was in the process of doing something like this concurrently for another client when I found your branch and am now testing this on the client site.

-N

101. By Nathaniel <catch@catch-toshiba>

Add default argument for node_load_cache_user().

Revision history for this message
Narayan Newton (nnewton-drupal) wrote :

Hi Nathan,

I noticed a small issue in the user_save hook which was preventing user_save's from completing on a test site. A patch is below:

=== modified file 'modules/user/user.module'
--- modules/user/user.module 2010-10-25 02:49:05 +0000
+++ modules/user/user.module 2010-12-06 19:06:19 +0000
@@ -265,10 +265,10 @@

   if ($hook_module !== FALSE) {
     $function = $hook_module . '_' . $hook;
- $return = $function($account, $array = array(), $category = 'account');
+ $return = $function($account, $array, $category);
   }
    else {
- $return = _user_save_direct($account, $array = array(), $category = 'account');
+ $return = _user_save_direct($account, $array, $category);
   }
   return $return;
 }

review: Needs Fixing
102. By Nathaniel Catchpole <catch@catch-laptop>

Fix user_save(), via Narayan.

Revision history for this message
Nathaniel Catchpole (catch-drupal) wrote :

Thanks Narayan, applied that patch to the branch, resubmitting.

review: Needs Resubmitting
Revision history for this message
Narayan Newton (nnewton-drupal) wrote :

Hey catch,

Here is a patch to update this to 6.20. Mind looking it over. I didn't push the array rename into the submodule, although we may want to do that.

http://nnewton.org/update_load_cache_to_6.20.diff

103. By Nathaniel Catchpole <catch@catch-laptop>

Always use ->uid, since ['uid'] can be null/0 on login.

104. By Nathaniel <catch@catch-toshiba>

Merge in trunk.

105. By Nathaniel <catch@catch-toshiba>

Didn't quite resolve one merge conflict.

106. By Nathaniel <catch@catch-toshiba>

Set module weight heavier to ensure node_load() cache is cleared after all other node hook implementations run on node_save().

Revision history for this message
Alex Andrascu (office-visualcandy-eu) wrote :

What's the status of this branch. Anything i should know before giving it a test drive ?

Thanks.

Revision history for this message
Nathaniel Catchpole (catch-drupal) wrote :

It should be quite stable, however I'm no longer maintaining these branches on launchpad - have moved to github instead at https://github.com/tag1consulting/pressflow6/tree/load_cache

Revision history for this message
Alex Andrascu (office-visualcandy-eu) wrote :

Hi Nat,

Thanks alot for letting me know. I've installed it on blottr.com (updating from Pressflow 6.20)
Everything went smooth. User cache and node cache enabled and mapped to their memcached bins.

Looks fine to me. Is there a new version of this one on git ?

Cheers

Revision history for this message
David Strauss (davidstrauss) wrote :

Just to let folks here know, I've moved Pressflow 6 to GitHub: https://github.com/pressflow/6

Please post any new requested changes as pull requests there for review and integration.

Revision history for this message
David Strauss (davidstrauss) wrote :

Pressflow 6 is now maintained on GitHub, so I'm marking this merge as rejected.

Unmerged revisions

106. By Nathaniel <catch@catch-toshiba>

Set module weight heavier to ensure node_load() cache is cleared after all other node hook implementations run on node_save().

105. By Nathaniel <catch@catch-toshiba>

Didn't quite resolve one merge conflict.

104. By Nathaniel <catch@catch-toshiba>

Merge in trunk.

103. By Nathaniel Catchpole <catch@catch-laptop>

Always use ->uid, since ['uid'] can be null/0 on login.

102. By Nathaniel Catchpole <catch@catch-laptop>

Fix user_save(), via Narayan.

101. By Nathaniel <catch@catch-toshiba>

Add default argument for node_load_cache_user().

100. By Nathaniel <catch@catch-toshiba>

Clear cache for translations when module is in a translation set.

99. By Nathaniel <catch@catch-toshiba>

Update from launchpad.

98. By Nathaniel <catch@catch-toshiba>

Merge in pressflow trunk.

97. By Nathaniel <catch@catch-toshiba>

Correct function name.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'modules/node/node.module'
--- modules/node/node.module 2011-05-26 02:58:29 +0000
+++ modules/node/node.module 2011-08-10 06:37:33 +0000
@@ -687,6 +687,8 @@
687}687}
688688
689/**689/**
690 * Replacement for node_load() to redirect to an alternative function when available.
691 *
690 * Load a node object from the database.692 * Load a node object from the database.
691 *693 *
692 * @param $param694 * @param $param
@@ -700,6 +702,43 @@
700 * A fully-populated node object.702 * A fully-populated node object.
701 */703 */
702function node_load($param = array(), $revision = NULL, $reset = NULL) {704function node_load($param = array(), $revision = NULL, $reset = NULL) {
705 static $hook_module;
706 $hook = 'node_load_cache';
707
708 if (!isset($hook_module)) {
709 $modules = module_implements($hook);
710 if (!empty($modules)) {
711 $hook_module = reset($modules);
712 }
713 else {
714 $hook_module = FALSE;
715 }
716 }
717
718 if ($hook_module !== FALSE) {
719 $function = $hook_module . '_' . $hook;
720 $return = $function($param, $revision, $reset);
721 }
722 else {
723 $return = _node_load_direct($param, $revision, $reset);
724 }
725 return $return;
726}
727
728/**
729 * Load a node object from the database.
730 *
731 * @param $param
732 * Either the nid of the node or an array of conditions to match against in the database query
733 * @param $revision
734 * Which numbered revision to load. Defaults to the current version.
735 * @param $reset
736 * Whether to reset the internal node_load cache.
737 *
738 * @return
739 * A fully-populated node object.
740 */
741function _node_load_direct($param = array(), $revision = NULL, $reset = NULL) {
703 static $nodes = array();742 static $nodes = array();
704743
705 if ($reset) {744 if ($reset) {
706745
=== added directory 'modules/node_load_cache'
=== added file 'modules/node_load_cache/node_load_cache.info'
--- modules/node_load_cache/node_load_cache.info 1970-01-01 00:00:00 +0000
+++ modules/node_load_cache/node_load_cache.info 2011-08-10 06:37:33 +0000
@@ -0,0 +1,5 @@
1; $Id$
2name = Node load cache
3description = Peristent caching for nodes.
4version = VERSION
5core = 6.x
06
=== added file 'modules/node_load_cache/node_load_cache.install'
--- modules/node_load_cache/node_load_cache.install 1970-01-01 00:00:00 +0000
+++ modules/node_load_cache/node_load_cache.install 2011-08-10 06:37:33 +0000
@@ -0,0 +1,40 @@
1<?php
2// $Id
3
4/**
5 * @file
6 * Install and update functions for node_load_cache module.
7 */
8
9/**
10 * Implements hook_schema().
11 */
12function node_load_cache_schema() {
13 $schema['cache_node'] = drupal_get_schema_unprocessed('system', 'cache');
14 $schema['cache_node']['description'] = 'Cache bin for node objects.';
15
16 return $schema;
17}
18
19/**
20 * Implements hook_install().
21 */
22function node_load_cache_install() {
23 drupal_install_schema('node_load_cache');
24 db_query("UPDATE {system} SET weight = 500 WHERE name = 'node_load_cache'");
25}
26
27/**
28 * Implements hook_uninstall().
29 */
30function node_load_cache_uninstall() {
31 drupal_uninstall_schema('node_load_cache');
32}
33
34/**
35 * Update module to set a heavier weight.
36 */
37function node_load_cache_update_6000() {
38 db_query("UPDATE {system} SET weight = 500 WHERE name = 'node_load_cache'");
39 return array();
40}
041
=== added file 'modules/node_load_cache/node_load_cache.module'
--- modules/node_load_cache/node_load_cache.module 1970-01-01 00:00:00 +0000
+++ modules/node_load_cache/node_load_cache.module 2011-08-10 06:37:33 +0000
@@ -0,0 +1,207 @@
1<?php
2// $Id
3
4/**
5 * @file
6 * Persistent caching for node_load().
7 */
8
9/**
10 * Implementation of hook_node_load_cache().
11 *
12 * Load a node object from the database.
13 *
14 * @param $param
15 * Either the nid of the node or an array of conditions to match against in the database query
16 * @param $revision
17 * Which numbered revision to load. Defaults to the current version.
18 * @param $reset
19 * Whether to reset the internal node_load cache.
20 *
21 * @return
22 * A fully-populated node object.
23 */
24function node_load_cache_node_load_cache($param = array(), $revision = NULL, $reset = NULL) {
25 global $user;
26 static $nodes = array();
27
28 $cachable = ($revision == NULL && is_numeric($param)) ? "node/$param" : 0;
29 if ($reset) {
30 $nodes = array();
31 if ($cacheable) {
32 node_load_cache_clear($param);
33 }
34 }
35
36 $arguments = array();
37 if (is_numeric($param)) {
38 if ($cachable) {
39 // Is the node statically cached?
40 if (isset($nodes[$param])) {
41 return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
42 }
43 $cache = cache_get($cachable, 'cache_node');
44 if (is_object($cache)) {
45 $cache = $cache->data;
46 // Cache the node statically so we don't have to re-request.
47 $nodes[$param] = is_object($cache) ? drupal_clone($cache) : $cache;
48 // Node types like polls need to do some custom logic when users are
49 // logged in and are more difficult to cache. We still get benefit
50 // from caching these node types for anonymous users.
51 if (!(in_array($nodes[$param]->type, variable_get('cache_node_anonymous_types', array('poll'))) && $user->uid)) {
52 return drupal_clone($nodes[$param]);
53 }
54 }
55 }
56 $cond = 'n.nid = %d';
57 $arguments[] = $param;
58 }
59 elseif (is_array($param)) {
60 // Turn the conditions into a query.
61 foreach ($param as $key => $value) {
62 $cond[] = 'n.'. db_escape_table($key) ." = '%s'";
63 $arguments[] = $value;
64 }
65 $cond = implode(' AND ', $cond);
66 }
67 else {
68 return FALSE;
69 }
70
71 // Retrieve a field list based on the site's schema.
72 $fields = drupal_schema_fields_sql('node', 'n');
73 $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions', 'r'));
74 $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
75 // Remove fields not needed in the query: n.vid and r.nid are redundant,
76 // n.title is unnecessary because the node title comes from the
77 // node_revisions table. We'll keep r.vid, r.title, and n.nid.
78 $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
79 $fields = implode(', ', $fields);
80 // Rename timestamp field for clarity.
81 $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp', $fields);
82 // Change name of revision uid so it doesn't conflict with n.uid.
83 $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
84
85 // Retrieve the node.
86 // No db_rewrite_sql is applied so as to get complete indexing for search.
87 if ($revision) {
88 array_unshift($arguments, $revision);
89 $node = db_fetch_object(db_query('SELECT '. $fields .' FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE '. $cond, $arguments));
90 }
91 else {
92 $node = db_fetch_object(db_query('SELECT '. $fields .' FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE '. $cond, $arguments));
93 }
94
95 if ($node && $node->nid) {
96 // Call the node specific callback (if any) and piggy-back the
97 // results to the node or overwrite some values.
98 if ($extra = node_invoke($node, 'load')) {
99 foreach ($extra as $key => $value) {
100 $node->$key = $value;
101 }
102 }
103
104 if ($extra = node_invoke_nodeapi($node, 'load')) {
105 foreach ($extra as $key => $value) {
106 $node->$key = $value;
107 }
108 }
109 if ($cachable) {
110 // To completely exclude a node type from the cache, add it to the
111 // cache_node_exclude_types variable (as an array). To exclude a
112 // node type from the cache only for logged in users, add it to the
113 // cache_node_anyonmous_types variable (as an array).
114 if (!in_array($node->type, variable_get('cache_node_exclude_types', array())) || ($user->uid && !in_array($node->type, variable_get('cache_node_anonymous_types', array('poll'))))) {
115 cache_set($cachable, $node, 'cache_node');
116 }
117 $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
118 }
119 }
120
121 return $node;
122}
123
124/**
125 * Clear the persistent cache for a node.
126 *
127 * @param $nid
128 * The node ID.
129 */
130function node_load_cache_clear($nid) {
131 cache_clear_all("node/$nid", 'cache_node');
132}
133
134/**
135 * Clear nodes that are related to the node being cleared.
136 */
137function node_load_cache_clear_related($node) {
138 // Support for translation module.
139 if (module_exists('translation') && translation_supported_type($node->type) && !empty($node->tnid)) {
140 $translations = translation_node_get_translations($node->tnid);
141 foreach ($translations as $translation) {
142 node_load_cache_clear($translation->nid);
143 }
144 }
145}
146
147/**
148 * Implements hook_nodeapi().
149 */
150function node_load_cache_nodeapi(&$node, $op, $a3, $a4) {
151 if ($op == 'update' || $op == 'delete') {
152 node_load_cache_clear($node->nid);
153 node_load_cache_clear_related($node);
154 }
155 if ($op == 'insert') {
156 node_load_cache_clear_related($node);
157 }
158}
159
160/**
161 * Implements hook_comment().
162 */
163function node_load_cache_comment(&$a1, $op) {
164 // Insert and update ops get passed an array.
165 if ($op == 'insert' || $op == 'update') {
166 node_load_cache_clear($a1['nid']);
167 }
168 // Delete op gets an object.
169 if ($op == 'delete') {
170 node_load_cache_clear($a1->nid);
171 }
172}
173
174/**
175 * Implements hook_user().
176 */
177function node_load_cache_user($op, &$edit, &$account, $category = NULL) {
178 // User module adds name, picture and other values to the node object
179 // in hook_node_load(). Clear the node cache when changes are made.
180 if ($op == 'update' || $op == 'delete') {
181 $result = db_query('SELECT nid FROM {node} WHERE uid = %d', $account->uid);
182 while ($record = db_fetch_object($result)) {
183 node_load_cache_clear($record->nid);
184 }
185 }
186}
187
188/**
189 * Implementation of hook_flush_caches().
190 */
191function node_load_cache_flush_caches() {
192 return array('cache_node');
193}
194
195/**
196 * Implements hook_form_FORM_ID_alter().
197 */
198function node_load_cache_poll_view_voting_form_alter(&$form, &$form_state) {
199 $form['vote']['#submit'][] = '_node_load_cache_poll_vote_submit';
200}
201
202/**
203 * Clear the node_load() cache when a poll vote is submitted.
204 */
205function _node_load_cache_poll_vote_submit($form, &$form_state) {
206 node_load_cache_clear($form['#node']->nid);
207}
0208
=== modified file 'modules/user/user.module'
--- modules/user/user.module 2011-05-26 02:58:29 +0000
+++ modules/user/user.module 2011-08-10 06:37:33 +0000
@@ -127,6 +127,46 @@
127}127}
128128
129/**129/**
130 * Replacement for user_load() to redirect to an alternative implementation when available.
131 *
132 * Fetch a user object.
133 *
134 * @param $array
135 * An associative array of attributes to search for in selecting the
136 * user, such as user name or e-mail address.
137 *
138 * @param $reset
139 * Whether to reset the user_load() cache.
140 *
141 * @return
142 * A fully-loaded $user object upon successful user load or FALSE if user
143 * cannot be loaded.
144 */
145function user_load($array = array(), $reset = FALSE) {
146 static $hook_module;
147 $hook = 'user_load_cache_load';
148
149 if (!isset($hook_module)) {
150 $modules = module_implements('user_load_cache_load');
151 if (!empty($modules)) {
152 $hook_module = reset($modules);
153 }
154 else {
155 $hook_module = FALSE;
156 }
157 }
158
159 if ($hook_module !== FALSE) {
160 $function = $hook_module . '_' . $hook;
161 $return = $function($array, $reset);
162 }
163 else {
164 $return = _user_load_direct($array);
165 }
166 return $return;
167}
168
169/**
130 * Fetch a user object.170 * Fetch a user object.
131 *171 *
132 * @param $user_info172 * @param $user_info
@@ -139,7 +179,7 @@
139 * A fully-loaded $user object upon successful user load or FALSE if user179 * A fully-loaded $user object upon successful user load or FALSE if user
140 * cannot be loaded.180 * cannot be loaded.
141 */181 */
142function user_load($user_info = array()) {182function _user_load_direct($user_info = array()) {
143 // Dynamically compose a SQL query:183 // Dynamically compose a SQL query:
144 $query = array();184 $query = array();
145 $params = array();185 $params = array();
@@ -191,6 +231,8 @@
191}231}
192232
193/**233/**
234 * Replacement for user_save that redirects to an alternative implementation if available.
235 *
194 * Save changes to a user account or add a new user.236 * Save changes to a user account or add a new user.
195 *237 *
196 * @param $account238 * @param $account
@@ -209,6 +251,49 @@
209 * A fully-loaded $user object upon successful save or FALSE if the save failed.251 * A fully-loaded $user object upon successful save or FALSE if the save failed.
210 */252 */
211function user_save($account, $array = array(), $category = 'account') {253function user_save($account, $array = array(), $category = 'account') {
254 static $hook_module;
255 $hook = 'user_load_cache_save';
256
257 if (!isset($hook_module)) {
258 $modules = module_implements($hook);
259 if (!empty($modules)) {
260 $hook_module = reset($modules);
261 }
262 else {
263 $hook_module = FALSE;
264 }
265 }
266
267 if ($hook_module !== FALSE) {
268 $function = $hook_module . '_' . $hook;
269 $return = $function($account, $array, $category);
270 }
271 else {
272 $return = _user_save_direct($account, $array, $category);
273 }
274 return $return;
275}
276
277
278/**
279 * Save changes to a user account or add a new user.
280 *
281 * @param $account
282 * The $user object for the user to modify or add. If $user->uid is
283 * omitted, a new user will be added.
284 *
285 * @param $array
286 * (optional) An array of fields and values to save. For example,
287 * array('name' => 'My name'); Setting a field to NULL deletes it from
288 * the data column.
289 *
290 * @param $category
291 * (optional) The category for storing profile information in.
292 *
293 * @return
294 * A fully-loaded $user object upon successful save or FALSE if the save failed.
295 */
296function _user_save_direct($account, $array = array(), $category = 'account') {
212 // Dynamically compose a SQL query:297 // Dynamically compose a SQL query:
213 $user_fields = user_fields();298 $user_fields = user_fields();
214 if (is_object($account) && $account->uid) {299 if (is_object($account) && $account->uid) {
215300
=== added directory 'modules/user_load_cache'
=== added file 'modules/user_load_cache/user_load_cache.info'
--- modules/user_load_cache/user_load_cache.info 1970-01-01 00:00:00 +0000
+++ modules/user_load_cache/user_load_cache.info 2011-08-10 06:37:33 +0000
@@ -0,0 +1,5 @@
1; $Id$
2name = User load caching
3description = Static and persistent caching for users.
4version = VERSION
5core = 6.x
06
=== added file 'modules/user_load_cache/user_load_cache.install'
--- modules/user_load_cache/user_load_cache.install 1970-01-01 00:00:00 +0000
+++ modules/user_load_cache/user_load_cache.install 2011-08-10 06:37:33 +0000
@@ -0,0 +1,31 @@
1<?php
2// $Id
3
4/**
5 * @file
6 * Install and update functions for user_load_cache module.
7 */
8
9/**
10 * Implements hook_schema().
11 */
12function user_load_cache_schema() {
13 $schema['cache_user'] = drupal_get_schema_unprocessed('system', 'cache');
14 $schema['cache_user']['description'] = 'Cache bin for user objects.';
15
16 return $schema;
17}
18
19/**
20 * Implements hook_install().
21 */
22function user_load_cache_install() {
23 drupal_install_schema('user_load_cache');
24}
25
26/**
27 * Implements hook_uninstall().
28 */
29function user_load_cache_uninstall() {
30 drupal_uninstall_schema('user_load_cache');
31}
032
=== added file 'modules/user_load_cache/user_load_cache.module'
--- modules/user_load_cache/user_load_cache.module 1970-01-01 00:00:00 +0000
+++ modules/user_load_cache/user_load_cache.module 2011-08-10 06:37:33 +0000
@@ -0,0 +1,326 @@
1<?php
2// $Id
3
4/**
5 * @file
6 * Add static and persistent caching to user_load().
7 */
8
9/**
10 * Implementation of hook_user_load_cache_load().
11 *
12 * Fetch a user object.
13 *
14 * @param $array
15 * An associative array of attributes to search for in selecting the
16 * user, such as user name or e-mail address.
17 * @param $reset
18 * (optional) Resets the static user cache, necessary when the user account
19 * has been saved.
20 *
21 * @return
22 * A fully-loaded $user object upon successful user load or FALSE if user
23 * cannot be loaded.
24 */
25function user_load_cache_user_load_cache_load($array = array(), $reset = FALSE) {
26 static $user_cache = array();
27 // Dynamically compose a SQL query:
28 $query = array();
29 $params = array();
30
31 if (is_numeric($array)) {
32 $array = array('uid' => $array);
33 }
34 elseif (!is_array($array)) {
35 return FALSE;
36 }
37
38 // Retrieve user from cache
39 if (count($array) === 1 && isset($array['uid']) && $array['uid']) {
40 if (!$reset && isset($user_cache[$array['uid']])) {
41 return $user_cache[$array['uid']];
42 }
43 $cid = 'user/' . $array['uid'];
44 $cache = cache_get($cid, 'cache_user');
45 if (is_object($cache)) {
46 $user_cache[$array['uid']] = $cache->data;
47 return $cache->data;
48 }
49 }
50 else {
51 $cid = 0;
52 }
53
54 foreach ($array as $key => $value) {
55 if ($key == 'uid' || $key == 'status') {
56 $query[] = "$key = %d";
57 $params[] = $value;
58 }
59 else if ($key == 'pass') {
60 $query[] = "pass = '%s'";
61 $params[] = md5($value);
62 }
63 else {
64 $query[]= "$key = '%s'";
65 $params[] = $value;
66 }
67 }
68 $result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);
69
70 if ($user = db_fetch_object($result)) {
71 $user = drupal_unpack($user);
72
73 $user->roles = array();
74 if ($user->uid) {
75 $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
76 }
77 else {
78 $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
79 }
80 $result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
81 while ($role = db_fetch_object($result)) {
82 $user->roles[$role->rid] = $role->name;
83 }
84 user_module_invoke('load', $array, $user);
85 }
86 else {
87 $user = FALSE;
88 }
89
90 if ($user->uid && $cid) {
91 // Add the user to the static cache.
92 $user_cache[$array['uid']] = $user;
93
94 // Add the user to the persistent cache.
95 cache_set($cid, $user, 'cache_user');
96 }
97
98 return $user;
99}
100
101/**
102 * Implementation of hook_user_load_cache_save().
103 */
104function user_load_cache_user_load_cache_save($account, $array = array(), $category = 'account') {
105 // Dynamically compose a SQL query:
106 $user_fields = user_fields();
107 if (is_object($account) && $account->uid) {
108 $cid = "user/$account->uid";
109 user_module_invoke('update', $array, $account, $category);
110 $query = '';
111 $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
112 // Consider users edited by an administrator as logged in, if they haven't
113 // already, so anonymous users can view the profile (if allowed).
114 if (empty($array['access']) && empty($account->access) && user_access('administer users')) {
115 $array['access'] = time();
116 }
117 foreach ($array as $key => $value) {
118 if ($key == 'pass' && !empty($value)) {
119 $query .= "$key = '%s', ";
120 $v[] = md5($value);
121 }
122 else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
123 if (in_array($key, $user_fields)) {
124 // Save standard fields.
125 $query .= "$key = '%s', ";
126 $v[] = $value;
127 }
128 else if ($key != 'roles') {
129 // Roles is a special case: it used below.
130 if ($value === NULL) {
131 unset($data[$key]);
132 }
133 elseif (!empty($key)) {
134 $data[$key] = $value;
135 }
136 }
137 }
138 }
139 $query .= "data = '%s' ";
140 $v[] = serialize($data);
141
142 $success = db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));
143 if (!$success) {
144 // The query failed - better to abort the save than risk further data loss.
145 return FALSE;
146 }
147
148 // Reload user roles if provided.
149 if (isset($array['roles']) && is_array($array['roles'])) {
150 db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
151
152 foreach (array_keys($array['roles']) as $rid) {
153 if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
154 db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
155 }
156 }
157 }
158
159 // Delete a blocked user's sessions to kick them if they are online.
160 if (isset($array['status']) && $array['status'] == 0) {
161 sess_destroy_uid($account->uid);
162 }
163
164 // If the password changed, delete all open sessions and recreate
165 // the current one.
166 if (!empty($array['pass'])) {
167 sess_destroy_uid($account->uid);
168 if ($account->uid == $GLOBALS['user']->uid) {
169 sess_regenerate();
170 }
171 }
172
173 // Flush user from cache.
174 cache_clear_all($cid, 'cache_user');
175
176 // Refresh user object.
177 $user = user_load(array('uid' => $account->uid), TRUE);
178
179 // Send emails after we have the new user object.
180 if (isset($array['status']) && $array['status'] != $account->status) {
181 // The user's status is changing; conditionally send notification email.
182 $op = $array['status'] == 1 ? 'status_activated' : 'status_blocked';
183 _user_mail_notify($op, $user);
184 }
185
186 user_module_invoke('after_update', $array, $user, $category);
187 }
188 else {
189 // Allow 'created' to be set by the caller.
190 if (!isset($array['created'])) {
191 $array['created'] = time();
192 }
193 // Consider users created by an administrator as already logged in, so
194 // anonymous users can view the profile (if allowed).
195 if (empty($array['access']) && user_access('administer users')) {
196 $array['access'] = time();
197 }
198
199 // Note: we wait to save the data column to prevent module-handled
200 // fields from being saved there. We cannot invoke hook_user('insert') here
201 // because we don't have a fully initialized user object yet.
202 foreach ($array as $key => $value) {
203 switch ($key) {
204 case 'pass':
205 $fields[] = $key;
206 $values[] = md5($value);
207 $s[] = "'%s'";
208 break;
209 case 'mode': case 'sort': case 'timezone':
210 case 'threshold': case 'created': case 'access':
211 case 'login': case 'status':
212 $fields[] = $key;
213 $values[] = $value;
214 $s[] = "%d";
215 break;
216 default:
217 if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
218 $fields[] = $key;
219 $values[] = $value;
220 $s[] = "'%s'";
221 }
222 break;
223 }
224 }
225 $success = db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
226 if (!$success) {
227 // On a failed INSERT some other existing user's uid may be returned.
228 // We must abort to avoid overwriting their account.
229 return FALSE;
230 }
231
232 // Build the initial user object.
233 $array['uid'] = db_last_insert_id('users', 'uid');
234 $user = user_load(array('uid' => $array['uid']), TRUE);
235
236 $cid = 'user/' . $array['uid'];
237 user_module_invoke('insert', $array, $user, $category);
238
239 // Build and save the serialized data field now.
240 $data = array();
241 foreach ($array as $key => $value) {
242 if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
243 $data[$key] = $value;
244 }
245 }
246 db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
247
248 // Save user roles (delete just to be safe).
249 if (isset($array['roles']) && is_array($array['roles'])) {
250 db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
251 foreach (array_keys($array['roles']) as $rid) {
252 if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
253 db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
254 }
255 }
256 }
257
258 // Flush user from cache.
259 cache_clear_all($cid, 'cache_user');
260
261 // Build the finished user object.
262 $user = user_load(array('uid' => $array['uid']), TRUE);
263 }
264
265 // Save distributed authentication mappings.
266 $authmaps = array();
267 foreach ($array as $key => $value) {
268 if (substr($key, 0, 4) == 'auth') {
269 $authmaps[$key] = $value;
270 }
271 }
272 if (sizeof($authmaps) > 0) {
273 user_set_authmaps($user, $authmaps);
274 }
275 cache_clear_all($cid, 'cache_user');
276
277 return $user;
278}
279
280
281/**
282 * Clear the user_load() cache.
283 *
284 * $param $uid
285 * The user ID.
286 */
287function user_load_cache_clear($uid) {
288 cache_clear_all("user/$uid", 'cache_user');
289 // memcache-session.inc caches the user object too, ensure that gets cleared.
290 cache_clear_all($uid, 'users');
291}
292
293/**
294 * Implementation of hook_user().
295 */
296function user_load_cache_user($op, &$edit, &$account, $category = NULL) {
297 if ($op == 'update' || $op == 'delete' || $op == 'login') {
298 user_load_cache_clear($account->uid);
299 }
300}
301
302/**
303 * Implementation of hook_flush_caches().
304 */
305function user_load_cache_flush_caches() {
306 return array('cache_user');
307}
308
309/**
310 * Implementation of hook_form_FORM_ID_alter().
311 *
312 * @todo: is this necessary?
313 */
314function user_load_cache_form_user_profile_form_alter(&$form, &$form_state) {
315 $form['#submit'][] = '_user_load_cache_profile_form_submit';
316}
317
318/**
319 * Submit handler for the user profile form.
320 */
321function _user_load_cache_profile_form_submit($form, &$form_state) {
322 if (user_access('select different theme')) {
323 // This prevents the system theme from getting corrupted.
324 init_theme();
325 }
326}

Subscribers

People subscribed via source and target branches

to status/vote changes: