Ticket #11914: 11914.3.diff

File 11914.3.diff, 11.3 KB (added by miqrogroove, 3 years ago)

Adds user object caching to minimize query count.

Line 
1Index: wp-admin/includes/schema.php
2===================================================================
3--- wp-admin/includes/schema.php        (revision 13173)
4+++ wp-admin/includes/schema.php        (working copy)
5@@ -145,7 +145,8 @@
6   PRIMARY KEY  (ID),
7   KEY post_name (post_name),
8   KEY type_status_date (post_type,post_status,post_date,ID),
9-  KEY post_parent (post_parent)
10+  KEY post_parent (post_parent),
11+  KEY post_author (post_author)
12 ) $charset_collate;
13 CREATE TABLE $wpdb->users (
14   ID bigint(20) unsigned NOT NULL auto_increment,
15Index: wp-admin/includes/template.php
16===================================================================
17--- wp-admin/includes/template.php      (revision 13173)
18+++ wp-admin/includes/template.php      (working copy)
19@@ -1789,16 +1789,17 @@
20 }
21 
22 /**
23- * {@internal Missing Short Description}}
24+ * Generate HTML for a single row on the users.php admin panel.
25  *
26  * @since unknown
27  *
28- * @param unknown_type $user_object
29- * @param unknown_type $style
30- * @param unknown_type $role
31- * @return unknown
32+ * @param object $user_object
33+ * @param string $style Optional. Attributes added to the TR element.  Must be sanitized.
34+ * @param string $role Key for the $wp_roles array.
35+ * @param int $numposts Optional. Post count to display for this user.  Defaults to zero, as in, a new user has made zero posts.
36+ * @return string
37  */
38-function user_row( $user_object, $style = '', $role = '' ) {
39+function user_row( $user_object, $style = '', $role = '', $numposts = 0 ) {
40        global $wp_roles;
41 
42        $current_user = wp_get_current_user();
43@@ -1814,7 +1815,6 @@
44                $short_url = substr( $short_url, 0, -1 );
45        if ( strlen( $short_url ) > 35 )
46                $short_url = substr( $short_url, 0, 32 ).'...';
47-       $numposts = get_usernumposts( $user_object->ID );
48        $checkbox = '';
49        // Check if the user for this row is editable
50        if ( current_user_can( 'edit_user', $user_object->ID ) ) {
51Index: wp-admin/users.php
52===================================================================
53--- wp-admin/users.php  (revision 13173)
54+++ wp-admin/users.php  (working copy)
55@@ -208,9 +208,15 @@
56        $userspage = isset($_GET['userspage']) ? $_GET['userspage'] : null;
57        $role = isset($_GET['role']) ? $_GET['role'] : null;
58 
59-       // Query the users
60+       // Query the user IDs for this page
61        $wp_user_search = new WP_User_Search($usersearch, $userspage, $role);
62 
63+       // Query the post counts for this page
64+       $post_counts = get_users_numposts($wp_user_search->get_results());
65+       
66+       // Query the users for this page
67+       cache_users($wp_user_search->get_results());
68+
69        $messages = array();
70        if ( isset($_GET['update']) ) :
71                switch($_GET['update']) {
72@@ -265,16 +271,10 @@
73 <?php
74 $role_links = array();
75 $avail_roles = array();
76-$users_of_blog = get_users_of_blog();
77-$total_users = count( $users_of_blog );
78-foreach ( (array) $users_of_blog as $b_user ) {
79-       $b_roles = unserialize($b_user->meta_value);
80-       foreach ( (array) $b_roles as $b_role => $val ) {
81-               if ( !isset($avail_roles[$b_role]) )
82-                       $avail_roles[$b_role] = 0;
83-               $avail_roles[$b_role]++;
84-       }
85-}
86+
87+$users_of_blog = count_blog_users_by_role_intime();
88+$total_users = $users_of_blog['total_users'];
89+$avail_roles =& $users_of_blog['avail_roles'];
90 unset($users_of_blog);
91 
92 $current_role = false;
93@@ -372,7 +372,7 @@
94        $role = array_shift($roles);
95 
96        $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
97-       echo "\n\t" . user_row($user_object, $style, $role);
98+       echo "\n\t", user_row($user_object, $style, $role, $post_counts[(string)$userid]);
99 }
100 ?>
101 </tbody>
102Index: wp-includes/pluggable.php
103===================================================================
104--- wp-includes/pluggable.php   (revision 13173)
105+++ wp-includes/pluggable.php   (working copy)
106@@ -139,6 +139,40 @@
107 }
108 endif;
109 
110+if ( !function_exists('cache_users') ) :
111+/**
112+ * Retrieve info for user lists to prevent multiple queries by get_userdata()
113+ *
114+ * @since 3.0.0
115+ *
116+ * @param array $users User ID numbers list
117+ */
118+function cache_users( $users ) {
119+       global $wpdb;
120+
121+       $clean = array();
122+       foreach($users as $id) {
123+               $id = (int) $id;
124+               if (wp_cache_get($id, 'users')) {
125+                       // seems to be cached already
126+               } else {
127+                       $clean[] = $id;
128+               }
129+       }
130+
131+       if ( 0 == count($clean) )
132+               return;
133+
134+       $list = implode(',', $clean);
135+
136+       $result = $wpdb->get_results("SELECT * FROM $wpdb->users WHERE ID IN ($list)", OBJECT_K);
137+
138+       foreach($result as $user_object) {
139+               wp_cache_add($user_object->ID, $user_object, 'users');
140+       }
141+}
142+endif;
143+
144 if ( !function_exists('get_user_by') ) :
145 /**
146  * Retrieve user info by a given field
147Index: wp-includes/post.php
148===================================================================
149--- wp-includes/post.php        (revision 13173)
150+++ wp-includes/post.php        (working copy)
151@@ -3623,9 +3623,23 @@
152  * @return string SQL code that can be added to a where clause.
153  */
154 function get_private_posts_cap_sql($post_type) {
155-       global $user_ID;
156-       $cap = '';
157+       return get_posts_by_author_sql($post_type, FALSE);
158+}
159 
160+/**
161+ * Retrieve the post SQL based on capability, author, and type.
162+ *
163+ * See above for full description.
164+ *
165+ * @since 3.0.0
166+ * @param string $post_type currently only supports 'post' or 'page'.
167+ * @param bool $full Optional.  Returns a full WHERE statement instead of just an 'andalso' term.
168+ * @param int $post_author Optional.  Query posts having a single author ID.
169+ * @return string SQL WHERE code that can be added to a query.
170+ */
171+function get_posts_by_author_sql($post_type, $full = TRUE, $post_author = NULL) {
172+       global $user_ID, $wpdb;
173+
174        // Private posts
175        if ($post_type == 'post') {
176                $cap = 'read_private_posts';
177@@ -3634,24 +3648,40 @@
178                $cap = 'read_private_pages';
179        // Dunno what it is, maybe plugins have their own post type?
180        } else {
181+               $cap = '';
182                $cap = apply_filters('pub_priv_sql_capability', $cap);
183 
184                if (empty($cap)) {
185                        // We don't know what it is, filters don't change anything,
186                        // so set the SQL up to return nothing.
187-                       return '1 = 0';
188+                       return ' 1 = 0 ';
189                }
190        }
191 
192-       $sql = '(post_status = \'publish\'';
193+       if ($full) {
194+               if (is_null($post_author)) {
195+                       $sql = $wpdb->prepare('WHERE post_type = %s AND ', $post_type);
196+               } else {
197+                       $sql = $wpdb->prepare('WHERE post_author = %d AND post_type = %s AND ', $post_author, $post_type);
198+               }
199+       } else {
200+               $sql = '';
201+       }
202 
203+       $sql .= "(post_status = 'publish'";
204+
205        if (current_user_can($cap)) {
206                // Does the user have the capability to view private posts? Guess so.
207-               $sql .= ' OR post_status = \'private\'';
208+               $sql .= " OR post_status = 'private'";
209        } elseif (is_user_logged_in()) {
210                // Users can view their own private posts.
211-               $sql .= ' OR post_status = \'private\' AND post_author = \'' . $user_ID . '\'';
212-       }
213+               $id = (int) $user_ID;
214+               if (is_null($post_author)) {
215+                       $sql .= " OR post_status = 'private' AND post_author = $id";
216+               } elseif ($id == (int)$post_author) {
217+                       $sql .= " OR post_status = 'private'";
218+               } // else none
219+       } // else none
220 
221        $sql .= ')';
222 
223@@ -4443,4 +4473,4 @@
224 
225                add_filter('the_preview', '_set_preview');
226        }
227-}
228\ No newline at end of file
229+}
230Index: wp-includes/user.php
231===================================================================
232--- wp-includes/user.php        (revision 13173)
233+++ wp-includes/user.php        (working copy)
234@@ -178,12 +178,47 @@
235  */
236 function get_usernumposts($userid) {
237        global $wpdb;
238-       $userid = (int) $userid;
239-       $count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $wpdb->posts WHERE post_author = %d AND post_type = 'post' AND ", $userid) . get_private_posts_cap_sql('post'));
240+
241+       $where = get_posts_by_author_sql('post', TRUE, $userid);
242+
243+       $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
244+
245        return apply_filters('get_usernumposts', $count, $userid);
246 }
247 
248 /**
249+ * Number of posts written by a list of users.
250+ *
251+ * @since 3.0.0
252+ * @param array $userid User ID number list.
253+ * @return array Amount of posts each user has written.
254+ */
255+function get_users_numposts($users) {
256+       global $wpdb;
257+       
258+       if (0 == count($users))
259+               return array();
260+           
261+       $userlist = implode(',', $users);
262+       $where = get_posts_by_author_sql('post');
263+
264+       $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N );
265+
266+       $count = array();
267+       foreach($result as $row) {
268+               $count[$row[0]] = $row[1];
269+       }
270+
271+       foreach($users as $id) {
272+               $id = (string) $id;
273+               if (!isset($count[$id]))
274+                       $count[$id] = 0;
275+       }
276+
277+       return $count;
278+}
279+
280+/**
281  * Check that the user login name and password is correct.
282  *
283  * @since 0.71
284@@ -440,6 +475,93 @@
285        return true;
286 }
287 
288+/**
289+ * Count number of users who have each of the user roles.
290+ *
291+ * Assumes there are neither duplicated nor orphaned capabilities meta_values.
292+ * Assumes the serialized array values are meaningless, only array keys are used.
293+ * Intended scale for this version is 10^5 users.  It is not quite capable of that yet due to WP Bug #12257
294+ * Memory exhaustion is expected at higher orders.
295+ *
296+ * @since 3.0.0
297+ * @return array Includes a grand total and an array of counts indexed by role strings.
298+ */
299+function count_blog_users_by_role_inmem() {
300+       global $wpdb, $blog_id;
301+
302+       $id = (int) $blog_id;
303+       $blog_prefix = $wpdb->get_blog_prefix($id);
304+       $avail_roles = array();
305+
306+       $users_of_blog = $wpdb->get_col( "SELECT meta_value FROM $wpdb->usermeta WHERE meta_key = '{$blog_prefix}capabilities'" );
307+
308+       foreach ( $users_of_blog as $caps_meta ) {
309+               $b_roles = unserialize($caps_meta);
310+               if ( is_array($b_roles) ) {
311+                       foreach ( $b_roles as $b_role => $val ) {
312+                               if ( isset($avail_roles[$b_role]) )
313+                                       $avail_roles[$b_role]++;
314+                               else
315+                                       $avail_roles[$b_role] = 1;
316+                       }
317+               }
318+       }
319+
320+       $result = array();
321+       $result['total_users'] = count( $users_of_blog );
322+       $result['avail_roles'] =& $avail_roles;
323+       
324+       return $result;
325+}
326+
327+/**
328+ * Count number of users who have each of the user roles.
329+ *
330+ * Assumes there are neither duplicated nor orphaned capabilities meta_values.
331+ * Assumes role names are unique phrases.  Same assumption made by WP_User_Search::prepare_query()
332+ * Intended scale for this version is 10^7 users.
333+ * CPU exhaustion is expected at higher orders.
334+ *
335+ * @since 3.0.0
336+ * @return array Includes a grand total and an array of counts indexed by role strings.
337+ */
338+function count_blog_users_by_role_intime() {
339+       global $wpdb, $blog_id, $wp_roles;
340+
341+       // Initialize
342+       $id = (int) $blog_id;
343+       $blog_prefix = $wpdb->get_blog_prefix($id);
344+       $avail_roles = $wp_roles->get_names();
345+
346+       // Build a CPU-intensive query that will return concise information.
347+       $select_count = array();
348+       foreach ( $avail_roles as $this_role => $name ) {
349+               $select_count[] = "COUNT(NULLIF(`meta_value` LIKE '%" . like_escape($this_role) . "%', FALSE))";
350+       }
351+       $select_count = implode(', ', $select_count);
352+
353+       // Add the meta_value index to the selection list, then run the query.
354+       $row = $wpdb->get_row( "SELECT $select_count, COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$blog_prefix}capabilities'", ARRAY_N );
355+
356+       // Run the previous loop again to associate results with role names.
357+       $col = 0;
358+       $role_counts = array();
359+       foreach ( $avail_roles as $this_role => $name ) {
360+               $count = (int) $row[$col++];
361+               if ($count > 0)
362+                       $role_counts[$this_role] = $count;
363+       }
364+
365+       // Get the meta_value index from the end of the result set.
366+       $total_users = (int) $row[$col];
367+
368+       $result = array();
369+       $result['total_users'] = $total_users;
370+       $result['avail_roles'] =& $role_counts;
371+
372+       return $result;
373+}
374+
375 //
376 // Private helper functions
377 //