From 7793e670b87ae9126b8ab45836851d1f9108a6dc Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 22 Mar 2019 17:25:38 +0000 Subject: [PATCH] Multisite: add new `sites_pre_query` and `networks_pre_query` filters to short circuit WP_Site_Query and WP_Network_Query queries. Similar to the `posts_pre_query` filter for WP_Query added in #36687. These filters lets you short circuit the queries to return your own results. Add a new filter `sites_pre_query` - which returns null by default. Return a non-null value to bypass WordPress's default `get_sites` queries. Developers should note that filtering functions that require pagination information are encouraged to set the `found_sites` property of the `WP_Site_Query` object, passed to the filter by reference. If `WP_Site_Query` does not perform a database query, it will not have enough information to generate these values itself. Add a new filter `networks_pre_query` - which returns null by default. Return a non-null value to bypass WordPress's default `get_networks` queries. Developers should note that filtering functions that require pagination information are encouraged to set the `found_networks` property of the `WP_Network_Query` object, passed to the filter by reference. If `WP_Network_Query` does not perform a database query, it will not have enough information to generate these values itself. Props spacedmonkey. Fixes #45749. git-svn-id: https://develop.svn.wordpress.org/trunk@44983 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-network-query.php | 61 ++++++++++++------- src/wp-includes/class-wp-site-query.php | 61 ++++++++++++------- .../phpunit/tests/multisite/networkQuery.php | 35 +++++++++++ tests/phpunit/tests/multisite/siteQuery.php | 36 +++++++++++ 4 files changed, 151 insertions(+), 42 deletions(-) diff --git a/src/wp-includes/class-wp-network-query.php b/src/wp-includes/class-wp-network-query.php index d381e60117..13c6cd094a 100644 --- a/src/wp-includes/class-wp-network-query.php +++ b/src/wp-includes/class-wp-network-query.php @@ -197,32 +197,51 @@ class WP_Network_Query { */ do_action_ref_array( 'pre_get_networks', array( &$this ) ); - // $args can include anything. Only use the args defined in the query_var_defaults to compute the key. - $_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ); + $network_ids = null; - // Ignore the $fields argument as the queried result will be the same regardless. - unset( $_args['fields'] ); + /** + * Filter the sites array before the query takes place. + * + * Return a non-null value to bypass WordPress's default site queries. + * + * + * @since 5.2.0 + * + * @param array|null $site_ids Return an array of site data to short-circuit WP's site query, + * or null to allow WP to run its normal queries. + * @param WP_Network_Query $this The WP_Network_Query instance, passed by reference. + */ + $network_ids = apply_filters_ref_array( 'networks_pre_query', array( $network_ids, &$this ) ); - $key = md5( serialize( $_args ) ); - $last_changed = wp_cache_get_last_changed( 'networks' ); + if ( null === $network_ids ) { - $cache_key = "get_network_ids:$key:$last_changed"; - $cache_value = wp_cache_get( $cache_key, 'networks' ); + // $args can include anything. Only use the args defined in the query_var_defaults to compute the key. + $_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ); - if ( false === $cache_value ) { - $network_ids = $this->get_network_ids(); - if ( $network_ids ) { - $this->set_found_networks(); + // Ignore the $fields argument as the queried result will be the same regardless. + unset( $_args['fields'] ); + + $key = md5( serialize( $_args ) ); + $last_changed = wp_cache_get_last_changed( 'networks' ); + + $cache_key = "get_network_ids:$key:$last_changed"; + $cache_value = wp_cache_get( $cache_key, 'networks' ); + + if ( false === $cache_value ) { + $network_ids = $this->get_network_ids(); + if ( $network_ids ) { + $this->set_found_networks(); + } + + $cache_value = array( + 'network_ids' => $network_ids, + 'found_networks' => $this->found_networks, + ); + wp_cache_add( $cache_key, $cache_value, 'networks' ); + } else { + $network_ids = $cache_value['network_ids']; + $this->found_networks = $cache_value['found_networks']; } - - $cache_value = array( - 'network_ids' => $network_ids, - 'found_networks' => $this->found_networks, - ); - wp_cache_add( $cache_key, $cache_value, 'networks' ); - } else { - $network_ids = $cache_value['network_ids']; - $this->found_networks = $cache_value['found_networks']; } if ( $this->found_networks && $this->query_vars['number'] ) { diff --git a/src/wp-includes/class-wp-site-query.php b/src/wp-includes/class-wp-site-query.php index bc3e3519a3..507c611e24 100644 --- a/src/wp-includes/class-wp-site-query.php +++ b/src/wp-includes/class-wp-site-query.php @@ -288,32 +288,51 @@ class WP_Site_Query { $this->meta_query_clauses = $this->meta_query->get_sql( 'blog', $wpdb->blogs, 'blog_id', $this ); } - // $args can include anything. Only use the args defined in the query_var_defaults to compute the key. - $_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ); + $site_ids = null; - // Ignore the $fields argument as the queried result will be the same regardless. - unset( $_args['fields'] ); + /** + * Filter the sites array before the query takes place. + * + * Return a non-null value to bypass WordPress's default site queries. + * + * + * @since 5.2.0 + * + * @param array|null $site_ids Return an array of site data to short-circuit WP's site query, + * or null to allow WP to run its normal queries. + * @param WP_Site_Query $this The WP_Site_Query instance, passed by reference. + */ + $site_ids = apply_filters_ref_array( 'sites_pre_query', array( $site_ids, &$this ) ); - $key = md5( serialize( $_args ) ); - $last_changed = wp_cache_get_last_changed( 'sites' ); + if ( null === $site_ids ) { - $cache_key = "get_sites:$key:$last_changed"; - $cache_value = wp_cache_get( $cache_key, 'sites' ); + // $args can include anything. Only use the args defined in the query_var_defaults to compute the key. + $_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ); - if ( false === $cache_value ) { - $site_ids = $this->get_site_ids(); - if ( $site_ids ) { - $this->set_found_sites(); + // Ignore the $fields argument as the queried result will be the same regardless. + unset( $_args['fields'] ); + + $key = md5( serialize( $_args ) ); + $last_changed = wp_cache_get_last_changed( 'sites' ); + + $cache_key = "get_sites:$key:$last_changed"; + $cache_value = wp_cache_get( $cache_key, 'sites' ); + + if ( false === $cache_value ) { + $site_ids = $this->get_site_ids(); + if ( $site_ids ) { + $this->set_found_sites(); + } + + $cache_value = array( + 'site_ids' => $site_ids, + 'found_sites' => $this->found_sites, + ); + wp_cache_add( $cache_key, $cache_value, 'sites' ); + } else { + $site_ids = $cache_value['site_ids']; + $this->found_sites = $cache_value['found_sites']; } - - $cache_value = array( - 'site_ids' => $site_ids, - 'found_sites' => $this->found_sites, - ); - wp_cache_add( $cache_key, $cache_value, 'sites' ); - } else { - $site_ids = $cache_value['site_ids']; - $this->found_sites = $cache_value['found_sites']; } if ( $this->found_sites && $this->query_vars['number'] ) { diff --git a/tests/phpunit/tests/multisite/networkQuery.php b/tests/phpunit/tests/multisite/networkQuery.php index 6ec7f16c42..0b48fdc8d5 100644 --- a/tests/phpunit/tests/multisite/networkQuery.php +++ b/tests/phpunit/tests/multisite/networkQuery.php @@ -522,6 +522,41 @@ if ( is_multisite() ) : ); $this->assertEquals( $number_of_queries + 1, $wpdb->num_queries ); } + + /** + * @ticket 45749 + */ + public function test_networks_pre_query_filter_should_bypass_database_query() { + global $wpdb; + + add_filter( 'networks_pre_query', array( __CLASS__, 'filter_networks_pre_query' ), 10, 2 ); + + $num_queries = $wpdb->num_queries; + + $q = new WP_Network_Query(); + $results = $q->query( + array( + 'fields' => 'ids', + ) + ); + + remove_filter( 'networks_pre_query', array( __CLASS__, 'filter_networks_pre_query' ), 10, 2 ); + + // Make sure no queries were executed. + $this->assertSame( $num_queries, $wpdb->num_queries ); + + // We manually inserted a non-existing site and overrode the results with it. + $this->assertSame( array( 555 ), $q->networks ); + + // Make sure manually setting total_users doesn't get overwritten. + $this->assertEquals( 1, $q->found_networks ); + } + + public static function filter_networks_pre_query( $networks, $query ) { + $query->found_networks = 1; + + return array( 555 ); + } } endif; diff --git a/tests/phpunit/tests/multisite/siteQuery.php b/tests/phpunit/tests/multisite/siteQuery.php index bac9269ff4..c17f977932 100644 --- a/tests/phpunit/tests/multisite/siteQuery.php +++ b/tests/phpunit/tests/multisite/siteQuery.php @@ -911,6 +911,42 @@ if ( is_multisite() ) : } } + + /** + * @ticket 45749 + */ + public function test_sites_pre_query_filter_should_bypass_database_query() { + global $wpdb; + + add_filter( 'sites_pre_query', array( __CLASS__, 'filter_sites_pre_query' ), 10, 2 ); + + $num_queries = $wpdb->num_queries; + + $q = new WP_Site_Query(); + $results = $q->query( + array( + 'fields' => 'ids', + ) + ); + + remove_filter( 'sites_pre_query', array( __CLASS__, 'filter_sites_pre_query' ), 10, 2 ); + + // Make sure no queries were executed. + $this->assertSame( $num_queries, $wpdb->num_queries ); + + // We manually inserted a non-existing site and overrode the results with it. + $this->assertSame( array( 555 ), $q->sites ); + + // Make sure manually setting total_users doesn't get overwritten. + $this->assertEquals( 1, $q->found_sites ); + } + + public static function filter_sites_pre_query( $sites, $query ) { + $query->found_sites = 1; + + return array( 555 ); + } + public function data_wp_site_query_meta_query() { return array( array(