Completed
Push — fix/sync-serialize-closures ( 75bbb7...de3065 )
by
unknown
171:05 queued 161:14
created

test_class.jetpack-sync-callables.php ➔ jetpack_foo_is_anon_callable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
4
use Automattic\Jetpack\Blocks;
5
use Automattic\Jetpack\Constants;
6
use Automattic\Jetpack\Sync\Defaults;
7
use Automattic\Jetpack\Sync\Functions;
8
use Automattic\Jetpack\Sync\Modules;
9
use Automattic\Jetpack\Sync\Modules\Callables;
10
use Automattic\Jetpack\Sync\Modules\WP_Super_Cache;
11
use Automattic\Jetpack\Sync\Sender;
12
use Automattic\Jetpack\Sync\Settings;
13
14
15
require_once 'test_class.jetpack-sync-base.php';
16
17
function jetpack_foo_is_callable() {
18
	return 'bar';
19
}
20
21
/**
22
 * Returns an anonymous function for use in testing .
23
 */
24
function jetpack_foo_is_anon_callable() {
25
	$function = function () {
26
		return 'red';
27
	};
28
	return $function;
29
}
30
31
/**
32
 * Testing Functions
33
 */
34
class WP_Test_Jetpack_Sync_Functions extends WP_Test_Jetpack_Sync_Base {
35
	protected $post;
36
	protected $callable_module;
37
38
	protected static $admin_id; // used in mock_xml_rpc_request
39
40
	public function setUp() {
41
		parent::setUp();
42
43
		$this->resetCallableAndConstantTimeouts();
44
45
		$this->callable_module = Modules::get_module( "functions" );
46
		set_current_screen( 'post-user' ); // this only works in is_admin()
47
	}
48
49 View Code Duplication
	function test_white_listed_function_is_synced() {
50
		$this->callable_module->set_callable_whitelist( array( 'jetpack_foo' => 'jetpack_foo_is_callable' ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method set_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
51
52
		$this->sender->do_sync();
53
54
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
55
		$this->assertEquals( jetpack_foo_is_callable(), $synced_value );
56
	}
57
58
	/**
59
	 * Verify that when a callable returns an anonymous function we don't fatal.
60
	 */
61 View Code Duplication
	public function test_anonymous_function_callable() {
62
		$this->callable_module->set_callable_whitelist( array( 'jetpack_foo_anon' => 'jetpack_foo_is_anon_callable' ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method set_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
63
64
		$this->sender->do_sync();
65
66
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_foo_anon' );
67
		$this->assertEquals( null, $synced_value );
68
	}
69
70
	public function test_sync_jetpack_updates() {
71
		$this->sender->do_sync();
72
		$updates = $this->server_replica_storage->get_callable( 'updates' );
73
		$this->assertEqualsObject( Jetpack::get_updates(), $updates, 'The updates object should match' );
74
	}
75
76
77
	function test_wp_version_is_synced() {
78
		global $wp_version;
79
		$this->sender->do_sync();
80
		$synced_value = $this->server_replica_storage->get_callable( 'wp_version' );
81
		$this->assertEquals( $synced_value, $wp_version );
82
	}
83
84
	public function test_sync_callable_whitelist() {
85
		// $this->setSyncClientDefaults();
86
87
		add_filter( 'jetpack_set_available_extensions',  array( $this, 'add_test_block' ) );
88
		Jetpack_Gutenberg::init();
89
		Blocks::jetpack_register_block( 'jetpack/test' );
90
91
		$callables = array(
92
			'wp_max_upload_size'               => wp_max_upload_size(),
93
			'is_main_network'                  => Jetpack::is_multi_network(),
94
			'is_multi_site'                    => is_multisite(),
95
			'main_network_site'                => Functions::main_network_site_url(),
96
			'single_user_site'                 => Jetpack::is_single_user_site(),
97
			'updates'                          => Jetpack::get_updates(),
98
			'home_url'                         => Functions::home_url(),
99
			'site_url'                         => Functions::site_url(),
100
			'has_file_system_write_access'     => Functions::file_system_write_access(),
101
			'is_version_controlled'            => Functions::is_version_controlled(),
102
			'taxonomies'                       => Functions::get_taxonomies(),
103
			'post_types'                       => Functions::get_post_types(),
104
			'post_type_features'               => Functions::get_post_type_features(),
105
			'rest_api_allowed_post_types'      => Functions::rest_api_allowed_post_types(),
106
			'rest_api_allowed_public_metadata' => Functions::rest_api_allowed_public_metadata(),
107
			'sso_is_two_step_required'         => Jetpack_SSO_Helpers::is_two_step_required(),
108
			'sso_should_hide_login_form'       => Jetpack_SSO_Helpers::should_hide_login_form(),
109
			'sso_match_by_email'               => Jetpack_SSO_Helpers::match_by_email(),
110
			'sso_new_user_override'            => Jetpack_SSO_Helpers::new_user_override(),
111
			'sso_bypass_default_login_form'    => Jetpack_SSO_Helpers::bypass_login_forward_wpcom(),
112
			'wp_version'                       => Functions::wp_version(),
113
			'get_plugins'                      => Functions::get_plugins(),
114
			'get_plugins_action_links'         => Functions::get_plugins_action_links(),
115
			'active_modules'                   => Jetpack::get_active_modules(),
116
			'hosting_provider'                 => Functions::get_hosting_provider(),
117
			'locale'                           => get_locale(),
118
			'site_icon_url'                    => Functions::site_icon_url(),
119
			'shortcodes'                       => Functions::get_shortcodes(),
120
			'roles'                            => Functions::roles(),
121
			'timezone'                         => Functions::get_timezone(),
122
			'available_jetpack_blocks'         => Jetpack_Gutenberg::get_availability(),
123
			'paused_themes'                    => Functions::get_paused_themes(),
124
			'paused_plugins'                   => Functions::get_paused_plugins(),
125
			'main_network_site_wpcom_id'       => Functions::main_network_site_wpcom_id(),
126
			'theme_support'                    => Functions::get_theme_support(),
127
			'wp_get_environment_type'          => wp_get_environment_type(),
128
		);
129
130
		if ( function_exists( 'wp_cache_is_enabled' ) ) {
131
			$callables['wp_super_cache_globals'] = WP_Super_Cache::get_wp_super_cache_globals();
132
		}
133
134
		if ( is_multisite() ) {
135
			$callables['network_name']                        = Jetpack::network_name();
136
			$callables['network_allow_new_registrations']     = Jetpack::network_allow_new_registrations();
137
			$callables['network_add_new_users']               = Jetpack::network_add_new_users();
138
			$callables['network_site_upload_space']           = Jetpack::network_site_upload_space();
139
			$callables['network_upload_file_types']           = Jetpack::network_upload_file_types();
140
			$callables['network_enable_administration_menus'] = Jetpack::network_enable_administration_menus();
141
		}
142
143
		$this->sender->do_sync();
144
145
		foreach ( $callables as $name => $value ) {
146
			// TODO: figure out why _sometimes_ the 'support' value of
147
			// the post_types value is being removed from the output
148
			if ( $name === 'post_types' ) {
149
				continue;
150
			}
151
152
			$this->assertCallableIsSynced( $name, $value );
153
		}
154
155
		$whitelist_keys = array_keys( $this->callable_module->get_callable_whitelist() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method get_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
156
		$callables_keys = array_keys( $callables );
157
158
		// Are we testing all the callables in the defaults?
159
		$whitelist_and_callable_keys_difference = array_diff( $whitelist_keys, $callables_keys );
160
		$this->assertTrue( empty( $whitelist_and_callable_keys_difference ), 'Some whitelisted options don\'t have a test: ' . print_r( $whitelist_and_callable_keys_difference, 1 ) );
161
162
		// Are there any duplicate keys?
163
		$unique_whitelist = array_unique( $whitelist_keys );
164
		$this->assertEquals( count( $unique_whitelist ), count( $whitelist_keys ), 'The duplicate keys are: ' . print_r( array_diff_key( $whitelist_keys, array_unique( $whitelist_keys ) ), 1 ) );
165
166
		remove_filter( 'jetpack_set_available_extensions',  array( $this, 'add_test_block' ) );
167
		Jetpack_Gutenberg::reset();
168
	}
169
170
	public function add_test_block() {
171
		return array( 'test' );
172
	}
173
174
	function assertCallableIsSynced( $name, $value ) {
175
		$this->assertEqualsObject( $value, $this->server_replica_storage->get_callable( $name ), 'Function ' . $name . ' didn\'t have the expected value of ' . json_encode( $value ) );
176
	}
177
178
	function test_white_listed_callables_doesnt_get_synced_twice() {
179
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
180
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
181
		$this->callable_module->set_callable_whitelist( array( 'jetpack_foo' => 'jetpack_foo_is_callable' ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method set_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
182
		$this->sender->do_sync();
183
184
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
185
		$this->assertEquals( 'bar', $synced_value );
186
187
		$this->server_replica_storage->reset();
188
189
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
190
		$this->sender->do_sync();
191
192
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'jetpack_foo' ) );
193
	}
194
195
	/**
196
	 * Tests that calling unlock_sync_callable_next_tick works as expected.
197
	 *
198
	 * Return null
199
	 */
200
	public function test_white_listed_callable_sync_on_next_tick() {
201
		// Setup...
202
		$this->callable_module->set_callable_whitelist( array( 'jetpack_foo' => 'jetpack_foo_is_callable_random' ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method set_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
203
		$this->sender->do_sync();
204
		$initial_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
205
206
		// Action happends that should has the correct data only on the next page load.
207
		$this->callable_module->unlock_sync_callable_next_tick(); // Calling this should have no effect on this sync.
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method unlock_sync_callable_next_tick() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
208
		$this->sender->do_sync();
209
		$should_be_initial_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
210
		$this->assertEquals( $initial_value, $should_be_initial_value );
211
212
		// Next tick...
213
		$this->sender->do_sync(); // This sync sends the updated data...
214
		$new_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
215
		$this->assertNotEquals( $initial_value, $new_value );
216
	}
217
218
	/**
219
	 * Tests that updating the theme should result in the no callabled transient being set.
220
	 *
221
	 * Return null
222
	 */
223
	public function test_updating_stylesheet_sends_the_theme_data() {
224
225
		// Make sure we don't already use this theme.
226
		$this->assertNotEquals( 'twentythirteen', get_option( 'stylesheet' ) );
227
228
		switch_theme( 'twentythirteen' );
229
		$this->sender->do_sync();
230
231
		// Since we can load up the data to see if new data will get send
232
		// this tests if we remove the transiant so that the data can get synced on the next tick.
233
		$this->assertFalse( get_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME ) );
234
	}
235
236
	function test_sync_always_sync_changes_to_modules_right_away() {
237
		Jetpack::update_active_modules( array( 'stats' ) );
238
239
		$this->sender->do_sync();
240
241
		$synced_value = $this->server_replica_storage->get_callable( 'active_modules' );
242
		$this->assertEquals(  array( 'stats' ), $synced_value  );
243
244
		$this->server_replica_storage->reset();
245
246
		Jetpack::update_active_modules( array( 'json-api' ) );
247
		$this->sender->do_sync();
248
249
		$synced_value = $this->server_replica_storage->get_callable( 'active_modules' );
250
		$this->assertEquals( array( 'json-api' ), $synced_value );
251
	}
252
253
	function test_sync_always_sync_changes_to_home_siteurl_right_away() {
254
		$original_home_option    = get_option( 'home' );
255
		$original_siteurl_option = get_option( 'siteurl' );
256
257
		// Let's see if the original values get synced
258
		$this->sender->do_sync();
259
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
260
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
261
262
		$this->assertEquals( $original_home_option, $synced_home_url );
263
		$this->assertEquals( $original_siteurl_option, $synced_site_url );
264
265
		$this->server_replica_storage->reset();
266
267
		$updated_home_option    = 'http://syncrocks.com';
268
		$updated_siteurl_option = 'http://syncrocks.com';
269
270
		update_option( 'home', $updated_home_option );
271
		update_option( 'siteurl', $updated_siteurl_option );
272
273
		$this->sender->do_sync();
274
275
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
276
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
277
278
		$this->assertEquals( $updated_home_option, $synced_home_url );
279
		$this->assertEquals( $updated_siteurl_option, $synced_site_url );
280
281
		// Cleanup
282
		update_option( 'home', $original_home_option );
283
		update_option( 'siteurl', $original_siteurl_option );
284
	}
285
286
	function test_sync_jetpack_sync_unlock_sync_callable_action_allows_syncing_siteurl_changes() {
287
		$original_home_option    = get_option( 'home' );
288
		$original_siteurl_option = get_option( 'siteurl' );
289
290
		// Let's see if the original values get synced. This will also set the await transient.
291
		$this->sender->do_sync();
292
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
293
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
294
295
		$this->assertEquals( $original_home_option, $synced_home_url );
296
		$this->assertEquals( $original_siteurl_option, $synced_site_url );
297
298
		$this->server_replica_storage->reset();
299
300
		update_option( 'home', $this->return_https_site_com_blog() );
301
		update_option( 'siteurl', $this->return_https_site_com_blog() );
302
303
		/**
304
		 * Used to signal that the callables await transient should be cleared. Clearing the await transient is useful
305
		 * in cases where we need to sync values to WordPress.com sooner than the default wait time.
306
		 *
307
		 * @since 4.4.0
308
		 */
309
		do_action( 'jetpack_sync_unlock_sync_callable' );
310
311
		$_SERVER['HTTPS'] = 'on';
312
313
		$this->sender->do_sync();
314
315
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
316
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
317
318
		$this->assertEquals( $this->return_https_site_com_blog(), $synced_home_url );
319
		$this->assertEquals( $this->return_https_site_com_blog(), $synced_site_url );
320
321
		// Cleanup
322
		unset( $_SERVER['HTTPS'] );
323
324
		update_option( 'home', $original_home_option );
325
		update_option( 'siteurl', $original_siteurl_option );
326
	}
327
328
	function test_home_site_urls_synced_while_migrate_for_idc_set() {
329
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
330
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
331
332
		$home_option    = get_option( 'home' );
333
		$siteurl_option = get_option( 'siteurl' );
334
		$main_network   = network_site_url();
335
336
		// First, let's see if the original values get synced
337
		$this->sender->do_sync();
338
339
		$this->assertEquals( $home_option,  $this->server_replica_storage->get_callable( 'home_url' ) );
340
		$this->assertEquals( $siteurl_option, $this->server_replica_storage->get_callable( 'site_url' ) );
341
		$this->assertEquals( $main_network, $this->server_replica_storage->get_callable( 'main_network_site' ) );
342
343
		// Second, let's make sure that values don't get synced again if the migrate_for_idc option is not set
344
		$this->server_replica_storage->reset();
345
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
346
		$this->sender->do_sync();
347
348
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'home_url' ) );
349
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'site_url' ) );
350
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'main_network_site' ) );
351
352
		// Third, let's test that values get syncd with the option set
353
		Jetpack_Options::update_option( 'migrate_for_idc', true );
354
355
		$this->server_replica_storage->reset();
356
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
357
		$this->sender->do_sync();
358
359
		$this->assertEquals( $home_option,  $this->server_replica_storage->get_callable( 'home_url' ) );
360
		$this->assertEquals( $siteurl_option, $this->server_replica_storage->get_callable( 'site_url' ) );
361
		$this->assertEquals( $main_network, $this->server_replica_storage->get_callable( 'main_network_site' ) );
362
363
		Jetpack_Options::delete_option( 'migrate_for_idc' );
364
	}
365
366
	function return_example_com() {
367
		return 'http://example.com';
368
	}
369
370
	function return_example_com_blog() {
371
		return 'http://example.com/blog';
372
	}
373
374
	function return_https_example_com() {
375
		return 'https://example.com';
376
	}
377
378
	function return_https_example_org() {
379
		return 'https://example.org';
380
	}
381
382
	function return_site_com() {
383
		return 'http://site.com';
384
	}
385
386
	function return_https_site_com() {
387
		return 'https://site.com';
388
	}
389
390
	function return_https_site_com_blog() {
391
		return 'https://site.com/blog';
392
	}
393
394
	function return_https_www_example_com() {
395
		return 'https://www.example.com';
396
	}
397
398
	function return_https_foo_example_com() {
399
		return 'https://foo.example.com';
400
	}
401
402
	function test_get_protocol_normalized_url_works_with_no_history() {
403
		$callable_type = 'home_url';
404
		$option_key = Functions::HTTPS_CHECK_OPTION_PREFIX . $callable_type;
405
		delete_option( $option_key );
406
407
		$this->assertStringStartsWith(
408
			'http://',
409
			Functions::get_protocol_normalized_url( $callable_type, $this->return_example_com() )
410
		);
411
412
		delete_option( $option_key );
413
414
		$this->assertStringStartsWith(
415
			'https://',
416
			Functions::get_protocol_normalized_url( $callable_type, $this->return_https_example_com() )
417
		);
418
419
		$this->assertCount( 1, get_option( $option_key ) );
420
421
		delete_option( $option_key );
422
	}
423
424
	function test_get_protocol_normalized_url_stores_max_history() {
425
		$callable_type = 'home_url';
426
		$option_key = Functions::HTTPS_CHECK_OPTION_PREFIX . $callable_type;
427
		delete_option( $option_key );
428
		for ( $i = 0; $i < 20; $i++ ) {
429
			Functions::get_protocol_normalized_url( $callable_type, $this->return_example_com() );
430
		}
431
432
		$this->assertCount( Functions::HTTPS_CHECK_HISTORY, get_option( $option_key ) );
433
		delete_option( $option_key );
434
	}
435
436
	function test_get_protocol_normalized_url_returns_http_when_https_falls_off() {
437
		$callable_type = 'home_url';
438
		$option_key = Functions::HTTPS_CHECK_OPTION_PREFIX . $callable_type;
439
		delete_option( $option_key );
440
441
		// Start with one https scheme
442
		$this->assertStringStartsWith(
443
			'https://',
444
			Functions::get_protocol_normalized_url( $callable_type, $this->return_https_example_com() )
445
		);
446
447
		// Now add enough http schemes to fill up the history
448
		for ( $i = 1; $i < Functions::HTTPS_CHECK_HISTORY; $i++ ) {
449
			$this->assertStringStartsWith(
450
				'https://',
451
				Functions::get_protocol_normalized_url( $callable_type, $this->return_example_com() )
452
			);
453
		}
454
455
		// Now that the history is full, this one should cause the function to return false.
456
		$this->assertStringStartsWith(
457
			'http://',
458
			Functions::get_protocol_normalized_url( $callable_type, $this->return_example_com() )
459
		);
460
	}
461
462
	function test_get_protocol_normalized_url_returns_new_value_cannot_parse() {
463
		$test_url = 'http:///example.com';
464
		$this->assertEquals(
465
			$test_url,
466
			Functions::get_protocol_normalized_url( 'home_url', $test_url )
467
		);
468
	}
469
470
	function test_get_protocol_normalized_url_cleared_on_reset_data() {
471
		Functions::get_protocol_normalized_url( 'home_url', get_home_url() );
472
		Functions::get_protocol_normalized_url( 'site_url', get_site_url() );
473
		Functions::get_protocol_normalized_url( 'main_network_site_url', network_site_url() );
474
475
		$url_callables = array( 'home_url', 'site_url', 'main_network_site_url' );
476
		foreach( $url_callables as $callable ) {
477
			$this->assertInternalType( 'array', get_option( Functions::HTTPS_CHECK_OPTION_PREFIX . $callable) );
478
		}
479
480
		Sender::get_instance()->uninstall();
481
482
		foreach( $url_callables as $callable ) {
483
			$this->assertFalse( get_option( Functions::HTTPS_CHECK_OPTION_PREFIX . $callable ) );
484
		}
485
	}
486
487
	function test_subdomain_switching_to_www_does_not_cause_sync() {
488
		// a lot of sites accept www.domain.com or just domain.com, and we want to prevent lots of
489
		// switching back and forth, so we force the domain to be the one in the siteurl option
490
		$this->setSyncClientDefaults();
491
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
492
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
493
494
		$original_site_url = site_url();
495
496
		// sync original value
497
		$this->sender->do_sync();
498
499
		$this->assertEquals( $original_site_url, $this->server_replica_storage->get_callable( 'site_url' ) );
500
501
		add_filter( 'site_url', array( $this, 'add_www_subdomain_to_siteurl' ) );
502
503
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
504
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
505
		$this->sender->do_sync();
506
507
		$this->assertEquals( $original_site_url, $this->server_replica_storage->get_callable( 'site_url' ) );
508
	}
509
510 View Code Duplication
	function test_sync_limited_set_of_callables_if_cron() {
511
		$all_callables = array_keys( Defaults::get_callable_whitelist() );
512
		$always_updated = Callables::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS;
513
514
		foreach ( $always_updated as $key => $option ) {
515
			if ( array_key_exists( $option, Callables::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
516
				$always_updated[ $key ] = Callables::OPTION_NAMES_TO_CALLABLE_NAMES[ $option ];
517
			}
518
519
		}
520
521
		// non-admin
522
		set_current_screen( 'front' );
523
		Settings::set_doing_cron( true );
524
525
		$this->sender->do_sync();
526
527
		foreach ( $all_callables as $callable ) {
528
			if ( in_array( $callable, $always_updated, true ) ) {
529
				$this->assertNotNull( $this->server_replica_storage->get_callable( $callable ) );
530
			} else {
531
				$this->assertEquals( null, $this->server_replica_storage->get_callable( $callable ) );
532
			}
533
		}
534
535
		Settings::set_doing_cron( false );
536
	}
537
538 View Code Duplication
	function test_sync_limited_set_of_callables_if_wp_cli() {
539
		$all_callables = array_keys( Defaults::get_callable_whitelist() );
540
		$always_updated = Callables::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS;
541
542
		foreach ( $always_updated as $key => $option ) {
543
			if ( array_key_exists( $option, Callables::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
544
				$always_updated[ $key ] = Callables::OPTION_NAMES_TO_CALLABLE_NAMES[ $option ];
545
			}
546
547
		}
548
549
		// non-admin
550
		set_current_screen( 'front' );
551
		Constants::set_constant( 'WP_CLI', true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
552
553
		$this->sender->do_sync();
554
555
		foreach ( $all_callables as $callable ) {
556
			if ( in_array( $callable, $always_updated, true ) ) {
557
				$this->assertNotNull( $this->server_replica_storage->get_callable( $callable ) );
558
			} else {
559
				$this->assertEquals( null, $this->server_replica_storage->get_callable( $callable ) );
560
			}
561
		}
562
563
		Constants::set_constant( 'WP_CLI', false );
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
564
	}
565
566
	function test_site_icon_url_returns_false_when_no_site_icon() {
567
		delete_option( 'jetpack_site_icon_url' );
568
		$this->sender->do_sync();
569
		$this->assertFalse( $this->server_replica_storage->get_callable( 'site_icon_url' ) );
570
	}
571
572
	function test_site_icon_url_returns_core_site_icon_url_when_set() {
573
		$attachment_id = $this->factory->post->create( array(
574
			'post_type'      => 'attachment',
575
			'post_mime_type' => 'image/png',
576
		) );
577
		add_post_meta( $attachment_id, '_wp_attached_file', '2016/09/core_site_icon_url.png' );
578
		update_option( 'site_icon', $attachment_id );
579
		update_option( 'jetpack_site_icon_url', 'http://website.com/wp-content/uploads/2016/09/jetpack_site_icon.png' );
580
581
		$this->sender->do_sync();
582
583
		$this->assertContains( 'core_site_icon_url', $this->server_replica_storage->get_callable( 'site_icon_url' ) );
584
585
		delete_option( 'site_icon' );
586
	}
587
588
	function test_site_icon_url_fallback_to_jetpack_site_icon_url() {
589
		delete_option( 'site_icon' );
590
		update_option( 'jetpack_site_icon_url', 'http://website.com/wp-content/uploads/2016/09/jetpack_site_icon.png' );
591
		$this->sender->do_sync();
592
593
		$this->assertContains( 'jetpack_site_icon', $this->server_replica_storage->get_callable( 'site_icon_url' ) );
594
	}
595
596
	function test_calling_taxonomies_do_not_modify_global() {
597
		global $wp_taxonomies;
598
		// adds taxonomies.
599
		$test = new ABC_FOO_TEST_Taxonomy_Example();
0 ignored issues
show
Unused Code introduced by
$test is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
600
		$this->setSyncClientDefaults();
601
		$sync_callable_taxonomies = Functions::get_taxonomies();
602
603
		$this->assertNull( $sync_callable_taxonomies['example']->update_count_callback );
604
		$this->assertNull( $sync_callable_taxonomies['example']->meta_box_cb );
605
606
		$this->assertNotNull( $wp_taxonomies['example']->update_count_callback );
607
		$this->assertNotNull( $wp_taxonomies['example']->meta_box_cb );
608
609
	}
610
611
	function test_sanitize_sync_taxonomies_method() {
612
613
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'meta_box_cb' => 'post_tags_meta_box' ) );
614
		$this->assertEquals( $sanitized->meta_box_cb, 'post_tags_meta_box' );
615
616
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'meta_box_cb' => 'post_categories_meta_box' ) );
617
		$this->assertEquals( $sanitized->meta_box_cb, 'post_categories_meta_box' );
618
619
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'meta_box_cb' => 'banana' ) );
620
		$this->assertEquals( $sanitized->meta_box_cb, null );
621
622
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'update_count_callback' => 'banana' ) );
623
		$this->assertFalse( isset( $sanitized->update_count_callback ) );
624
625
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'rest_controller_class' => 'banana' ) );
626
		$this->assertEquals( $sanitized->rest_controller_class, null );
627
628
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'rest_controller_class' => 'WP_REST_Terms_Controller' ) );
629
630
		$this->assertEquals( $sanitized->rest_controller_class, 'WP_REST_Terms_Controller' );
631
	}
632
633 View Code Duplication
	function test_sanitize_sync_post_type_method_default() {
634
		$label = 'foo_default';
635
		$post_type_object = new WP_Post_Type( $label );
636
		$post_type_object->add_supports();
637
		$post_type_object->add_rewrite_rules();
638
		$post_type_object->register_meta_boxes();
639
		$post_type_object->add_hooks();
640
		$post_type_object->register_taxonomies();
641
642
		$sanitized = Functions::sanitize_post_type( $post_type_object );
643
		$this->assert_sanitized_post_type_default( $sanitized, $label );
644
645
	}
646
647 View Code Duplication
	function test_sanitize_sync_post_type_method_remove_unknown_values_set() {
648
		$label = 'foo_strange';
649
		$post_type_object = new WP_Post_Type( $label, array( 'foo' => 'bar' ) );
650
		$post_type_object->add_supports();
651
		$post_type_object->add_rewrite_rules();
652
		$post_type_object->register_meta_boxes();
653
		$post_type_object->add_hooks();
654
		$post_type_object->register_taxonomies();
655
656
		$sanitized = Functions::sanitize_post_type( $post_type_object );
657
		$this->assert_sanitized_post_type_default( $sanitized, $label );
658
	}
659
660
	function assert_sanitized_post_type_default( $sanitized, $label ) {
661
		$this->assertEquals( $label, $sanitized->name );
662
		$this->assertEquals( 'Posts', $sanitized->label );
663
		$this->assertEquals( '', $sanitized->description );
664
		$this->assertEquals( $label, $sanitized->rewrite['slug'] );
665
		$this->assertEquals( $label, $sanitized->query_var );
666
		$this->assertEquals( 'post', $sanitized->capability_type );
667
		$this->assertEquals( array(), $sanitized->taxonomies );
668
		$this->assertEquals( array(), $sanitized->supports );
669
		$this->assertEquals( '', $sanitized->_edit_link );
670
671
		$this->assertFalse( $sanitized->public );
672
		$this->assertFalse( $sanitized->has_archive );
673
		$this->assertFalse( $sanitized->publicly_queryable );
674
		$this->assertFalse( $sanitized->hierarchical );
675
		$this->assertFalse( $sanitized->show_ui );
676
		$this->assertFalse( $sanitized->show_in_menu );
677
		$this->assertFalse( $sanitized->show_in_nav_menus );
678
		$this->assertFalse( $sanitized->show_in_admin_bar );
679
		$this->assertFalse( $sanitized->rest_base );
680
		$this->assertFalse( $sanitized->_builtin );
681
682
		$this->assertTrue( $sanitized->exclude_from_search );
683
		$this->assertTrue( $sanitized->can_export );
684
		$this->assertTrue( $sanitized->map_meta_cap );
685
		$this->assertTrue( is_object( $sanitized->labels ) );
686
		$this->assertTrue( is_array( $sanitized->rewrite ) );
687
		$this->assertTrue( is_object( $sanitized->cap ) );
688
689
	}
690
691
	function test_sanitize_sync_post_type_method_all_values_set() {
692
		$args = array(
693
			'labels'                => array(
694
				'stuff' => 'apple',
695
			),
696
			'description'           => 'banana',
697
			'public'                => true,
698
			'hierarchical'          => true,
699
			'exclude_from_search'   => false,
700
			'publicly_queryable'    => true,
701
			'show_ui'               => true,
702
			'show_in_menu'          => true,
703
			'show_in_nav_menus'     => true,
704
			'show_in_admin_bar'     => true,
705
			'menu_position'         => 10,
706
			'menu_icon'             => 'jetpack',
707
			'capability_type'       => 'foo',
708
			'capabilities'          => array( 'banana' => true ),
709
			'map_meta_cap'          => false,
710
			'supports'              => array( 'everything' ),
711
			'taxonomies'            => array( 'orange'),
712
			'has_archive'           => true,
713
			'rewrite'               => false,
714
			'query_var'             => 'foo_all_stuff',
715
			'can_export'            => false,
716
			'delete_with_user'      => true,
717
			'show_in_rest'          => true,
718
			'rest_base'             => 'foo_all_stuffing',
719
		);
720
		$post_type_object = new WP_Post_Type( 'foo_all', $args );
721
		$post_type_object->add_supports();
722
		$post_type_object->add_rewrite_rules();
723
		$post_type_object->register_meta_boxes();
724
		$post_type_object->add_hooks();
725
		$post_type_object->register_taxonomies();
726
727
		$sanitized = Functions::sanitize_post_type( $post_type_object );
728
		foreach( $args as $arg_key => $arg_value ) {
729
			//
730
			if ( in_array( $arg_key, array( 'labels', 'capabilities', 'supports' ) ) ) {
731
				continue;
732
			}
733
			$this->assertEquals( $arg_value, $sanitized->{ $arg_key }, 'Value for ' . $arg_key . 'not as expected' );
734
		}
735
	}
736
737
	function test_get_post_types_method() {
738
		global $wp_post_types;
739
		$synced = Functions::get_post_types();
740
		foreach( $wp_post_types as $post_type => $post_type_object ) {
741
			$post_type_object->rest_controller_class = false;
742
			if ( ! isset( $post_type_object->supports ) ) {
743
				$post_type_object->supports = array();
744
			}
745
			$synced_post_type = Functions::expand_synced_post_type( $synced[ $post_type ], $post_type );
746
			$this->assertEqualsObject( $post_type_object, $synced_post_type, 'POST TYPE :'. $post_type . ' not equal' );
747
		}
748
	}
749
750
	function test_register_post_types_callback_error() {
751
		register_post_type( 'testing', array( 'register_meta_box_cb' => function() {} ) );
752
		$this->sender->do_sync();
753
754
		$post_types =  $this->server_replica_storage->get_callable( 'post_types' );
755
		$this->assertTrue( isset( $post_types['testing'] ) );
756
	}
757
758
	function test_get_raw_url_by_option_bypasses_filters() {
759
		add_filter( 'option_home', array( $this, 'return_filtered_url' ) );
760
		$this->assertTrue( 'http://filteredurl.com' !== Functions::get_raw_url( 'home' ) );
761
		remove_filter( 'option_home', array( $this, 'return_filtered_url' ) );
762
	}
763
764
	function test_get_raw_url_by_constant_bypasses_filters() {
765
		Constants::set_constant( 'WP_HOME', 'http://constanturl.com' );
766
		Constants::set_constant( 'WP_SITEURL', 'http://constanturl.com' );
767
		add_filter( 'option_home', array( $this, 'return_filtered_url' ) );
768
		add_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
769
770
		if ( is_multisite() ) {
771
			$this->assertTrue( $this->return_filtered_url() !== Functions::get_raw_url( 'home' ) );
772
			$this->assertTrue( $this->return_filtered_url() !== Functions::get_raw_url( 'siteurl' ) );
773
		} else {
774
			$this->assertEquals( 'http://constanturl.com', Functions::get_raw_url( 'home' ) );
775
			$this->assertEquals( 'http://constanturl.com', Functions::get_raw_url( 'siteurl' ) );
776
		}
777
778
		remove_filter( 'option_home', array( $this, 'return_filtered_url' ) );
779
		remove_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
780
		Constants::clear_constants();
781
	}
782
783 View Code Duplication
	function test_get_raw_url_returns_with_http_if_is_ssl() {
784
		$home_option = get_option( 'home' );
785
786
		// Test without https first
787
		$this->assertEquals( $home_option, Functions::get_raw_url( 'home' ) );
788
789
		// Now, with https
790
		$_SERVER['HTTPS'] = 'on';
791
		$this->assertEquals(
792
			set_url_scheme( $home_option, 'http' ),
793
			Functions::get_raw_url( 'home' )
794
		);
795
		unset( $_SERVER['HTTPS'] );
796
	}
797
798 View Code Duplication
	function test_raw_home_url_is_https_when_is_ssl() {
799
		Constants::set_constant( 'JETPACK_SYNC_USE_RAW_URL', true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
800
801
		$home_option = get_option( 'home' );
802
803
		// Test without https first
804
		$this->assertEquals(
805
			$home_option,
806
			Functions::home_url()
807
		);
808
809
		// Now, with https
810
		$_SERVER['HTTPS'] = 'on';
811
		$this->assertEquals(
812
			set_url_scheme( $home_option, 'https' ),
813
			Functions::home_url()
814
		);
815
		unset( $_SERVER['HTTPS'] );
816
	}
817
818
	function test_user_can_stop_raw_urls() {
819
		add_filter( 'option_home', array( $this, 'return_filtered_url' ) );
820
		add_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
821
822
		// Test with constant first
823
		$this->assertTrue( 'http://filteredurl.com' !== Functions::home_url() );
824
825
		// Now, without, which should return the filtered URL
826
		Constants::set_constant( 'JETPACK_SYNC_USE_RAW_URL', false );
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
827
		$this->assertEquals( $this->return_filtered_url(), Functions::home_url() );
828
		Constants::clear_constants();
829
830
		remove_filter( 'option_home', array( $this, 'return_filtered_url' ) );
831
		remove_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
832
	}
833
834
	function test_plugin_action_links_get_synced() {
835
		// Makes sure that we start fresh
836
		delete_transient( 'jetpack_plugin_api_action_links_refresh' );
837
		$helper_all = new Jetpack_Sync_Test_Helper();
838
839
		$helper_all->array_override = array( '<a href="fun.php">fun</a>' );
840
		add_filter( 'plugin_action_links', array( $helper_all, 'filter_override_array' ), 10 );
841
842
		$helper_jetpack = new Jetpack_Sync_Test_Helper();
843
		$helper_jetpack->array_override = array( '<a href="settings.php">settings</a>', '<a href="https://jetpack.com/support">support</a>' );
844
		add_filter( 'plugin_action_links_jetpack/jetpack.php', array( $helper_jetpack, 'filter_override_array' ), 10 );
845
846
		set_current_screen( 'banana' );
847
		// Let's see if the original values get synced
848
		$this->sender->do_sync();
849
850
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
851
852
		$expected_array = array(
853
			'hello.php' => array(
854
				'fun' => admin_url( 'fun.php' )
855
			),
856
			'jetpack/jetpack.php' => array(
857
				'settings' => admin_url( 'settings.php' ),
858
				'support' => 'https://jetpack.com/support'
859
			)
860
		);
861
862
		$this->assertEquals( $expected_array, $this->extract_plugins_we_are_testing( $plugins_action_links )  );
863
864
		$helper_all->array_override = array( '<a href="not-fun.php">not fun</a>' );
865
866
		$this->resetCallableAndConstantTimeouts();
867
868
		set_current_screen( 'banana' );
869
		$this->sender->do_sync();
870
871
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
872
873
		// Nothing should have changed since we cache the results.
874
		$this->assertEquals( $this->extract_plugins_we_are_testing( $plugins_action_links ), $expected_array );
875
876
		if ( file_exists( WP_PLUGIN_DIR . '/hello.php' ) ) {
877
			activate_plugin('hello.php', '', false, true );
878
		}
879
		if ( file_exists( WP_PLUGIN_DIR . '/hello-dolly/hello.php' ) ) {
880
			activate_plugin('hello-dolly/hello.php', '', false, true );
881
		}
882
883
		$this->resetCallableAndConstantTimeouts();
884
		set_current_screen( 'banana' );
885
		$this->sender->do_sync();
886
887
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
888
889
		// Links should have changes now since we activated the plugin.
890
		$expected_array['hello.php'] = array( 'not fun' => admin_url( 'not-fun.php' ) );
891
		$this->assertEquals( $this->extract_plugins_we_are_testing( $plugins_action_links ), $expected_array, 'Array was not updated to the new value as expected' );
892
	}
893
894
	function extract_plugins_we_are_testing( $plugins_action_links ) {
895
		$only_plugins_we_care_about = array();
896
		if ( isset( $plugins_action_links['hello.php'] ) ) {
897
			$only_plugins_we_care_about['hello.php'] = isset( $plugins_action_links['hello.php'] ) ? $plugins_action_links['hello.php'] : '';
898
		} else {
899
			$only_plugins_we_care_about['hello.php'] = isset( $plugins_action_links['hello-dolly/hello.php'] ) ? $plugins_action_links['hello-dolly/hello.php'] : '';
900
		}
901
902
		$only_plugins_we_care_about['jetpack/jetpack.php'] = isset( $plugins_action_links['jetpack/jetpack.php'] ) ? $plugins_action_links['jetpack/jetpack.php'] : '';
903
		return $only_plugins_we_care_about;
904
	}
905
906
	function cause_fatal_error( $actions ) {
907
		unset( $actions['activate'] );
908
		$actions[] = '<a href="/hello">world</a>';
909
		return $actions;
910
	}
911
912
	function test_fixes_fatal_error( ) {
913
914
		delete_transient( 'jetpack_plugin_api_action_links_refresh' );
915
		add_filter( 'plugin_action_links', array( $this, 'cause_fatal_error' ) );
916
917
		set_current_screen( 'plugins' );
918
919
		$this->resetCallableAndConstantTimeouts();
920
		set_current_screen( 'plugins' );
921
		$this->sender->do_sync();
922
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
923
		$plugins_action_links = $this->extract_plugins_we_are_testing( $plugins_action_links );
924
		$this->assertTrue( isset( $plugins_action_links['hello.php']['world'] ), 'World is not set' );
925
	}
926
927
	/**
928
	 * Return "http://filteredurl.com".
929
	 *
930
	 * @return string
931
	 */
932
	public function return_filtered_url() {
933
		return 'http://filteredurl.com';
934
	}
935
936
	/**
937
	 * Add a "www" subdomain to a URL.
938
	 *
939
	 * @param string $url URL.
940
	 * @return string
941
	 */
942
	public function add_www_subdomain_to_siteurl( $url ) {
943
		$parsed_url = wp_parse_url( $url );
944
945
		return "{$parsed_url['scheme']}://www.{$parsed_url['host']}";
946
	}
947
948
	/**
949
	 * Test "taxonomies_objects_do_not_have_meta_box_callback".
950
	 */
951
	public function test_taxonomies_objects_do_not_have_meta_box_callback() {
952
953
		new ABC_FOO_TEST_Taxonomy_Example();
954
		$taxonomies = Functions::get_taxonomies();
955
		$taxonomy   = $taxonomies['example'];
956
957
		$this->assertInternalType( 'object', $taxonomy );
958
		// Did we get rid of the expected attributes?
959
		$this->assertNull( $taxonomy->update_count_callback, 'example has the update_count_callback attribute, which should be removed since it is a callback' );
960
		$this->assertNull( $taxonomy->meta_box_cb, 'example has the meta_box_cb attribute, which should be removed since it is a callback' );
961
		$this->assertNull( $taxonomy->rest_controller_class );
962
		// Did we preserve the expected attributes?
963
		$check_object_vars = array(
964
			'labels',
965
			'description',
966
			'public',
967
			'publicly_queryable',
968
			'hierarchical',
969
			'show_ui',
970
			'show_in_menu',
971
			'show_in_nav_menus',
972
			'show_tagcloud',
973
			'show_in_quick_edit',
974
			'show_admin_column',
975
			'rewrite',
976
		);
977
		foreach ( $check_object_vars as $test ) {
978
			$this->assertObjectHasAttribute( $test, $taxonomy, "Taxonomy does not have expected {$test} attribute." );
979
		}
980
	}
981
982
	/**
983
	 * Test "force_sync_callable_on_plugin_update".
984
	 */
985
	public function test_force_sync_callable_on_plugin_update() {
986
		// fake the cron so that we really prevent the callables from being called.
987
		Settings::$is_doing_cron = true;
988
989
		$this->callable_module->set_callable_whitelist( array( 'jetpack_foo' => 'jetpack_foo_is_callable_random' ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method set_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
990
		$this->sender->do_sync();
991
		$this->server_replica_storage->get_callable( 'jetpack_foo' );
992
993
		$this->server_replica_storage->reset();
994
995
		$synced_value2 = $this->server_replica_storage->get_callable( 'jetpack_foo' );
996
		$this->assertEmpty( $synced_value2 );
997
998
		$upgrader = (object) array(
999
			'skin' => (object) array(
1000
				'result' => new WP_Error( 'fail', 'Fail' ),
1001
			),
1002
		);
1003
1004
		do_action(
1005
			'upgrader_process_complete',
1006
			$upgrader,
1007
			array(
1008
				'action'  => 'update',
1009
				'type'    => 'plugin',
1010
				'bulk'    => true,
1011
				'plugins' => array( 'the/the.php' ),
1012
			)
1013
		);
1014
1015
		$this->sender->do_sync();
1016
		$synced_value3           = $this->server_replica_storage->get_callable( 'jetpack_foo' );
1017
		Settings::$is_doing_cron = false;
1018
		$this->assertNotEmpty( $synced_value3, 'value is empty!' );
1019
1020
	}
1021
1022
	/**
1023
	 * Test "xml_rpc_request_callables_has_actor".
1024
	 */
1025
	public function test_xml_rpc_request_callables_has_actor() {
1026
		$this->server_event_storage->reset();
1027
		$user = wp_get_current_user();
1028
		wp_set_current_user( 0 ); //
1029
		$this->sender->do_sync();
1030
		$event = $this->server_event_storage->get_most_recent_event( 'jetpack_sync_callable' );
1031
		$this->assertEquals( $event->user_id, 0, ' Callables user_id is null' );
1032
1033
		$this->resetCallableAndConstantTimeouts();
1034
		$this->mock_authenticated_xml_rpc(); // mock requet
1035
		$this->sender->do_sync();
1036
1037
		$event = $this->server_event_storage->get_most_recent_event( 'jetpack_sync_callable' );
1038
		// clean up by unsetting globals, etc. set previously by $this->mock_authenticated_xml_rpc()
1039
		$this->mock_authenticated_xml_rpc_cleanup( $user->ID );
1040
1041
		$this->assertEquals( $event->user_id, self::$admin_id, ' Callables XMLRPC_Reqeust not equal to event user_id' );
1042
	}
1043
1044
	/**
1045
	 * Mock authenticated XML RPC.
1046
	 */
1047
	public function mock_authenticated_xml_rpc() {
1048
		self::$admin_id = $this->factory->user->create( array(
1049
			'role' => 'administrator',
1050
		) );
1051
1052
		add_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10, 2 );
1053
		$_GET['token']     = 'pretend_this_is_valid:1:' . self::$admin_id;
1054
		$_GET['timestamp'] = (string) time();
1055
		$_GET['nonce']     = 'testing123';
1056
1057
		$_SERVER['REQUEST_URI']        = '/xmlrpc.php';
1058
		$_GET['body']                  = 'abc';
1059
		$_GET['body-hash']             = base64_encode( sha1( 'abc', true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1060
		$GLOBALS['HTTP_RAW_POST_DATA'] = 'abc'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1061
		$_SERVER['REQUEST_METHOD']     = 'POST';
1062
1063
		$normalized_request_pieces = array(
1064
			$_GET['token'],
1065
			$_GET['timestamp'],
1066
			$_GET['nonce'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1067
			$_GET['body-hash'],
1068
			'POST',
1069
			'example.org',
1070
			'80',
1071
			'/xmlrpc.php',
1072
		);
1073
		$normalize                 = join( "\n", $normalized_request_pieces ) . "\n";
1074
1075
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1076
		$_GET['signature'] = base64_encode( hash_hmac( 'sha1', $normalize, 'secret', true ) );
1077
1078
		// call one of the authenticated endpoints
1079
		Constants::set_constant( 'XMLRPC_REQUEST', true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1080
		Jetpack::init();
1081
		$connection = Jetpack::connection();
1082
		$connection->xmlrpc_methods( array() );
1083
		$connection->require_jetpack_authentication();
1084
		$connection->verify_xml_rpc_signature();
1085
	}
1086
1087
	/**
1088
	 * Mock authenticated XML RPC cleanup.
1089
	 *
1090
	 * @param int $user_id User ID.
1091
	 */
1092
	public function mock_authenticated_xml_rpc_cleanup( $user_id ) {
1093
		Constants::clear_constants();
1094
		remove_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 );
1095
1096
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
1097
		unset( $_GET['token'] );
1098
		unset( $_GET['timestamp'] );
1099
		unset( $_GET['nonce'] );
1100
		$_SERVER['REQUEST_URI'] = '';
1101
		unset( $_GET['body'] );
1102
		unset( $_GET['body-hash'] );
1103
		unset( $GLOBALS['HTTP_RAW_POST_DATA'] );
1104
		unset( $_SERVER['REQUEST_METHOD'] );
1105
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
1106
1107
		Connection_Rest_Authentication::init()->reset_saved_auth_state();
1108
		Jetpack::connection()->reset_raw_post_data();
1109
		wp_set_current_user( $user_id );
1110
		self::$admin_id = null;
1111
	}
1112
1113
	/**
1114
	 * Mock Jetpack private options.
1115
	 */
1116 View Code Duplication
	public function mock_jetpack_private_options() {
1117
		$user_tokens                    = array();
1118
		$user_tokens[ self::$admin_id ] = 'pretend_this_is_valid.secret.' . self::$admin_id;
1119
		return array(
1120
			'user_tokens' => $user_tokens,
1121
		);
1122
	}
1123
1124
	/**
1125
	 * Test "get_timezone_from_timezone_string".
1126
	 */
1127
	public function test_get_timezone_from_timezone_string() {
1128
		update_option( 'timezone_string', 'America/Rankin_Inlet' );
1129
		update_option( 'gmt_offset', '' );
1130
		$this->assertEquals( 'America/Rankin Inlet', Functions::get_timezone() );
1131
	}
1132
1133
	/**
1134
	 * Test "get_timezone_from_gmt_offset_zero".
1135
	 */
1136
	public function test_get_timezone_from_gmt_offset_zero() {
1137
		update_option( 'timezone_string', '' );
1138
		update_option( 'gmt_offset', '0' );
1139
		$this->assertEquals( 'UTC+0', Functions::get_timezone() );
1140
	}
1141
1142
	/**
1143
	 * Test "get_timezone_from_gmt_offset_plus".
1144
	 */
1145
	public function test_get_timezone_from_gmt_offset_plus() {
1146
		update_option( 'timezone_string', '' );
1147
		update_option( 'gmt_offset', '1' );
1148
		$this->assertEquals( 'UTC+1', Functions::get_timezone() );
1149
	}
1150
1151
	/**
1152
	 * Test "get_timezone_from_gmt_offset_fractions".
1153
	 */
1154
	public function test_get_timezone_from_gmt_offset_fractions() {
1155
		update_option( 'timezone_string', '' );
1156
		update_option( 'gmt_offset', '5.5' );
1157
		$this->assertEquals( 'UTC+5:30', Functions::get_timezone() );
1158
	}
1159
1160
	/**
1161
	 * Test "get_timezone_from_gmt_offset_minus".
1162
	 */
1163
	public function test_get_timezone_from_gmt_offset_minus() {
1164
		update_option( 'timezone_string', '' );
1165
		update_option( 'gmt_offset', '-1' );
1166
		$this->assertEquals( 'UTC-1', Functions::get_timezone() );
1167
	}
1168
1169
	/**
1170
	 * Test "sync_callable_recursive_gets_checksum".
1171
	 */
1172 View Code Duplication
	public function test_sync_callable_recursive_gets_checksum() {
1173
1174
		$this->callable_module->set_callable_whitelist( array( 'jetpack_banana' => 'jetpack_recursive_banana' ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method set_callable_whitelist() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Callables. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1175
		$this->sender->do_sync();
1176
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_banana' );
1177
		$this->assertTrue( ! empty( $synced_value ), 'We couldn\'t synced a value!' );
1178
	}
1179
1180
	/**
1181
	 * Test get_hosting_provider() callable to ensure that known hosts have the
1182
	 * right hosting provider returned.
1183
	 *
1184
	 * @return void
1185
	 */
1186
	public function test_get_hosting_provider_callable_with_unknown_host() {
1187
		$this->assertEquals( Functions::get_hosting_provider(), 'unknown' );
1188
	}
1189
1190
	/**
1191
	 * Test getting a hosting provider by a known constant
1192
	 *
1193
	 * @return void
1194
	 */
1195
	public function test_get_hosting_provider_by_known_constant() {
1196
		$functions = new Functions();
1197
		Constants::set_constant( 'GD_SYSTEM_PLUGIN_DIR', 'set' );
1198
		$this->assertEquals( $functions->get_hosting_provider_by_known_constant(), 'gd-managed-wp' );
1199
		Constants::clear_constants();
1200
1201
		Constants::set_constant( 'UNKNOWN', 'set' );
1202
		$this->assertFalse( $functions->get_hosting_provider_by_known_constant() );
1203
		Constants::clear_constants();
1204
	}
1205
1206
	/**
1207
	 * Test getting a hosting provider by a known class
1208
	 *
1209
	 * @return void
1210
	 */
1211
	public function test_get_hosting_provider_by_known_class() {
1212
		$functions = new Functions();
1213
1214
		$this->assertFalse( $functions->get_hosting_provider_by_known_class() );
1215
1216
		$class_mock = $this->getMockBuilder( '\\WPaaS\\Plugin' )
0 ignored issues
show
Unused Code introduced by
$class_mock is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1217
					->getMock(); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1218
1219
		$this->assertEquals( $functions->get_hosting_provider_by_known_class(), 'gd-managed-wp' );
1220
1221
	}
1222
1223
	/**
1224
	 * Test getting a hosting provider by a known function
1225
	 *
1226
	 * @return bool
1227
	 */
1228
	public function test_get_hosting_provider_by_known_function() {
1229
1230
		/**
1231
		 * Stub is_wpe for testing function exists
1232
		 *
1233
		 * @return boolean
1234
		 */
1235
		function is_wpe() { // phpcs:ignore MediaWiki.Usage.NestedFunctions.NestedFunction
1236
			return true;
1237
		}
1238
1239
		$functions = new Functions();
1240
1241
		// Get hosting provider by known function.
1242
		$this->assertEquals( $functions->get_hosting_provider_by_known_function(), 'wpe' );
1243
	}
1244
1245
	/**
1246
	 * Test getting the main network site wpcom ID in multisite installs
1247
	 *
1248
	 * @return void
1249
	 */
1250
	public function test_get_main_network_site_wpcom_id_multisite() {
1251
		if ( ! is_multisite() ) {
1252
			$this->markTestSkipped( 'Only used on multisite' );
1253
		}
1254
1255
		// set the Jetpack ID for this site.
1256
		$main_network_wpcom_id = 12345;
1257
		\Jetpack_Options::update_option( 'id', $main_network_wpcom_id );
1258
1259
		$user_id = $this->factory->user->create();
1260
1261
		// NOTE this is necessary because WPMU causes certain assumptions about transients.
1262
		// to be wrong, and tests to explode. @see: https://github.com/sheabunge/WordPress/commit/ff4f1bb17095c6af8a0f35ac304f79074f3c3ff6 .
1263
		global $wpdb;
1264
1265
		$suppress      = $wpdb->suppress_errors();
1266
		$other_blog_id = wpmu_create_blog( 'foo.com', '', 'My Blog', $user_id );
1267
		$wpdb->suppress_errors( $suppress );
1268
1269
		switch_to_blog( $other_blog_id );
1270
1271
		$functions = new Functions();
1272
		$this->assertEquals( $main_network_wpcom_id, $functions->main_network_site_wpcom_id() );
1273
1274
		restore_current_blog();
1275
	}
1276
1277
	/**
1278
	 * Verify get_check_sum is consistent for differently ordered arrays.
1279
	 */
1280 View Code Duplication
	public function test_sync_does_not_send_updates_if_array_order_is_only_change() {
1281
		$plugins = Functions::get_plugins();
1282
1283
		// Let's see if the original values get synced.
1284
		$this->sender->do_sync();
1285
		$plugins_synced = $this->server_replica_storage->get_callable( 'get_plugins' );
1286
1287
		$this->assertEquals( $plugins, $plugins_synced );
1288
1289
		add_filter( 'all_plugins', array( $this, 'reorder_array_keys' ), 100, 1 );
1290
		do_action( 'jetpack_sync_unlock_sync_callable' );
1291
		$this->server_event_storage->reset();
1292
		$this->sender->do_sync();
1293
1294
		$event = $this->server_event_storage->get_most_recent_event( 'jetpack_sync_callable' );
1295
		$this->assertFalse( $event );
1296
	}
1297
1298
	/**
1299
	 * Reorder the get_plugins array keys.
1300
	 *
1301
	 * @param array $plugins array of plugins.
1302
	 *
1303
	 * @return array
1304
	 */
1305
	public function reorder_array_keys( $plugins ) {
1306
		// First plugin in array.
1307
		$plugin_key = array_keys( $plugins )[0];
1308
1309
		// reverse the 1st plugin's array entries.
1310
		$plugins[ $plugin_key ] = array_reverse( $plugins[ $plugin_key ] );
1311
1312
		// reverse the full array.
1313
		return array_reverse( $plugins );
1314
	}
1315
1316
	/**
1317
	 * Test getting the main network site wpcom ID in single site installs
1318
	 *
1319
	 * @return void
1320
	 */
1321
	public function test_get_main_network_site_wpcom_id_single() {
1322
		// set the Jetpack ID for this site.
1323
		$main_network_wpcom_id = 7891011;
1324
		\Jetpack_Options::update_option( 'id', $main_network_wpcom_id );
1325
1326
		$functions = new Functions();
1327
		$this->assertEquals( $main_network_wpcom_id, $functions->main_network_site_wpcom_id() );
1328
	}
1329
1330
}
1331
1332
/**
1333
 * Create a recursive object.
1334
 *
1335
 * @return object
1336
 */
1337
function jetpack_recursive_banana() {
1338
	$banana        = new stdClass();
1339
	$banana->arr   = array();
1340
	$banana->arr[] = $banana;
1341
	return $banana;
1342
}
1343
1344
/**
1345
 * Return a "random" number.
1346
 *
1347
 * Previously just returned `rand()`. I'm guessing something is trying to test
1348
 * caching or cache busting by having a different value returned each time, so
1349
 * let's do that reliably.
1350
 *
1351
 * @return int
1352
 */
1353
function jetpack_foo_is_callable_random() {
1354
	static $value = null;
1355
1356
	if ( null === $value ) {
1357
		$value = wp_rand();
1358
	}
1359
1360
	return $value++;
1361
}
1362
1363
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
1364
/**
1365
 * Example Test Taxonomy
1366
 */
1367
class ABC_FOO_TEST_Taxonomy_Example {
1368
1369
	/**
1370
	 * Constructor. Duh.
1371
	 */
1372
	public function __construct() {
1373
1374
		register_taxonomy(
1375
			'example',
1376
			'posts',
1377
			array(
1378
				'meta_box_cb'           => 'bob',
1379
				'update_count_callback' => array( $this, 'callback_update_count_callback_tags' ), // phpcs:ignore WordPress.Arrays.CommaAfterArrayItem.NoComma
1380
				'rest_controller_class' => 'tom',
1381
			)
1382
		);
1383
	}
1384
1385
	/**
1386
	 * `update_count_callback` callback.
1387
	 *
1388
	 * @return int
1389
	 */
1390
	public function callback_update_count_callback_tags() {
1391
		return 123;
1392
	}
1393
1394
	/**
1395
	 * Prevent this class being used as part of a Serialization injection attack
1396
	 */
1397
	public function __clone() {
1398
		wp_die( 'Please don\'t __clone ' . __CLASS__ );
1399
	}
1400
1401
	/**
1402
	 * Prevent this class being used as part of a Serialization injection attack
1403
	 */
1404
	public function __wakeup() {
1405
		wp_die( 'Please don\'t __wakeup ' . __CLASS__ );
1406
	}
1407
}
1408