From e2e15f18768613378b14eba5c595e1994380aa7b Mon Sep 17 00:00:00 2001 From: spacedmonkey Date: Tue, 29 Mar 2022 12:42:13 +0000 Subject: [PATCH] Users: Introduce the concept of a large site to single site installations. Currently in WordPress multisite there is a concept of large networks. The function `wp_is_large_network` is used to determine if a network has a large number of sites or users. If a network is marked as large, then expensive queries to calculate user counts are not run on page load but deferred to scheduled events. However there are a number of places in a single site installation where this functionality would also be useful, as expensive calls to count users and roles can make screens in the admin extremely slow. In this change, the `get_user_count` function and related functionality around it is ported to be available in a single site context. This means that expensive calls to the `count_users` function are replaced with calls to `get_user_count`. This change also includes a new function called `wp_is_large_user_count` and a filter of the same name, to mark if a site is large. Props johnbillion, Spacedmonkey, Mista-Flo, lumpysimon, tharsheblows, obenland, miss_jwo, jrchamp, flixos90, macbookandrew, pento, desrosj, johnjamesjacoby, jb510, davidbaumwald, costdev. Fixes #38741. Built from https://develop.svn.wordpress.org/trunk@53011 git-svn-id: http://core.svn.wordpress.org/trunk@52600 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/includes/class-wp-debug-data.php | 17 +-- .../includes/class-wp-posts-list-table.php | 2 +- .../includes/class-wp-users-list-table.php | 57 +++++---- wp-admin/includes/schema.php | 1 + wp-admin/includes/upgrade.php | 20 ++++ wp-includes/default-filters.php | 7 ++ wp-includes/functions.php | 110 ++++++++++++++++++ wp-includes/ms-default-filters.php | 7 +- wp-includes/ms-functions.php | 28 +---- wp-includes/update.php | 5 +- wp-includes/version.php | 2 +- 11 files changed, 191 insertions(+), 65 deletions(-) diff --git a/wp-admin/includes/class-wp-debug-data.php b/wp-admin/includes/class-wp-debug-data.php index db34024e4f..3d4e664ed7 100644 --- a/wp-admin/includes/class-wp-debug-data.php +++ b/wp-admin/includes/class-wp-debug-data.php @@ -388,11 +388,6 @@ class WP_Debug_Data { $site_count += get_blog_count( $network_id ); } - $info['wp-core']['fields']['user_count'] = array( - 'label' => __( 'User count' ), - 'value' => get_user_count(), - ); - $info['wp-core']['fields']['site_count'] = array( 'label' => __( 'Site count' ), 'value' => $site_count, @@ -402,15 +397,13 @@ class WP_Debug_Data { 'label' => __( 'Network count' ), 'value' => $network_query->found_networks, ); - } else { - $user_count = count_users(); - - $info['wp-core']['fields']['user_count'] = array( - 'label' => __( 'User count' ), - 'value' => $user_count['total_users'], - ); } + $info['wp-core']['fields']['user_count'] = array( + 'label' => __( 'User count' ), + 'value' => get_user_count(), + ); + // WordPress features requiring processing. $wp_dotorg = wp_remote_get( 'https://wordpress.org', array( 'timeout' => 10 ) ); diff --git a/wp-admin/includes/class-wp-posts-list-table.php b/wp-admin/includes/class-wp-posts-list-table.php index 96b3adb06b..7fd4bf89ac 100644 --- a/wp-admin/includes/class-wp-posts-list-table.php +++ b/wp-admin/includes/class-wp-posts-list-table.php @@ -1653,7 +1653,7 @@ class WP_Posts_List_Table extends WP_List_Table { post_type, 'author' ) ) { + if ( post_type_supports( $screen->post_type, 'author' ) && ! wp_is_large_user_count() ) { $authors_dropdown = ''; if ( current_user_can( $post_type_object->cap->edit_others_posts ) ) { diff --git a/wp-admin/includes/class-wp-users-list-table.php b/wp-admin/includes/class-wp-users-list-table.php index c01945e2ea..1955e09fbc 100644 --- a/wp-admin/includes/class-wp-users-list-table.php +++ b/wp-admin/includes/class-wp-users-list-table.php @@ -177,28 +177,33 @@ class WP_Users_List_Table extends WP_List_Table { $wp_roles = wp_roles(); + $count_users = ! wp_is_large_user_count(); + 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(); } else { - $url = 'users.php'; - $users_of_blog = count_users(); + $url = 'users.php'; } - $total_users = $users_of_blog['total_users']; - $avail_roles =& $users_of_blog['avail_roles']; - unset( $users_of_blog ); - + $role_links = array(); + $avail_roles = array(); + $all_text = __( 'All' ); $current_link_attributes = empty( $role ) ? ' class="current" aria-current="page"' : ''; - $role_links = array(); - $role_links['all'] = sprintf( - '%s', - $url, - $current_link_attributes, - sprintf( + if ( $count_users ) { + if ( $this->is_site_users ) { + switch_to_blog( $this->site_id ); + $users_of_blog = count_users( 'time', $this->site_id ); + restore_current_blog(); + } else { + $users_of_blog = count_users(); + } + + $total_users = $users_of_blog['total_users']; + $avail_roles =& $users_of_blog['avail_roles']; + unset( $users_of_blog ); + + $all_text = sprintf( /* translators: %s: Number of users. */ _nx( 'All (%s)', @@ -207,11 +212,13 @@ class WP_Users_List_Table extends WP_List_Table { 'users' ), number_format_i18n( $total_users ) - ) - ); + ); + } + + $role_links['all'] = sprintf( '%s', $url, $current_link_attributes, $all_text ); foreach ( $wp_roles->get_names() as $this_role => $name ) { - if ( ! isset( $avail_roles[ $this_role ] ) ) { + if ( $count_users && ! isset( $avail_roles[ $this_role ] ) ) { continue; } @@ -222,12 +229,14 @@ class WP_Users_List_Table extends WP_List_Table { } $name = translate_user_role( $name ); - $name = sprintf( - /* translators: 1: User role name, 2: Number of users. */ - __( '%1$s (%2$s)' ), - $name, - number_format_i18n( $avail_roles[ $this_role ] ) - ); + if ( $count_users ) { + $name = sprintf( + /* translators: 1: User role name, 2: Number of users. */ + __( '%1$s (%2$s)' ), + $name, + number_format_i18n( $avail_roles[ $this_role ] ) + ); + } $role_links[ $this_role ] = "$name"; } diff --git a/wp-admin/includes/schema.php b/wp-admin/includes/schema.php index d7728e2dff..89ca9d8b30 100644 --- a/wp-admin/includes/schema.php +++ b/wp-admin/includes/schema.php @@ -1267,6 +1267,7 @@ We hope you enjoy your new site. Thanks! 'subdomain_install' => $subdomain_install, 'global_terms_enabled' => global_terms_enabled() ? '1' : '0', 'ms_files_rewriting' => is_multisite() ? get_site_option( 'ms_files_rewriting' ) : '0', + 'user_count' => get_site_option( 'user_count' ), 'initial_db_version' => get_option( 'initial_db_version' ), 'active_sitewide_plugins' => array(), 'WPLANG' => get_locale(), diff --git a/wp-admin/includes/upgrade.php b/wp-admin/includes/upgrade.php index 88900bc400..5e48437434 100644 --- a/wp-admin/includes/upgrade.php +++ b/wp-admin/includes/upgrade.php @@ -845,6 +845,10 @@ function upgrade_all() { upgrade_590(); } + if ( $wp_current_db_version < 53011 ) { + upgrade_600(); + } + maybe_disable_link_manager(); maybe_disable_automattic_widgets(); @@ -2282,6 +2286,22 @@ function upgrade_590() { } } +/** + * Executes changes made in WordPress 6.0.0. + * + * @ignore + * @since 6.0.0 + * + * @global int $wp_current_db_version The old (current) database version. + */ +function upgrade_600() { + global $wp_current_db_version; + + if ( $wp_current_db_version < 53011 ) { + wp_update_user_counts(); + } +} + /** * Executes network-level upgrade routines. * diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index 182f94eddf..124b1c2440 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -98,6 +98,13 @@ add_filter( 'post_mime_type', 'sanitize_mime_type' ); // Meta. add_filter( 'register_meta_args', '_wp_register_meta_args_allowed_list', 10, 2 ); +// Counts. +add_action( 'admin_init', 'wp_schedule_update_user_counts' ); +add_action( 'wp_update_user_counts', 'wp_schedule_update_user_counts', 10, 0 ); +foreach ( array( 'user_register', 'deleted_user' ) as $action ) { + add_action( $action, 'wp_maybe_update_user_counts', 10, 0 ); +} + // Post meta. add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' ); add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' ); diff --git a/wp-includes/functions.php b/wp-includes/functions.php index 0e0f626e56..da5c6d3752 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -8418,3 +8418,113 @@ function is_php_version_compatible( $required ) { function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { return abs( (float) $expected - (float) $actual ) <= $precision; } + +/** + * Returns the number of active users in your installation. + * + * Note that on a large site the count may be cached and only updated twice daily. + * + * @since MU (3.0.0) + * @since 4.8.0 The `$network_id` parameter has been added. + * @since 6.0.0 Move to wp-includes/functions.php. + * + * @param int|null $network_id ID of the network. Default is the current network. + * @return int Number of active users on the network. + */ +function get_user_count( $network_id = null ) { + if ( ! is_multisite() && null !== $network_id ) { + _doing_it_wrong( __FUNCTION__, __( 'Unable to pass $network_id if not using multisite.' ), '6.0.0' ); + } + return (int) get_network_option( $network_id, 'user_count', -1 ); +} + +/** + * Updates the total count of users on the site if live user counting is enabled. + * + * @since 6.0.0 + * + * @param int|null $network_id ID of the network. Default is the current network. + * @return bool Whether the update was successful. + */ +function wp_maybe_update_user_counts( $network_id = null ) { + if ( ! is_multisite() && null !== $network_id ) { + _doing_it_wrong( __FUNCTION__, __( 'Unable to pass $network_id if not using multisite.' ), '6.0.0' ); + } + + $is_small_network = ! wp_is_large_user_count( $network_id ); + /** This filter is documented in wp-includes/ms-functions.php */ + if ( ! apply_filters( 'enable_live_network_counts', $is_small_network, 'users' ) ) { + return false; + } + + return wp_update_user_counts( $network_id ); +} + +/** + * Updates the total count of users on the site. + * + * @global wpdb $wpdb WordPress database abstraction object. + * @since 6.0.0 + * + * @param int|null $network_id ID of the network. Default is the current network. + * @return bool Whether the update was successful. + */ +function wp_update_user_counts( $network_id = null ) { + global $wpdb; + + if ( ! is_multisite() && null !== $network_id ) { + _doing_it_wrong( __FUNCTION__, __( 'Unable to pass $network_id if not using multisite.' ), '6.0.0' ); + } + + $query = "SELECT COUNT(ID) as c FROM $wpdb->users"; + if ( is_multisite() ) { + $query .= " WHERE spam = '0' AND deleted = '0'"; + } + + $count = $wpdb->get_var( $query ); + + return update_network_option( $network_id, 'user_count', $count ); +} + +/** + * Schedules a recurring recalculation of the total count of users. + * + * @since 6.0.0 + */ +function wp_schedule_update_user_counts() { + if ( ! is_main_site() ) { + return; + } + + if ( ! wp_next_scheduled( 'wp_update_user_counts' ) && ! wp_installing() ) { + wp_schedule_event( time(), 'twicedaily', 'wp_update_user_counts' ); + } +} + +/** + * Determines whether the site has a large number of users. + * + * The default criteria for a large site is more than 10,000 users. + * + * @since 6.0.0 + * + * @param int|null $network_id ID of the network. Default is the current network. + * @return bool Whether the site has a large number of users. + */ +function wp_is_large_user_count( $network_id = null ) { + if ( ! is_multisite() && null !== $network_id ) { + _doing_it_wrong( __FUNCTION__, __( 'Unable to pass $network_id if not using multisite.' ), '6.0.0' ); + } + $count = get_user_count( $network_id ); + + /** + * Filters whether the site is considered large, based on its number of users. + * + * @since 6.0.0 + * + * @param bool $is_large_user_count Whether the site has a large number of users. + * @param int $count The total number of users. + * @param int|null $network_id ID of the network. `null` represents the current network. + */ + return apply_filters( 'wp_is_large_user_count', $count > 10000, $count, $network_id ); +} diff --git a/wp-includes/ms-default-filters.php b/wp-includes/ms-default-filters.php index 1b32f7a32b..3411e7dba0 100644 --- a/wp-includes/ms-default-filters.php +++ b/wp-includes/ms-default-filters.php @@ -84,9 +84,14 @@ add_action( 'transition_post_status', '_update_posts_count_on_transition_post_st // Counts. add_action( 'admin_init', 'wp_schedule_update_network_counts' ); add_action( 'update_network_counts', 'wp_update_network_counts', 10, 0 ); -foreach ( array( 'user_register', 'deleted_user', 'wpmu_new_user', 'make_spam_user', 'make_ham_user' ) as $action ) { +foreach ( array( 'wpmu_new_user', 'make_spam_user', 'make_ham_user' ) as $action ) { add_action( $action, 'wp_maybe_update_network_user_counts', 10, 0 ); } + +// These counts are handled by wp_update_network_counts() on Multisite: +remove_action( 'admin_init', 'wp_schedule_update_user_counts' ); +remove_action( 'wp_update_user_counts', 'wp_schedule_update_user_counts' ); + foreach ( array( 'make_spam_blog', 'make_ham_blog', 'archive_blog', 'unarchive_blog', 'make_delete_blog', 'make_undelete_blog' ) as $action ) { add_action( $action, 'wp_maybe_update_network_site_counts', 10, 0 ); } diff --git a/wp-includes/ms-functions.php b/wp-includes/ms-functions.php index 2059e6da7d..9e99f7f1a6 100644 --- a/wp-includes/ms-functions.php +++ b/wp-includes/ms-functions.php @@ -100,21 +100,6 @@ function get_active_blog_for_user( $user_id ) { } } -/** - * The number of active users in your installation. - * - * The count is cached and updated twice daily. This is not a live count. - * - * @since MU (3.0.0) - * @since 4.8.0 The `$network_id` parameter has been added. - * - * @param int|null $network_id ID of the network. Default is the current network. - * @return int Number of active users on the network. - */ -function get_user_count( $network_id = null ) { - return get_network_option( $network_id, 'user_count' ); -} - /** * The number of active sites on your installation. * @@ -2611,16 +2596,12 @@ function wp_update_network_site_counts( $network_id = null ) { * * @since 3.7.0 * @since 4.8.0 The `$network_id` parameter has been added. - * - * @global wpdb $wpdb WordPress database abstraction object. + * @since 6.0.0 This function is now a wrapper for wp_update_user_counts(). * * @param int|null $network_id ID of the network. Default is the current network. */ function wp_update_network_user_counts( $network_id = null ) { - global $wpdb; - - $count = $wpdb->get_var( "SELECT COUNT(ID) as c FROM $wpdb->users WHERE spam = '0' AND deleted = '0'" ); - update_network_option( $network_id, 'user_count', $count ); + wp_update_user_counts( $network_id ); } /** @@ -2754,6 +2735,9 @@ function wp_is_large_network( $using = 'sites', $network_id = null ) { if ( 'users' === $using ) { $count = get_user_count( $network_id ); + + $is_large_network = wp_is_large_user_count( $network_id ); + /** * Filters whether the network is considered large. * @@ -2765,7 +2749,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_network, 'users', $count, $network_id ); } $count = get_blog_count( $network_id ); diff --git a/wp-includes/update.php b/wp-includes/update.php index e30da7358e..a18a81e477 100644 --- a/wp-includes/update.php +++ b/wp-includes/update.php @@ -80,13 +80,10 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { } if ( is_multisite() ) { - $user_count = get_user_count(); $num_blogs = get_blog_count(); $wp_install = network_site_url(); $multisite_enabled = 1; } else { - $user_count = count_users(); - $user_count = $user_count['total_users']; $multisite_enabled = 0; $num_blogs = 1; $wp_install = home_url( '/' ); @@ -99,7 +96,7 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { 'mysql' => $mysql_version, 'local_package' => isset( $wp_local_package ) ? $wp_local_package : '', 'blogs' => $num_blogs, - 'users' => $user_count, + 'users' => get_user_count(), 'multisite_enabled' => $multisite_enabled, 'initial_db_version' => get_site_option( 'initial_db_version' ), ); diff --git a/wp-includes/version.php b/wp-includes/version.php index 0bd2060604..a2194e750e 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.0-alpha-53010'; +$wp_version = '6.0-alpha-53011'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.