Make WordPress Core

Ticket #11914: 11914.6.diff

File 11914.6.diff, 15.9 KB (added by miqrogroove, 15 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?>