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
1=== modified file 'modules/node/node.module'
2--- modules/node/node.module 2011-05-26 02:58:29 +0000
3+++ modules/node/node.module 2011-08-10 06:37:33 +0000
4@@ -687,6 +687,8 @@
5 }
6
7 /**
8+ * Replacement for node_load() to redirect to an alternative function when available.
9+ *
10 * Load a node object from the database.
11 *
12 * @param $param
13@@ -700,6 +702,43 @@
14 * A fully-populated node object.
15 */
16 function node_load($param = array(), $revision = NULL, $reset = NULL) {
17+ static $hook_module;
18+ $hook = 'node_load_cache';
19+
20+ if (!isset($hook_module)) {
21+ $modules = module_implements($hook);
22+ if (!empty($modules)) {
23+ $hook_module = reset($modules);
24+ }
25+ else {
26+ $hook_module = FALSE;
27+ }
28+ }
29+
30+ if ($hook_module !== FALSE) {
31+ $function = $hook_module . '_' . $hook;
32+ $return = $function($param, $revision, $reset);
33+ }
34+ else {
35+ $return = _node_load_direct($param, $revision, $reset);
36+ }
37+ return $return;
38+}
39+
40+/**
41+ * Load a node object from the database.
42+ *
43+ * @param $param
44+ * Either the nid of the node or an array of conditions to match against in the database query
45+ * @param $revision
46+ * Which numbered revision to load. Defaults to the current version.
47+ * @param $reset
48+ * Whether to reset the internal node_load cache.
49+ *
50+ * @return
51+ * A fully-populated node object.
52+ */
53+function _node_load_direct($param = array(), $revision = NULL, $reset = NULL) {
54 static $nodes = array();
55
56 if ($reset) {
57
58=== added directory 'modules/node_load_cache'
59=== added file 'modules/node_load_cache/node_load_cache.info'
60--- modules/node_load_cache/node_load_cache.info 1970-01-01 00:00:00 +0000
61+++ modules/node_load_cache/node_load_cache.info 2011-08-10 06:37:33 +0000
62@@ -0,0 +1,5 @@
63+; $Id$
64+name = Node load cache
65+description = Peristent caching for nodes.
66+version = VERSION
67+core = 6.x
68
69=== added file 'modules/node_load_cache/node_load_cache.install'
70--- modules/node_load_cache/node_load_cache.install 1970-01-01 00:00:00 +0000
71+++ modules/node_load_cache/node_load_cache.install 2011-08-10 06:37:33 +0000
72@@ -0,0 +1,40 @@
73+<?php
74+// $Id
75+
76+/**
77+ * @file
78+ * Install and update functions for node_load_cache module.
79+ */
80+
81+/**
82+ * Implements hook_schema().
83+ */
84+function node_load_cache_schema() {
85+ $schema['cache_node'] = drupal_get_schema_unprocessed('system', 'cache');
86+ $schema['cache_node']['description'] = 'Cache bin for node objects.';
87+
88+ return $schema;
89+}
90+
91+/**
92+ * Implements hook_install().
93+ */
94+function node_load_cache_install() {
95+ drupal_install_schema('node_load_cache');
96+ db_query("UPDATE {system} SET weight = 500 WHERE name = 'node_load_cache'");
97+}
98+
99+/**
100+ * Implements hook_uninstall().
101+ */
102+function node_load_cache_uninstall() {
103+ drupal_uninstall_schema('node_load_cache');
104+}
105+
106+/**
107+ * Update module to set a heavier weight.
108+ */
109+function node_load_cache_update_6000() {
110+ db_query("UPDATE {system} SET weight = 500 WHERE name = 'node_load_cache'");
111+ return array();
112+}
113
114=== added file 'modules/node_load_cache/node_load_cache.module'
115--- modules/node_load_cache/node_load_cache.module 1970-01-01 00:00:00 +0000
116+++ modules/node_load_cache/node_load_cache.module 2011-08-10 06:37:33 +0000
117@@ -0,0 +1,207 @@
118+<?php
119+// $Id
120+
121+/**
122+ * @file
123+ * Persistent caching for node_load().
124+ */
125+
126+/**
127+ * Implementation of hook_node_load_cache().
128+ *
129+ * Load a node object from the database.
130+ *
131+ * @param $param
132+ * Either the nid of the node or an array of conditions to match against in the database query
133+ * @param $revision
134+ * Which numbered revision to load. Defaults to the current version.
135+ * @param $reset
136+ * Whether to reset the internal node_load cache.
137+ *
138+ * @return
139+ * A fully-populated node object.
140+ */
141+function node_load_cache_node_load_cache($param = array(), $revision = NULL, $reset = NULL) {
142+ global $user;
143+ static $nodes = array();
144+
145+ $cachable = ($revision == NULL && is_numeric($param)) ? "node/$param" : 0;
146+ if ($reset) {
147+ $nodes = array();
148+ if ($cacheable) {
149+ node_load_cache_clear($param);
150+ }
151+ }
152+
153+ $arguments = array();
154+ if (is_numeric($param)) {
155+ if ($cachable) {
156+ // Is the node statically cached?
157+ if (isset($nodes[$param])) {
158+ return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
159+ }
160+ $cache = cache_get($cachable, 'cache_node');
161+ if (is_object($cache)) {
162+ $cache = $cache->data;
163+ // Cache the node statically so we don't have to re-request.
164+ $nodes[$param] = is_object($cache) ? drupal_clone($cache) : $cache;
165+ // Node types like polls need to do some custom logic when users are
166+ // logged in and are more difficult to cache. We still get benefit
167+ // from caching these node types for anonymous users.
168+ if (!(in_array($nodes[$param]->type, variable_get('cache_node_anonymous_types', array('poll'))) && $user->uid)) {
169+ return drupal_clone($nodes[$param]);
170+ }
171+ }
172+ }
173+ $cond = 'n.nid = %d';
174+ $arguments[] = $param;
175+ }
176+ elseif (is_array($param)) {
177+ // Turn the conditions into a query.
178+ foreach ($param as $key => $value) {
179+ $cond[] = 'n.'. db_escape_table($key) ." = '%s'";
180+ $arguments[] = $value;
181+ }
182+ $cond = implode(' AND ', $cond);
183+ }
184+ else {
185+ return FALSE;
186+ }
187+
188+ // Retrieve a field list based on the site's schema.
189+ $fields = drupal_schema_fields_sql('node', 'n');
190+ $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions', 'r'));
191+ $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
192+ // Remove fields not needed in the query: n.vid and r.nid are redundant,
193+ // n.title is unnecessary because the node title comes from the
194+ // node_revisions table. We'll keep r.vid, r.title, and n.nid.
195+ $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
196+ $fields = implode(', ', $fields);
197+ // Rename timestamp field for clarity.
198+ $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp', $fields);
199+ // Change name of revision uid so it doesn't conflict with n.uid.
200+ $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
201+
202+ // Retrieve the node.
203+ // No db_rewrite_sql is applied so as to get complete indexing for search.
204+ if ($revision) {
205+ array_unshift($arguments, $revision);
206+ $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));
207+ }
208+ else {
209+ $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));
210+ }
211+
212+ if ($node && $node->nid) {
213+ // Call the node specific callback (if any) and piggy-back the
214+ // results to the node or overwrite some values.
215+ if ($extra = node_invoke($node, 'load')) {
216+ foreach ($extra as $key => $value) {
217+ $node->$key = $value;
218+ }
219+ }
220+
221+ if ($extra = node_invoke_nodeapi($node, 'load')) {
222+ foreach ($extra as $key => $value) {
223+ $node->$key = $value;
224+ }
225+ }
226+ if ($cachable) {
227+ // To completely exclude a node type from the cache, add it to the
228+ // cache_node_exclude_types variable (as an array). To exclude a
229+ // node type from the cache only for logged in users, add it to the
230+ // cache_node_anyonmous_types variable (as an array).
231+ 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'))))) {
232+ cache_set($cachable, $node, 'cache_node');
233+ }
234+ $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
235+ }
236+ }
237+
238+ return $node;
239+}
240+
241+/**
242+ * Clear the persistent cache for a node.
243+ *
244+ * @param $nid
245+ * The node ID.
246+ */
247+function node_load_cache_clear($nid) {
248+ cache_clear_all("node/$nid", 'cache_node');
249+}
250+
251+/**
252+ * Clear nodes that are related to the node being cleared.
253+ */
254+function node_load_cache_clear_related($node) {
255+ // Support for translation module.
256+ if (module_exists('translation') && translation_supported_type($node->type) && !empty($node->tnid)) {
257+ $translations = translation_node_get_translations($node->tnid);
258+ foreach ($translations as $translation) {
259+ node_load_cache_clear($translation->nid);
260+ }
261+ }
262+}
263+
264+/**
265+ * Implements hook_nodeapi().
266+ */
267+function node_load_cache_nodeapi(&$node, $op, $a3, $a4) {
268+ if ($op == 'update' || $op == 'delete') {
269+ node_load_cache_clear($node->nid);
270+ node_load_cache_clear_related($node);
271+ }
272+ if ($op == 'insert') {
273+ node_load_cache_clear_related($node);
274+ }
275+}
276+
277+/**
278+ * Implements hook_comment().
279+ */
280+function node_load_cache_comment(&$a1, $op) {
281+ // Insert and update ops get passed an array.
282+ if ($op == 'insert' || $op == 'update') {
283+ node_load_cache_clear($a1['nid']);
284+ }
285+ // Delete op gets an object.
286+ if ($op == 'delete') {
287+ node_load_cache_clear($a1->nid);
288+ }
289+}
290+
291+/**
292+ * Implements hook_user().
293+ */
294+function node_load_cache_user($op, &$edit, &$account, $category = NULL) {
295+ // User module adds name, picture and other values to the node object
296+ // in hook_node_load(). Clear the node cache when changes are made.
297+ if ($op == 'update' || $op == 'delete') {
298+ $result = db_query('SELECT nid FROM {node} WHERE uid = %d', $account->uid);
299+ while ($record = db_fetch_object($result)) {
300+ node_load_cache_clear($record->nid);
301+ }
302+ }
303+}
304+
305+/**
306+ * Implementation of hook_flush_caches().
307+ */
308+function node_load_cache_flush_caches() {
309+ return array('cache_node');
310+}
311+
312+/**
313+ * Implements hook_form_FORM_ID_alter().
314+ */
315+function node_load_cache_poll_view_voting_form_alter(&$form, &$form_state) {
316+ $form['vote']['#submit'][] = '_node_load_cache_poll_vote_submit';
317+}
318+
319+/**
320+ * Clear the node_load() cache when a poll vote is submitted.
321+ */
322+function _node_load_cache_poll_vote_submit($form, &$form_state) {
323+ node_load_cache_clear($form['#node']->nid);
324+}
325
326=== modified file 'modules/user/user.module'
327--- modules/user/user.module 2011-05-26 02:58:29 +0000
328+++ modules/user/user.module 2011-08-10 06:37:33 +0000
329@@ -127,6 +127,46 @@
330 }
331
332 /**
333+ * Replacement for user_load() to redirect to an alternative implementation when available.
334+ *
335+ * Fetch a user object.
336+ *
337+ * @param $array
338+ * An associative array of attributes to search for in selecting the
339+ * user, such as user name or e-mail address.
340+ *
341+ * @param $reset
342+ * Whether to reset the user_load() cache.
343+ *
344+ * @return
345+ * A fully-loaded $user object upon successful user load or FALSE if user
346+ * cannot be loaded.
347+ */
348+function user_load($array = array(), $reset = FALSE) {
349+ static $hook_module;
350+ $hook = 'user_load_cache_load';
351+
352+ if (!isset($hook_module)) {
353+ $modules = module_implements('user_load_cache_load');
354+ if (!empty($modules)) {
355+ $hook_module = reset($modules);
356+ }
357+ else {
358+ $hook_module = FALSE;
359+ }
360+ }
361+
362+ if ($hook_module !== FALSE) {
363+ $function = $hook_module . '_' . $hook;
364+ $return = $function($array, $reset);
365+ }
366+ else {
367+ $return = _user_load_direct($array);
368+ }
369+ return $return;
370+}
371+
372+/**
373 * Fetch a user object.
374 *
375 * @param $user_info
376@@ -139,7 +179,7 @@
377 * A fully-loaded $user object upon successful user load or FALSE if user
378 * cannot be loaded.
379 */
380-function user_load($user_info = array()) {
381+function _user_load_direct($user_info = array()) {
382 // Dynamically compose a SQL query:
383 $query = array();
384 $params = array();
385@@ -191,6 +231,8 @@
386 }
387
388 /**
389+ * Replacement for user_save that redirects to an alternative implementation if available.
390+ *
391 * Save changes to a user account or add a new user.
392 *
393 * @param $account
394@@ -209,6 +251,49 @@
395 * A fully-loaded $user object upon successful save or FALSE if the save failed.
396 */
397 function user_save($account, $array = array(), $category = 'account') {
398+ static $hook_module;
399+ $hook = 'user_load_cache_save';
400+
401+ if (!isset($hook_module)) {
402+ $modules = module_implements($hook);
403+ if (!empty($modules)) {
404+ $hook_module = reset($modules);
405+ }
406+ else {
407+ $hook_module = FALSE;
408+ }
409+ }
410+
411+ if ($hook_module !== FALSE) {
412+ $function = $hook_module . '_' . $hook;
413+ $return = $function($account, $array, $category);
414+ }
415+ else {
416+ $return = _user_save_direct($account, $array, $category);
417+ }
418+ return $return;
419+}
420+
421+
422+/**
423+ * Save changes to a user account or add a new user.
424+ *
425+ * @param $account
426+ * The $user object for the user to modify or add. If $user->uid is
427+ * omitted, a new user will be added.
428+ *
429+ * @param $array
430+ * (optional) An array of fields and values to save. For example,
431+ * array('name' => 'My name'); Setting a field to NULL deletes it from
432+ * the data column.
433+ *
434+ * @param $category
435+ * (optional) The category for storing profile information in.
436+ *
437+ * @return
438+ * A fully-loaded $user object upon successful save or FALSE if the save failed.
439+ */
440+function _user_save_direct($account, $array = array(), $category = 'account') {
441 // Dynamically compose a SQL query:
442 $user_fields = user_fields();
443 if (is_object($account) && $account->uid) {
444
445=== added directory 'modules/user_load_cache'
446=== added file 'modules/user_load_cache/user_load_cache.info'
447--- modules/user_load_cache/user_load_cache.info 1970-01-01 00:00:00 +0000
448+++ modules/user_load_cache/user_load_cache.info 2011-08-10 06:37:33 +0000
449@@ -0,0 +1,5 @@
450+; $Id$
451+name = User load caching
452+description = Static and persistent caching for users.
453+version = VERSION
454+core = 6.x
455
456=== added file 'modules/user_load_cache/user_load_cache.install'
457--- modules/user_load_cache/user_load_cache.install 1970-01-01 00:00:00 +0000
458+++ modules/user_load_cache/user_load_cache.install 2011-08-10 06:37:33 +0000
459@@ -0,0 +1,31 @@
460+<?php
461+// $Id
462+
463+/**
464+ * @file
465+ * Install and update functions for user_load_cache module.
466+ */
467+
468+/**
469+ * Implements hook_schema().
470+ */
471+function user_load_cache_schema() {
472+ $schema['cache_user'] = drupal_get_schema_unprocessed('system', 'cache');
473+ $schema['cache_user']['description'] = 'Cache bin for user objects.';
474+
475+ return $schema;
476+}
477+
478+/**
479+ * Implements hook_install().
480+ */
481+function user_load_cache_install() {
482+ drupal_install_schema('user_load_cache');
483+}
484+
485+/**
486+ * Implements hook_uninstall().
487+ */
488+function user_load_cache_uninstall() {
489+ drupal_uninstall_schema('user_load_cache');
490+}
491
492=== added file 'modules/user_load_cache/user_load_cache.module'
493--- modules/user_load_cache/user_load_cache.module 1970-01-01 00:00:00 +0000
494+++ modules/user_load_cache/user_load_cache.module 2011-08-10 06:37:33 +0000
495@@ -0,0 +1,326 @@
496+<?php
497+// $Id
498+
499+/**
500+ * @file
501+ * Add static and persistent caching to user_load().
502+ */
503+
504+/**
505+ * Implementation of hook_user_load_cache_load().
506+ *
507+ * Fetch a user object.
508+ *
509+ * @param $array
510+ * An associative array of attributes to search for in selecting the
511+ * user, such as user name or e-mail address.
512+ * @param $reset
513+ * (optional) Resets the static user cache, necessary when the user account
514+ * has been saved.
515+ *
516+ * @return
517+ * A fully-loaded $user object upon successful user load or FALSE if user
518+ * cannot be loaded.
519+ */
520+function user_load_cache_user_load_cache_load($array = array(), $reset = FALSE) {
521+ static $user_cache = array();
522+ // Dynamically compose a SQL query:
523+ $query = array();
524+ $params = array();
525+
526+ if (is_numeric($array)) {
527+ $array = array('uid' => $array);
528+ }
529+ elseif (!is_array($array)) {
530+ return FALSE;
531+ }
532+
533+ // Retrieve user from cache
534+ if (count($array) === 1 && isset($array['uid']) && $array['uid']) {
535+ if (!$reset && isset($user_cache[$array['uid']])) {
536+ return $user_cache[$array['uid']];
537+ }
538+ $cid = 'user/' . $array['uid'];
539+ $cache = cache_get($cid, 'cache_user');
540+ if (is_object($cache)) {
541+ $user_cache[$array['uid']] = $cache->data;
542+ return $cache->data;
543+ }
544+ }
545+ else {
546+ $cid = 0;
547+ }
548+
549+ foreach ($array as $key => $value) {
550+ if ($key == 'uid' || $key == 'status') {
551+ $query[] = "$key = %d";
552+ $params[] = $value;
553+ }
554+ else if ($key == 'pass') {
555+ $query[] = "pass = '%s'";
556+ $params[] = md5($value);
557+ }
558+ else {
559+ $query[]= "$key = '%s'";
560+ $params[] = $value;
561+ }
562+ }
563+ $result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);
564+
565+ if ($user = db_fetch_object($result)) {
566+ $user = drupal_unpack($user);
567+
568+ $user->roles = array();
569+ if ($user->uid) {
570+ $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
571+ }
572+ else {
573+ $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
574+ }
575+ $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);
576+ while ($role = db_fetch_object($result)) {
577+ $user->roles[$role->rid] = $role->name;
578+ }
579+ user_module_invoke('load', $array, $user);
580+ }
581+ else {
582+ $user = FALSE;
583+ }
584+
585+ if ($user->uid && $cid) {
586+ // Add the user to the static cache.
587+ $user_cache[$array['uid']] = $user;
588+
589+ // Add the user to the persistent cache.
590+ cache_set($cid, $user, 'cache_user');
591+ }
592+
593+ return $user;
594+}
595+
596+/**
597+ * Implementation of hook_user_load_cache_save().
598+ */
599+function user_load_cache_user_load_cache_save($account, $array = array(), $category = 'account') {
600+ // Dynamically compose a SQL query:
601+ $user_fields = user_fields();
602+ if (is_object($account) && $account->uid) {
603+ $cid = "user/$account->uid";
604+ user_module_invoke('update', $array, $account, $category);
605+ $query = '';
606+ $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
607+ // Consider users edited by an administrator as logged in, if they haven't
608+ // already, so anonymous users can view the profile (if allowed).
609+ if (empty($array['access']) && empty($account->access) && user_access('administer users')) {
610+ $array['access'] = time();
611+ }
612+ foreach ($array as $key => $value) {
613+ if ($key == 'pass' && !empty($value)) {
614+ $query .= "$key = '%s', ";
615+ $v[] = md5($value);
616+ }
617+ else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
618+ if (in_array($key, $user_fields)) {
619+ // Save standard fields.
620+ $query .= "$key = '%s', ";
621+ $v[] = $value;
622+ }
623+ else if ($key != 'roles') {
624+ // Roles is a special case: it used below.
625+ if ($value === NULL) {
626+ unset($data[$key]);
627+ }
628+ elseif (!empty($key)) {
629+ $data[$key] = $value;
630+ }
631+ }
632+ }
633+ }
634+ $query .= "data = '%s' ";
635+ $v[] = serialize($data);
636+
637+ $success = db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));
638+ if (!$success) {
639+ // The query failed - better to abort the save than risk further data loss.
640+ return FALSE;
641+ }
642+
643+ // Reload user roles if provided.
644+ if (isset($array['roles']) && is_array($array['roles'])) {
645+ db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
646+
647+ foreach (array_keys($array['roles']) as $rid) {
648+ if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
649+ db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
650+ }
651+ }
652+ }
653+
654+ // Delete a blocked user's sessions to kick them if they are online.
655+ if (isset($array['status']) && $array['status'] == 0) {
656+ sess_destroy_uid($account->uid);
657+ }
658+
659+ // If the password changed, delete all open sessions and recreate
660+ // the current one.
661+ if (!empty($array['pass'])) {
662+ sess_destroy_uid($account->uid);
663+ if ($account->uid == $GLOBALS['user']->uid) {
664+ sess_regenerate();
665+ }
666+ }
667+
668+ // Flush user from cache.
669+ cache_clear_all($cid, 'cache_user');
670+
671+ // Refresh user object.
672+ $user = user_load(array('uid' => $account->uid), TRUE);
673+
674+ // Send emails after we have the new user object.
675+ if (isset($array['status']) && $array['status'] != $account->status) {
676+ // The user's status is changing; conditionally send notification email.
677+ $op = $array['status'] == 1 ? 'status_activated' : 'status_blocked';
678+ _user_mail_notify($op, $user);
679+ }
680+
681+ user_module_invoke('after_update', $array, $user, $category);
682+ }
683+ else {
684+ // Allow 'created' to be set by the caller.
685+ if (!isset($array['created'])) {
686+ $array['created'] = time();
687+ }
688+ // Consider users created by an administrator as already logged in, so
689+ // anonymous users can view the profile (if allowed).
690+ if (empty($array['access']) && user_access('administer users')) {
691+ $array['access'] = time();
692+ }
693+
694+ // Note: we wait to save the data column to prevent module-handled
695+ // fields from being saved there. We cannot invoke hook_user('insert') here
696+ // because we don't have a fully initialized user object yet.
697+ foreach ($array as $key => $value) {
698+ switch ($key) {
699+ case 'pass':
700+ $fields[] = $key;
701+ $values[] = md5($value);
702+ $s[] = "'%s'";
703+ break;
704+ case 'mode': case 'sort': case 'timezone':
705+ case 'threshold': case 'created': case 'access':
706+ case 'login': case 'status':
707+ $fields[] = $key;
708+ $values[] = $value;
709+ $s[] = "%d";
710+ break;
711+ default:
712+ if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
713+ $fields[] = $key;
714+ $values[] = $value;
715+ $s[] = "'%s'";
716+ }
717+ break;
718+ }
719+ }
720+ $success = db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
721+ if (!$success) {
722+ // On a failed INSERT some other existing user's uid may be returned.
723+ // We must abort to avoid overwriting their account.
724+ return FALSE;
725+ }
726+
727+ // Build the initial user object.
728+ $array['uid'] = db_last_insert_id('users', 'uid');
729+ $user = user_load(array('uid' => $array['uid']), TRUE);
730+
731+ $cid = 'user/' . $array['uid'];
732+ user_module_invoke('insert', $array, $user, $category);
733+
734+ // Build and save the serialized data field now.
735+ $data = array();
736+ foreach ($array as $key => $value) {
737+ if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
738+ $data[$key] = $value;
739+ }
740+ }
741+ db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
742+
743+ // Save user roles (delete just to be safe).
744+ if (isset($array['roles']) && is_array($array['roles'])) {
745+ db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
746+ foreach (array_keys($array['roles']) as $rid) {
747+ if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
748+ db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
749+ }
750+ }
751+ }
752+
753+ // Flush user from cache.
754+ cache_clear_all($cid, 'cache_user');
755+
756+ // Build the finished user object.
757+ $user = user_load(array('uid' => $array['uid']), TRUE);
758+ }
759+
760+ // Save distributed authentication mappings.
761+ $authmaps = array();
762+ foreach ($array as $key => $value) {
763+ if (substr($key, 0, 4) == 'auth') {
764+ $authmaps[$key] = $value;
765+ }
766+ }
767+ if (sizeof($authmaps) > 0) {
768+ user_set_authmaps($user, $authmaps);
769+ }
770+ cache_clear_all($cid, 'cache_user');
771+
772+ return $user;
773+}
774+
775+
776+/**
777+ * Clear the user_load() cache.
778+ *
779+ * $param $uid
780+ * The user ID.
781+ */
782+function user_load_cache_clear($uid) {
783+ cache_clear_all("user/$uid", 'cache_user');
784+ // memcache-session.inc caches the user object too, ensure that gets cleared.
785+ cache_clear_all($uid, 'users');
786+}
787+
788+/**
789+ * Implementation of hook_user().
790+ */
791+function user_load_cache_user($op, &$edit, &$account, $category = NULL) {
792+ if ($op == 'update' || $op == 'delete' || $op == 'login') {
793+ user_load_cache_clear($account->uid);
794+ }
795+}
796+
797+/**
798+ * Implementation of hook_flush_caches().
799+ */
800+function user_load_cache_flush_caches() {
801+ return array('cache_user');
802+}
803+
804+/**
805+ * Implementation of hook_form_FORM_ID_alter().
806+ *
807+ * @todo: is this necessary?
808+ */
809+function user_load_cache_form_user_profile_form_alter(&$form, &$form_state) {
810+ $form['#submit'][] = '_user_load_cache_profile_form_submit';
811+}
812+
813+/**
814+ * Submit handler for the user profile form.
815+ */
816+function _user_load_cache_profile_form_submit($form, &$form_state) {
817+ if (user_access('select different theme')) {
818+ // This prevents the system theme from getting corrupted.
819+ init_theme();
820+ }
821+}

Subscribers

People subscribed via source and target branches

to status/vote changes: