Ticket #11914: 11914.6.diff

File 11914.6.diff, 15.9 KB (added by miqrogroove, 3 years ago)

Adds usermeta caching, improvements suggested by ryan.

  • wp-admin/includes/schema.php

     
    146146  PRIMARY KEY  (ID), 
    147147  KEY post_name (post_name), 
    148148  KEY type_status_date (post_type,post_status,post_date,ID), 
    149   KEY post_parent (post_parent) 
     149  KEY post_parent (post_parent), 
     150  KEY post_author (post_author) 
    150151) $charset_collate; 
    151152CREATE TABLE $wpdb->users ( 
    152153  ID bigint(20) unsigned NOT NULL auto_increment, 
  • wp-admin/includes/template.php

     
    17941794} 
    17951795 
    17961796/** 
    1797  * {@internal Missing Short Description}} 
     1797 * Generate HTML for a single row on the users.php admin panel. 
    17981798 * 
    17991799 * @since unknown 
    18001800 * 
    1801  * @param unknown_type $user_object 
    1802  * @param unknown_type $style 
    1803  * @param unknown_type $role 
    1804  * @return unknown 
     1801 * @param object $user_object 
     1802 * @param string $style Optional. Attributes added to the TR element.  Must be sanitized. 
     1803 * @param string $role Key for the $wp_roles array. 
     1804 * @param int $numposts Optional. Post count to display for this user.  Defaults to zero, as in, a new user has made zero posts. 
     1805 * @return string 
    18051806 */ 
    1806 function user_row( $user_object, $style = '', $role = '' ) { 
     1807function user_row( $user_object, $style = '', $role = '', $numposts = 0 ) { 
    18071808        global $wp_roles; 
    18081809 
    18091810        $current_user = wp_get_current_user(); 
     
    18191820                $short_url = substr( $short_url, 0, -1 ); 
    18201821        if ( strlen( $short_url ) > 35 ) 
    18211822                $short_url = substr( $short_url, 0, 32 ).'...'; 
    1822         $numposts = get_usernumposts( $user_object->ID ); 
    18231823        $checkbox = ''; 
    18241824        // Check if the user for this row is editable 
    18251825        if ( current_user_can( 'edit_user', $user_object->ID ) ) { 
  • wp-admin/users.php

     
    208208        $userspage = isset($_GET['userspage']) ? $_GET['userspage'] : null; 
    209209        $role = isset($_GET['role']) ? $_GET['role'] : null; 
    210210 
    211         // Query the users 
     211        // Query the user IDs for this page 
    212212        $wp_user_search = new WP_User_Search($usersearch, $userspage, $role); 
    213213 
     214        // Query the post counts for this page 
     215        $post_counts = count_many_users_posts($wp_user_search->get_results()); 
     216 
     217        // Query the users for this page 
     218        cache_users($wp_user_search->get_results()); 
     219 
    214220        $messages = array(); 
    215221        if ( isset($_GET['update']) ) : 
    216222                switch($_GET['update']) { 
     
    263269<form id="list-filter" action="" method="get"> 
    264270<ul class="subsubsub"> 
    265271<?php 
    266 $role_links = array(); 
    267 $avail_roles = array(); 
    268 $users_of_blog = get_users_of_blog(); 
    269 $total_users = count( $users_of_blog ); 
    270 foreach ( (array) $users_of_blog as $b_user ) { 
    271         $b_roles = unserialize($b_user->meta_value); 
    272         foreach ( (array) $b_roles as $b_role => $val ) { 
    273                 if ( !isset($avail_roles[$b_role]) ) 
    274                         $avail_roles[$b_role] = 0; 
    275                 $avail_roles[$b_role]++; 
    276         } 
    277 } 
     272$users_of_blog = count_users(); 
     273$total_users = $users_of_blog['total_users']; 
     274$avail_roles =& $users_of_blog['avail_roles']; 
    278275unset($users_of_blog); 
    279276 
    280277$current_role = false; 
    281278$class = empty($role) ? ' class="current"' : ''; 
     279$role_links = array(); 
    282280$role_links[] = "<li><a href='users.php'$class>" . sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_users, 'users' ), number_format_i18n( $total_users ) ) . '</a>'; 
    283281foreach ( $wp_roles->get_names() as $this_role => $name ) { 
    284282        if ( !isset($avail_roles[$this_role]) ) 
     
    372370        $role = array_shift($roles); 
    373371 
    374372        $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"'; 
    375         echo "\n\t" . user_row($user_object, $style, $role); 
     373        echo "\n\t", user_row($user_object, $style, $role, $post_counts[(string)$userid]); 
    376374} 
    377375?> 
    378376</tbody> 
  • wp-includes/author-template.php

     
    151151 * 
    152152 * @since 1.5 
    153153 * @uses $post The current post in the Loop's DB object. 
    154  * @uses get_usernumposts() 
     154 * @uses count_user_posts() 
    155155 * @return int The number of posts by the author. 
    156156 */ 
    157157function get_the_author_posts() { 
    158158        global $post; 
    159         return get_usernumposts($post->post_author); 
     159        return count_user_posts($post->post_author); 
    160160} 
    161161 
    162162/** 
  • wp-includes/pluggable.php

     
    139139} 
    140140endif; 
    141141 
     142if ( !function_exists('cache_users') ) : 
     143/** 
     144 * Retrieve info for user lists to prevent multiple queries by get_userdata() 
     145 * 
     146 * @since 3.0.0 
     147 * 
     148 * @param array $users User ID numbers list 
     149 */ 
     150function cache_users( $users ) { 
     151        global $wpdb; 
     152 
     153        $clean = array(); 
     154        foreach($users as $id) { 
     155                $id = (int) $id; 
     156                if (wp_cache_get($id, 'users')) { 
     157                        // seems to be cached already 
     158                } else { 
     159                        $clean[] = $id; 
     160                } 
     161        } 
     162 
     163        if ( 0 == count($clean) ) 
     164                return; 
     165 
     166        $list = implode(',', $clean); 
     167 
     168        $results = $wpdb->get_results("SELECT * FROM $wpdb->users WHERE ID IN ($list)"); 
     169     
     170        _fill_many_users($results); 
     171} 
     172endif; 
     173 
    142174if ( !function_exists('get_user_by') ) : 
    143175/** 
    144176 * Retrieve user info by a given field 
  • wp-includes/post.php

     
    36393639 * @return string SQL code that can be added to a where clause. 
    36403640 */ 
    36413641function get_private_posts_cap_sql($post_type) { 
    3642         global $user_ID; 
    3643         $cap = ''; 
     3642        return get_posts_by_author_sql($post_type, FALSE); 
     3643} 
    36443644 
     3645/** 
     3646 * Retrieve the post SQL based on capability, author, and type. 
     3647 * 
     3648 * See above for full description. 
     3649 * 
     3650 * @since 3.0.0 
     3651 * @param string $post_type currently only supports 'post' or 'page'. 
     3652 * @param bool $full Optional.  Returns a full WHERE statement instead of just an 'andalso' term. 
     3653 * @param int $post_author Optional.  Query posts having a single author ID. 
     3654 * @return string SQL WHERE code that can be added to a query. 
     3655 */ 
     3656function get_posts_by_author_sql($post_type, $full = TRUE, $post_author = NULL) { 
     3657        global $user_ID, $wpdb; 
     3658 
    36453659        // Private posts 
    36463660        if ($post_type == 'post') { 
    36473661                $cap = 'read_private_posts'; 
     
    36503664                $cap = 'read_private_pages'; 
    36513665        // Dunno what it is, maybe plugins have their own post type? 
    36523666        } else { 
     3667                $cap = ''; 
    36533668                $cap = apply_filters('pub_priv_sql_capability', $cap); 
    36543669 
    36553670                if (empty($cap)) { 
    36563671                        // We don't know what it is, filters don't change anything, 
    36573672                        // so set the SQL up to return nothing. 
    3658                         return '1 = 0'; 
     3673                        return ' 1 = 0 '; 
    36593674                } 
    36603675        } 
    36613676 
    3662         $sql = '(post_status = \'publish\''; 
     3677        if ($full) { 
     3678                if (is_null($post_author)) { 
     3679                        $sql = $wpdb->prepare('WHERE post_type = %s AND ', $post_type); 
     3680                } else { 
     3681                        $sql = $wpdb->prepare('WHERE post_author = %d AND post_type = %s AND ', $post_author, $post_type); 
     3682                } 
     3683        } else { 
     3684                $sql = ''; 
     3685        } 
    36633686 
     3687        $sql .= "(post_status = 'publish'"; 
     3688 
    36643689        if (current_user_can($cap)) { 
    36653690                // Does the user have the capability to view private posts? Guess so. 
    3666                 $sql .= ' OR post_status = \'private\''; 
     3691                $sql .= " OR post_status = 'private'"; 
    36673692        } elseif (is_user_logged_in()) { 
    36683693                // Users can view their own private posts. 
    3669                 $sql .= ' OR post_status = \'private\' AND post_author = \'' . $user_ID . '\''; 
    3670         } 
     3694                $id = (int) $user_ID; 
     3695                if (is_null($post_author) || !$full) { 
     3696                        $sql .= " OR post_status = 'private' AND post_author = $id"; 
     3697                } elseif ($id == (int)$post_author) { 
     3698                        $sql .= " OR post_status = 'private'"; 
     3699                } // else none 
     3700        } // else none 
    36713701 
    36723702        $sql .= ')'; 
    36733703 
  • wp-includes/user.php

     
    176176 * @param int $userid User ID. 
    177177 * @return int Amount of posts user has written. 
    178178 */ 
    179 function get_usernumposts($userid) { 
     179function count_user_posts($userid) { 
    180180        global $wpdb; 
    181         $userid = (int) $userid; 
    182         $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')); 
     181 
     182        $where = get_posts_by_author_sql('post', TRUE, $userid); 
     183 
     184        $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" ); 
     185 
    183186        return apply_filters('get_usernumposts', $count, $userid); 
    184187} 
    185188 
    186189/** 
     190 * Number of posts written by a list of users. 
     191 * 
     192 * @since 3.0.0 
     193 * @param array $userid User ID number list. 
     194 * @return array Amount of posts each user has written. 
     195 */ 
     196function count_many_users_posts($users) { 
     197        global $wpdb; 
     198         
     199        if (0 == count($users)) 
     200                return array(); 
     201             
     202        $userlist = implode(',', $users); 
     203        $where = get_posts_by_author_sql('post'); 
     204 
     205        $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N ); 
     206 
     207        $count = array(); 
     208        foreach($result as $row) { 
     209                $count[$row[0]] = $row[1]; 
     210        } 
     211 
     212        foreach($users as $id) { 
     213                $id = (string) $id; 
     214                if (!isset($count[$id])) 
     215                        $count[$id] = 0; 
     216        } 
     217 
     218        return $count; 
     219} 
     220 
     221/** 
    187222 * Check that the user login name and password is correct. 
    188223 * 
    189224 * @since 0.71 
     
    438473        return true; 
    439474} 
    440475 
     476/** 
     477 * Count number of users who have each of the user roles. 
     478 * 
     479 * Assumes there are neither duplicated nor orphaned capabilities meta_values. 
     480 * Assumes role names are unique phrases.  Same assumption made by WP_User_Search::prepare_query() 
     481 * Using $strategy = 'time' this is CPU-intensive and should handle around 10^7 users. 
     482 * Using $strategy = 'memory' this is memory-intensive and should handle around 10^5 users, but see WP Bug #12257. 
     483 * 
     484 * @since 3.0.0 
     485 * @param string $strategy 'time' or 'memory' 
     486 * @return array Includes a grand total and an array of counts indexed by role strings. 
     487 */ 
     488function count_users($strategy = 'time') { 
     489        global $wpdb, $blog_id, $wp_roles; 
     490 
     491        // Initialize 
     492        $id = (int) $blog_id; 
     493        $blog_prefix = $wpdb->get_blog_prefix($id); 
     494        $result = array(); 
     495 
     496        if ('time' == $strategy) { 
     497                $avail_roles = $wp_roles->get_names(); 
     498 
     499                // Build a CPU-intensive query that will return concise information. 
     500                $select_count = array(); 
     501                foreach ( $avail_roles as $this_role => $name ) { 
     502                        $select_count[] = "COUNT(NULLIF(`meta_value` LIKE '%" . like_escape($this_role) . "%', FALSE))"; 
     503                } 
     504                $select_count = implode(', ', $select_count); 
     505 
     506                // Add the meta_value index to the selection list, then run the query. 
     507                $row = $wpdb->get_row( "SELECT $select_count, COUNT(*) FROM $wpdb->usermeta WHERE meta_key = '{$blog_prefix}capabilities'", ARRAY_N ); 
     508 
     509                // Run the previous loop again to associate results with role names. 
     510                $col = 0; 
     511                $role_counts = array(); 
     512                foreach ( $avail_roles as $this_role => $name ) { 
     513                        $count = (int) $row[$col++]; 
     514                        if ($count > 0) { 
     515                                $role_counts[$this_role] = $count; 
     516                        } 
     517                } 
     518 
     519                // Get the meta_value index from the end of the result set. 
     520                $total_users = (int) $row[$col]; 
     521 
     522                $result['total_users'] = $total_users; 
     523                $result['avail_roles'] =& $role_counts; 
     524        } else { 
     525                $avail_roles = array(); 
     526 
     527                $users_of_blog = $wpdb->get_col( "SELECT meta_value FROM $wpdb->usermeta WHERE meta_key = '{$blog_prefix}capabilities'" ); 
     528 
     529                foreach ( $users_of_blog as $caps_meta ) { 
     530                        $b_roles = unserialize($caps_meta); 
     531                        if ( is_array($b_roles) ) { 
     532                                foreach ( $b_roles as $b_role => $val ) { 
     533                                        if ( isset($avail_roles[$b_role]) ) { 
     534                                                $avail_roles[$b_role]++; 
     535                                        } else { 
     536                                                $avail_roles[$b_role] = 1; 
     537                                        } 
     538                                } 
     539                        } 
     540                } 
     541 
     542                $result['total_users'] = count( $users_of_blog ); 
     543                $result['avail_roles'] =& $avail_roles; 
     544        } 
     545 
     546        return $result; 
     547} 
     548 
    441549// 
    442550// Private helper functions 
    443551// 
     
    588696 * 
    589697 * The finished user data is cached, but the cache is not used to fill in the 
    590698 * user data for the given object. Once the function has been used, the cache 
    591  * should be used to retrieve user data. The purpose seems then to be to ensure 
    592  * that the data in the object is always fresh. 
     699 * should be used to retrieve user data. The intention is if the current data 
     700 * had been cached already, there would be no need to call this function. 
    593701 * 
    594702 * @access private 
    595703 * @since 2.5.0 
     
    598706 * @param object $user The user data object. 
    599707 */ 
    600708function _fill_user( &$user ) { 
     709        $metavalues = get_user_metavalues(array($user->ID)); 
     710        _fill_single_user($user, $metavalues[$user->ID]); 
     711} 
     712 
     713/** 
     714 * Perform the query to get the $metavalues array(s) needed by _fill_user and _fill_many_users 
     715 * 
     716 * @since 3.0.0 
     717 * @param array $ids User ID numbers list. 
     718 * @return array of arrays. The array is indexed by user_id, containing $metavalues object arrays. 
     719 */ 
     720function get_user_metavalues($ids) { 
    601721        global $wpdb; 
    602722 
     723        $clean = array_map('intval', $ids); 
     724        if ( 0 == count($clean) ) 
     725                return $objects; 
     726 
     727        $list = implode(',', $clean); 
     728 
    603729        $show = $wpdb->hide_errors(); 
    604         $metavalues = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM $wpdb->usermeta WHERE user_id = %d", $user->ID)); 
     730        $metavalues = $wpdb->get_results("SELECT user_id, meta_key, meta_value FROM $wpdb->usermeta WHERE user_id IN ($list)"); 
    605731        $wpdb->show_errors($show); 
    606732 
    607         if ( $metavalues ) { 
    608                 foreach ( (array) $metavalues as $meta ) { 
    609                         $value = maybe_unserialize($meta->meta_value); 
    610                         $user->{$meta->meta_key} = $value; 
    611                 } 
     733        $objects = array(); 
     734        foreach($clean as $id) { 
     735                $objects[$id] = array(); 
    612736        } 
     737        foreach($metavalues as $meta_object) { 
     738                $objects[$meta_object->user_id][] = $meta_object; 
     739        } 
    613740 
     741        return $objects; 
     742} 
     743 
     744/** 
     745 * Unserialize user metadata, fill $user object, then cache everything. 
     746 * 
     747 * @since 3.0.0 
     748 * @param object $user The User object. 
     749 * @param array $metavalues An array of objects provided by get_user_metavalues() 
     750 */ 
     751function _fill_single_user( &$user, &$metavalues ) { 
     752        global $wpdb; 
     753 
     754        foreach ( $metavalues as $meta ) { 
     755                $value = maybe_unserialize($meta->meta_value); 
     756                $user->{$meta->meta_key} = $value; 
     757        } 
     758 
    614759        $level = $wpdb->prefix . 'user_level'; 
    615760        if ( isset( $user->{$level} ) ) 
    616761                $user->user_level = $user->{$level}; 
     
    623768        if ( isset($user->description) ) 
    624769                $user->user_description = $user->description; 
    625770 
    626         wp_cache_add($user->ID, $user, 'users'); 
    627         wp_cache_add($user->user_login, $user->ID, 'userlogins'); 
    628         wp_cache_add($user->user_email, $user->ID, 'useremail'); 
    629         wp_cache_add($user->user_nicename, $user->ID, 'userslugs'); 
     771        update_user_caches($user); 
    630772} 
    631773 
    632774/** 
     775 * Take an array of user objects, fill them with metas, and cache them. 
     776 * 
     777 * @since 3.0.0 
     778 * @param array $users User objects 
     779 * @param array $metas User metavalues objects 
     780 */ 
     781function _fill_many_users( &$users ) { 
     782        $ids = array(); 
     783        foreach($users as $user_object) { 
     784                $ids[] = $user_object->ID; 
     785        } 
     786 
     787    $metas = get_user_metavalues($ids); 
     788 
     789        foreach($users as $user_object) { 
     790                if (isset($metas[$user_object->ID])) { 
     791                _fill_single_user($user_object, $metas[$user_object->ID]); 
     792                } 
     793        } 
     794} 
     795 
     796/** 
    633797 * Sanitize every user field. 
    634798 * 
    635799 * If the context is 'raw', then the user object or array will get minimal santization of the int fields. 
     
    746910} 
    747911 
    748912/** 
     913 * Update all user caches 
     914 * 
     915 * @since 3.0.0 
     916 * 
     917 * @param object $user User object to be cached 
     918 */ 
     919function update_user_caches(&$user) { 
     920        wp_cache_add($user->ID, $user, 'users'); 
     921        wp_cache_add($user->user_login, $user->ID, 'userlogins'); 
     922        wp_cache_add($user->user_email, $user->ID, 'useremail'); 
     923        wp_cache_add($user->user_nicename, $user->ID, 'userslugs'); 
     924} 
     925 
     926/** 
    749927 * Clean all user caches 
    750928 * 
    751  * @since 3.0 
     929 * @since 3.0.0 
    752930 * 
    753931 * @param int $id User ID 
    754  * @return void 
    755932 */ 
    756933function clean_user_cache($id) { 
    757934        $user = new WP_User($id); 
     
    762939        wp_cache_delete($user->user_nicename, 'userslugs'); 
    763940} 
    764941 
    765 ?> 
    766  No newline at end of file 
     942?>