Skip to content

Commit 83b9f50

Browse files
authored
Ignore deactivated subsites (#253)
1 parent 6892c00 commit 83b9f50

File tree

4 files changed

+146
-34
lines changed

4 files changed

+146
-34
lines changed

__tests__/bootstrap.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,19 @@ function _manually_load_plugin() {
4545

4646
// Start up the WP testing environment.
4747
require $_tests_dir . '/includes/bootstrap.php';
48+
49+
// Setup WP CLI depedencies.
50+
if ( ! defined( 'WP_CLI_ROOT' ) ) {
51+
define( 'WP_CLI_ROOT', __DIR__ . '/../vendor/wp-cli/wp-cli' );
52+
}
53+
54+
include WP_CLI_ROOT . '/php/utils.php';
55+
include WP_CLI_ROOT . '/php/dispatcher.php';
56+
include WP_CLI_ROOT . '/php/class-wp-cli.php';
57+
include WP_CLI_ROOT . '/php/class-wp-cli-command.php';
58+
59+
\WP_CLI\Utils\load_dependencies();
60+
61+
// WP_CLI wasn't defined during plugin bootup, so bootstrap our cli classes manually
62+
require dirname( dirname( __FILE__ ) ) . '/includes/wp-cli.php';
63+
Cron_Control\CLI\prepare_environment();
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace Automattic\WP\Cron_Control\Tests;
4+
5+
use Automattic\WP\Cron_Control\CLI;
6+
use WP_Site;
7+
8+
class Orchestrate_Sites_Tests extends \WP_UnitTestCase {
9+
function setUp() {
10+
if ( ! is_multisite() ) {
11+
$this->markTestSkipped( 'Skipping tests that only run on multisites.' );
12+
}
13+
14+
parent::setUp();
15+
}
16+
17+
function tearDown() {
18+
parent::tearDown();
19+
}
20+
21+
function test_list_sites_removes_inactive_subsites() {
22+
add_filter( 'sites_pre_query', [ $this, 'mock_get_sites' ], 10, 2 );
23+
24+
// The archived/spam/deleted subsites should not be returned.
25+
$expected = wp_json_encode( [ [ 'url' => 'site1.com' ], [ 'url' => 'site2.com/two' ], [ 'url' => 'site3.com/three' ], [ 'url' => 'site7.com/seven' ] ] );
26+
$this->expectOutputString( $expected );
27+
( new CLI\Orchestrate_Sites() )->list();
28+
29+
remove_filter( 'sites_pre_query', [ $this, 'mock_get_sites' ], 10, 2 );
30+
}
31+
32+
function test_list_sites_2_hosts() {
33+
add_filter( 'sites_pre_query', [ $this, 'mock_get_sites' ], 10, 2 );
34+
35+
// With two hosts, all active sites should still be returned.
36+
$this->mock_hosts_list( 2 );
37+
$expected = wp_json_encode( [ [ 'url' => 'site1.com' ], [ 'url' => 'site2.com/two' ], [ 'url' => 'site3.com/three' ], [ 'url' => 'site7.com/seven' ] ] );
38+
$this->expectOutputString( $expected );
39+
( new CLI\Orchestrate_Sites() )->list();
40+
41+
remove_filter( 'sites_pre_query', [ $this, 'mock_get_sites' ], 10, 2 );
42+
}
43+
44+
function test_list_sites_7_hosts() {
45+
add_filter( 'sites_pre_query', [ $this, 'mock_get_sites' ], 10, 2 );
46+
47+
// With seven hosts, our current request should only be given two of the active sites.
48+
$this->mock_hosts_list( 7 );
49+
$expected = wp_json_encode( [ [ 'url' => 'site1.com' ], [ 'url' => 'site7.com/seven' ] ] );
50+
$this->expectOutputString( $expected );
51+
( new CLI\Orchestrate_Sites() )->list();
52+
53+
remove_filter( 'sites_pre_query', [ $this, 'mock_get_sites' ], 10, 2 );
54+
}
55+
56+
function mock_hosts_list( $number_of_hosts ) {
57+
// Always have the "current" host.
58+
$heartbeats = [ gethostname() => time() ];
59+
60+
if ( $number_of_hosts > 1 ) {
61+
for ( $i = 1; $i < $number_of_hosts; $i++ ) {
62+
$heartbeats[ "test_$i" ] = time();
63+
}
64+
}
65+
66+
wp_cache_set( CLI\Orchestrate_Sites::RUNNER_HOST_HEARTBEAT_KEY, $heartbeats );
67+
}
68+
69+
function mock_get_sites( $site_data, $query_class ) {
70+
if ( $query_class->query_vars['count'] ) {
71+
return 7;
72+
}
73+
74+
return [
75+
new WP_Site( (object) [ 'domain' => 'site1.com', 'path' => '/' ] ),
76+
new WP_Site( (object) [ 'domain' => 'site2.com', 'path' => '/two' ] ),
77+
new WP_Site( (object) [ 'domain' => 'site3.com', 'path' => '/three' ] ),
78+
new WP_Site( (object) [ 'domain' => 'site4.com', 'path' => '/four', 'archived' => '1' ] ),
79+
new WP_Site( (object) [ 'domain' => 'site5.com', 'path' => '/five', 'spam' => '1' ] ),
80+
new WP_Site( (object) [ 'domain' => 'site6.com', 'path' => '/six', 'deleted' => '1' ] ),
81+
new WP_Site( (object) [ 'domain' => 'site7.com', 'path' => '/seven' ] ),
82+
];
83+
}
84+
}

includes/wp-cli/class-orchestrate-sites.php

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
namespace Automattic\WP\Cron_Control\CLI;
1111

12-
1312
/**
1413
* Commands used by the Go-based runner to list sites
1514
*/
@@ -61,44 +60,40 @@ public function list() {
6160
* @param int $group Group number.
6261
*/
6362
private function display_sites( $num_groups = 1, $group = 0 ) {
64-
$sites = get_sites( [ 'number' => 10000 ] );
65-
66-
// Only return sites in our group.
67-
$sites = array_filter(
68-
$sites,
69-
function( $id ) use ( $num_groups, $group ) {
70-
return $id % $num_groups === $group;
71-
},
72-
ARRAY_FILTER_USE_KEY
73-
);
63+
$site_count = get_sites( [ 'count' => 1 ] );
64+
if ( $site_count > 10000 ) {
65+
trigger_error( 'Cron-Control: This multisite has more than 10000 subsites, currently unsupported.', E_USER_WARNING );
66+
}
7467

75-
// Add the site URL to each site object.
76-
$sites = array_map(
77-
function( $site ) {
78-
// Don't use home or siteurl since those don't always match up with the `wp_blogs` entry.
79-
// That can result in a "site not found" when passed via the `--url` WP-CLI param.
80-
// Instead, construct the URL from data in the `wp_blogs` table.
81-
$domain = $site->domain;
82-
83-
$path = '';
84-
if ( $site->path && '/' !== $site->path ) {
85-
$path = $site->path;
86-
}
68+
// Keep the query simple, then process the results.
69+
$all_sites = get_sites( [ 'number' => 10000 ] );
70+
$sites_to_display = [];
71+
foreach ( $all_sites as $index => $site ) {
72+
if ( ! ( $index % $num_groups === $group ) ) {
73+
// The site does not belong to this group.
74+
continue;
75+
}
8776

88-
$site->url = sprintf( '%s%s', $domain, $path );
77+
if ( in_array( '1', array( $site->archived, $site->spam, $site->deleted ), true ) ) {
78+
// Deactivated subsites don't need cron run on them.
79+
continue;
80+
}
8981

90-
return $site;
91-
},
92-
$sites
93-
);
82+
// We just need the url to display.
83+
$sites_to_display[] = [ 'url' => $this->get_raw_site_url( $site->path, $site->domain ) ];
84+
}
9485

95-
$assoc_args = [
96-
'fields' => 'url',
97-
'format' => 'json',
98-
];
86+
\WP_CLI\Utils\format_items( 'json', $sites_to_display, 'url' );
87+
}
9988

100-
$formatter = new \WP_CLI\Formatter( $assoc_args, null, 'site' );
101-
$formatter->display_items( $sites );
89+
/**
90+
* We can't use the home or siteurl since those don't always match with the `wp_blogs` entry.
91+
* And that can lead to "site not found" errors when passed via the `--url` WP-CLI param.
92+
* Instead, we construct the URL from data in the `wp_blogs` table.
93+
*/
94+
private function get_raw_site_url( string $site_path, string $site_domain ): string {
95+
$path = ( $site_path && '/' !== $site_path ) ? $site_path : '';
96+
return $site_domain . $path;
10297
}
10398

10499
/**

phpunit-multisite.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<phpunit
2+
bootstrap="__tests__/bootstrap.php"
3+
backupGlobals="false"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true"
8+
>
9+
<php>
10+
<const name="WP_TESTS_MULTISITE" value="1" />
11+
</php>
12+
<testsuites>
13+
<testsuite name="Unit Tests">
14+
<directory prefix="test-" suffix=".php">./__tests__/unit-tests/</directory>
15+
</testsuite>
16+
</testsuites>
17+
</phpunit>

0 commit comments

Comments
 (0)