From 1507df9d59e24d3bbdd3dd6b2bde21a5253792f6 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 27 Sep 2017 13:03:03 +0000 Subject: [PATCH] Users: Introduce the concept of a large site in order to speed up the Users screen when there are many users. Calling the `count_users()` function is expensive, regardless of the counting strategy that's used, and it gets slower the more users there are on a site. In order to speed up the Users screen in the admin area, calling `count_users()` can be avoided entirely while still displaying the total count for users. This introduces some new functions: * `wp_is_large_user_count()` * `wp_get_active_user_count()` * `wp_update_active_user_count()` A corresponding `wp_is_large_user_count` filter is also introduced. Props tharsheblows, johnbillion Fixes #38741 git-svn-id: https://develop.svn.wordpress.org/trunk@41613 602fd350-edb4-49c9-b593-d223f7449a82 --- .../includes/class-wp-users-list-table.php | 53 +++++++++++----- src/wp-includes/default-filters.php | 5 ++ src/wp-includes/functions.php | 62 +++++++++++++++++++ src/wp-includes/ms-functions.php | 7 ++- src/wp-includes/update.php | 3 +- tests/phpunit/tests/functions.php | 54 ++++++++++++++++ 6 files changed, 167 insertions(+), 17 deletions(-) diff --git a/src/wp-admin/includes/class-wp-users-list-table.php b/src/wp-admin/includes/class-wp-users-list-table.php index 61ea897c5a..6825bca5f0 100644 --- a/src/wp-admin/includes/class-wp-users-list-table.php +++ b/src/wp-admin/includes/class-wp-users-list-table.php @@ -166,27 +166,48 @@ class WP_Users_List_Table extends WP_List_Table { global $role; $wp_roles = wp_roles(); + $count_users = true; + + if ( wp_is_large_user_count() ) { + $count_users = false; + } elseif ( is_multisite() && wp_is_large_network( 'users' ) ) { + $count_users = false; + } if ( $this->is_site_users ) { $url = 'site-users.php?id=' . $this->site_id; - switch_to_blog( $this->site_id ); - $users_of_blog = count_users( 'time', $this->site_id ); - restore_current_blog(); + if ( $count_users ) { + switch_to_blog( $this->site_id ); + $users_of_blog = count_users( 'time', $this->site_id ); + restore_current_blog(); + } } else { $url = 'users.php'; - $users_of_blog = count_users(); + if ( $count_users ) { + $users_of_blog = count_users(); + } } - $total_users = $users_of_blog['total_users']; - $avail_roles =& $users_of_blog['avail_roles']; - unset($users_of_blog); + if ( $count_users ) { + $total_users = $users_of_blog['total_users']; + $avail_roles =& $users_of_blog['avail_roles']; + unset($users_of_blog); + } else { + $avail_roles = array(); + } $class = empty($role) ? ' class="current"' : ''; $role_links = array(); - $role_links['all'] = "" . sprintf( _nx( 'All (%s)', 'All (%s)', $total_users, 'users' ), number_format_i18n( $total_users ) ) . ''; + + if ( $count_users ) { + $role_links['all'] = "" . sprintf( _nx( 'All (%s)', 'All (%s)', $total_users, 'users' ), number_format_i18n( $total_users ) ) . ''; + } else { + $role_links['all'] = "" . _x( 'All', 'users' ) . ''; + } foreach ( $wp_roles->get_names() as $this_role => $name ) { - if ( !isset($avail_roles[$this_role]) ) + if ( $count_users && !isset($avail_roles[$this_role]) ) { continue; + } $class = ''; @@ -195,12 +216,14 @@ class WP_Users_List_Table extends WP_List_Table { } $name = translate_user_role( $name ); - /* translators: User role name with count */ - $name = sprintf( __('%1$s (%2$s)'), $name, number_format_i18n( $avail_roles[$this_role] ) ); + if ( $count_users ) { + /* translators: User role name with count */ + $name = sprintf( __('%1$s (%2$s)'), $name, number_format_i18n( $avail_roles[$this_role] ) ); + } $role_links[$this_role] = "$name"; } - if ( ! empty( $avail_roles['none' ] ) ) { + if ( ! $count_users || ! empty( $avail_roles['none' ] ) ) { $class = ''; @@ -209,8 +232,10 @@ class WP_Users_List_Table extends WP_List_Table { } $name = __( 'No role' ); - /* translators: User role name with count */ - $name = sprintf( __('%1$s (%2$s)'), $name, number_format_i18n( $avail_roles['none' ] ) ); + if ( $count_users ) { + /* translators: User role name with count */ + $name = sprintf( __('%1$s (%2$s)'), $name, number_format_i18n( $avail_roles['none' ] ) ); + } $role_links['none'] = "$name"; } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 0ed4be9056..a725221381 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -466,6 +466,11 @@ add_filter( 'nav_menu_item_id', '_nav_menu_item_id_use_once', 10, 2 ); // Widgets add_action( 'init', 'wp_widgets_init', 1 ); +// User counts +foreach ( array( 'user_register', 'deleted_user' ) as $action ){ + add_action( $action, 'wp_update_active_user_count' ); +} + // Admin Bar // Don't remove. Wrong way to disable. add_action( 'template_redirect', '_wp_admin_bar_init', 0 ); diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 2559613fbc..63551a227b 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -5833,3 +5833,65 @@ All at ###SITENAME### $site_name ), $email_change_email['message'], $email_change_email['headers'] ); } + +/** + * Whether or not we have a large site, based on its number of users. + * + * The default criteria for a large site is more than 10,000 users. + * + * @since 4.9.0 + * + * @return bool True if the site meets the criteria for large. False otherwise. + */ +function wp_is_large_user_count() { + $count = wp_get_active_user_count(); + + /** + * Filters whether the site is considered large, based on its number of users. + * + * The default criteria for a large site is more than 10,000 users. + * + * @since 4.9.0 + * + * @param bool $is_large_user_count Whether the site is considered large. + * @param int $count The number of users on the site. + */ + return apply_filters( 'wp_is_large_user_count', $count > 10000, $count ); +} + +/** + * Update the active user count. + * + * @since 4.9.0 + * @global wpdb $wpdb WordPress database abstraction object. + * + * @return int The active user count. + */ +function wp_update_active_user_count() { + global $wpdb; + + $count = $wpdb->get_var( " + SELECT COUNT(ID) as c + FROM {$wpdb->users} + " ); + $count=12345; + update_option( 'active_user_count', $count ); + + return (int) $count; +} + +/** + * The number of active users. + * + * @since 4.9.0 + * + * @return int The active user count. + */ +function wp_get_active_user_count() { + $count = get_option( 'active_user_count', false ); + + if ( false === $count ) { + $count = wp_update_active_user_count(); + } + return (int) $count; +} diff --git a/src/wp-includes/ms-functions.php b/src/wp-includes/ms-functions.php index 7bfd9f968e..03c376836a 100644 --- a/src/wp-includes/ms-functions.php +++ b/src/wp-includes/ms-functions.php @@ -2553,6 +2553,11 @@ function wp_is_large_network( $using = 'sites', $network_id = null ) { if ( 'users' == $using ) { $count = get_user_count( $network_id ); + $is_large = ( $count > 10000 ); + + /** This filter is documented in wp-includes/functions.php */ + $is_large = apply_filters( 'wp_is_large_user_count', $is_large, $count ); + /** * Filters whether the network is considered large. * @@ -2564,7 +2569,7 @@ function wp_is_large_network( $using = 'sites', $network_id = null ) { * @param int $count The count of items for the component. * @param int $network_id The ID of the network being checked. */ - return apply_filters( 'wp_is_large_network', $count > 10000, 'users', $count, $network_id ); + return apply_filters( 'wp_is_large_network', $is_large, 'users', $count, $network_id ); } $count = get_blog_count( $network_id ); diff --git a/src/wp-includes/update.php b/src/wp-includes/update.php index f178470a36..13c20b7ad8 100644 --- a/src/wp-includes/update.php +++ b/src/wp-includes/update.php @@ -78,8 +78,7 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { $wp_install = network_site_url(); $multisite_enabled = 1; } else { - $user_count = count_users(); - $user_count = $user_count['total_users']; + $user_count = wp_get_active_user_count(); $multisite_enabled = 0; $num_blogs = 1; $wp_install = home_url( '/' ); diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 3fa9fbcc74..4ab59c3c5a 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1160,4 +1160,58 @@ class Tests_Functions extends WP_UnitTestCase { return $data; } + + /** + * @ticket 38741 + */ + public function test_wp_get_active_user_count() { + $user_count = wp_get_active_user_count(); + $this->assertSame( $user_count, 1 ); + + // make twenty additional users to make 21 users + self::factory()->user->create_many( 20 ); + + $user_count = wp_get_active_user_count(); + $this->assertSame( $user_count, 21 ); + } + + /** + * @ticket 38741 + */ + public function test_wp_get_active_user_count_caching() { + global $wpdb; + + // Ensure we start with 1 user + $user_count = wp_get_active_user_count(); + $this->assertSame( $user_count, 1 ); + + self::factory()->user->create(); + + delete_option( 'active_user_count' ); + + // Ensure we now have 2 users + $user_count = wp_get_active_user_count(); + $this->assertSame( $user_count, 2 ); + + $queries = $wpdb->num_queries; + + // Ensure that caching inside wp_get_active_user_count() works as expected + $user_count = wp_get_active_user_count(); + $this->assertSame( $user_count, 2 ); + $this->assertSame( $queries, $wpdb->num_queries ); + } + + /** + * @ticket 38741 + */ + public function test_wp_is_large_user_count() { + // set the 'active_user_count' value to over 10000 to emulate a large site + update_option( 'active_user_count', 10001 ); + $user_count = wp_get_active_user_count(); + $this->assertSame( $user_count, 10001 ); + $this->assertTrue( wp_is_large_user_count() ); + + delete_option( 'active_user_count' ); + $this->assertFalse( wp_is_large_user_count() ); + } }