WP_Test_Jetpack_Sync_Functions   F
last analyzed

Complexity

Total Complexity 105

Size/Duplication

Total Lines 1317
Duplicated Lines 12.53 %

Coupling/Cohesion

Components 2
Dependencies 21

Importance

Changes 0
Metric Value
dl 165
loc 1317
rs 0.8
c 0
b 0
f 0
wmc 105
lcom 2
cbo 21

77 Methods

Rating   Name   Duplication   Size   Complexity  
A add_test_block() 0 3 1
A assertCallableIsSynced() 0 3 1
A test_white_listed_callables_doesnt_get_synced_twice() 0 16 1
A test_white_listed_callable_sync_on_next_tick() 0 17 1
A test_updating_stylesheet_sends_the_theme_data() 0 12 1
A test_sync_always_sync_changes_to_modules_right_away() 0 16 1
A test_sync_always_sync_changes_to_home_siteurl_right_away() 0 32 1
A test_sync_jetpack_sync_unlock_sync_callable_action_allows_syncing_siteurl_changes() 0 41 1
A test_home_site_urls_synced_while_migrate_for_idc_set() 0 37 1
A return_example_com() 0 3 1
A return_example_com_blog() 0 3 1
A return_https_example_com() 0 3 1
A return_https_example_org() 0 3 1
A return_site_com() 0 3 1
A return_https_site_com() 0 3 1
A return_https_site_com_blog() 0 3 1
A return_https_www_example_com() 0 3 1
A return_https_foo_example_com() 0 3 1
A test_get_protocol_normalized_url_works_with_no_history() 0 21 1
A test_get_protocol_normalized_url_stores_max_history() 0 11 2
A test_get_protocol_normalized_url_returns_http_when_https_falls_off() 0 25 2
A test_get_protocol_normalized_url_returns_new_value_cannot_parse() 0 7 1
A test_get_protocol_normalized_url_cleared_on_reset_data() 0 16 3
A test_subdomain_switching_to_www_does_not_cause_sync() 0 22 1
A test_sync_limited_set_of_callables_if_cron() 27 27 5
A test_sync_limited_set_of_callables_if_wp_cli() 27 27 5
A test_site_icon_url_returns_false_when_no_site_icon() 0 5 1
A test_site_icon_url_returns_core_site_icon_url_when_set() 0 15 1
A test_site_icon_url_fallback_to_jetpack_site_icon_url() 0 7 1
A test_calling_taxonomies_do_not_modify_global() 0 14 1
A test_sanitize_sync_taxonomies_method() 0 21 1
A test_sanitize_sync_post_type_method_default() 13 13 1
A test_sanitize_sync_post_type_method_remove_unknown_values_set() 12 12 1
A assert_sanitized_post_type_default() 0 30 1
A test_sanitize_sync_post_type_method_all_values_set() 0 45 3
A setUp() 0 8 1
A test_white_listed_function_is_synced() 8 8 1
A test_anonymous_function_callable() 8 8 1
A test_sync_jetpack_updates() 0 5 1
A test_wp_version_is_synced() 0 6 1
B test_sync_callable_whitelist() 0 85 5
A test_class.jetpack-sync-callables.php ➔ is_wpe() 0 3 1
A test_get_post_types_method() 0 13 3
A test_register_post_types_callback_error() 0 7 1
A test_get_raw_url_by_option_bypasses_filters() 0 5 1
A test_get_raw_url_by_constant_bypasses_filters() 0 18 2
A test_get_raw_url_returns_with_http_if_is_ssl() 14 14 1
A test_raw_home_url_is_https_when_is_ssl() 19 19 1
A test_user_can_stop_raw_urls() 0 15 1
B test_plugin_action_links_get_synced() 0 59 3
A extract_plugins_we_are_testing() 0 11 5
A cause_fatal_error() 0 5 1
A test_fixes_fatal_error() 0 14 1
A return_filtered_url() 0 3 1
A add_www_subdomain_to_siteurl() 0 5 1
A test_taxonomies_objects_do_not_have_meta_box_callback() 0 30 2
A test_force_sync_callable_on_plugin_update() 0 36 1
A test_xml_rpc_request_callables_has_actor() 0 18 1
A mock_authenticated_xml_rpc() 0 39 1
A mock_authenticated_xml_rpc_cleanup() 0 20 1
A mock_jetpack_private_options() 7 7 1
A test_get_timezone_from_timezone_string() 0 5 1
A test_get_timezone_from_gmt_offset_zero() 0 5 1
A test_get_timezone_from_gmt_offset_plus() 0 5 1
A test_get_timezone_from_gmt_offset_fractions() 0 5 1
A test_get_timezone_from_gmt_offset_minus() 0 5 1
A test_sync_callable_recursive_gets_checksum() 7 7 1
A test_get_hosting_provider_callable_with_unknown_host() 0 3 1
A test_get_hosting_provider_by_known_constant() 0 10 1
A test_get_hosting_provider_by_known_class() 0 11 1
A test_get_hosting_provider_by_known_function() 0 16 1
A test_get_main_network_site_wpcom_id_multisite() 0 26 2
A test_sync_does_not_send_updates_if_array_order_is_only_change() 17 17 1
A test_get_objects_by_id_all() 0 5 1
A reorder_array_keys() 0 10 1
A test_get_main_network_site_wpcom_id_single() 0 8 1
A test_get_objects_by_id_singular() 6 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_Test_Jetpack_Sync_Functions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WP_Test_Jetpack_Sync_Functions, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
4
use Automattic\Jetpack\Blocks;
5
use Automattic\Jetpack\Connection\Urls;
6
use Automattic\Jetpack\Constants;
7
use Automattic\Jetpack\Sync\Defaults;
8
use Automattic\Jetpack\Sync\Functions;
9
use Automattic\Jetpack\Sync\Modules;
10
use Automattic\Jetpack\Sync\Modules\Callables;
11
use Automattic\Jetpack\Sync\Modules\WP_Super_Cache;
12
use Automattic\Jetpack\Sync\Sender;
13
use Automattic\Jetpack\Sync\Settings;
14
15
16
require_once 'test_class.jetpack-sync-base.php';
17
18
function jetpack_foo_is_callable() {
19
	return 'bar';
20
}
21
22
/**
23
 * Returns an anonymous function for use in testing .
24
 */
25
function jetpack_foo_is_anon_callable() {
26
	$function = function () {
27
		return 'red';
28
	};
29
	return $function;
30
}
31
32
/**
33
 * Testing Functions
34
 */
35
class WP_Test_Jetpack_Sync_Functions extends WP_Test_Jetpack_Sync_Base {
36
	protected $post;
37
	protected $callable_module;
38
39
	protected static $admin_id; // used in mock_xml_rpc_request
40
41
	public function setUp() {
42
		parent::setUp();
43
44
		$this->resetCallableAndConstantTimeouts();
45
46
		$this->callable_module = Modules::get_module( "functions" );
47
		set_current_screen( 'post-user' ); // this only works in is_admin()
48
	}
49
50 View Code Duplication
	function test_white_listed_function_is_synced() {
51
		$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...
52
53
		$this->sender->do_sync();
54
55
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
56
		$this->assertEquals( jetpack_foo_is_callable(), $synced_value );
57
	}
58
59
	/**
60
	 * Verify that when a callable returns an anonymous function we don't fatal.
61
	 */
62 View Code Duplication
	public function test_anonymous_function_callable() {
63
		$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...
64
65
		$this->sender->do_sync();
66
67
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_foo_anon' );
68
		$this->assertEquals( null, $synced_value );
69
	}
70
71
	public function test_sync_jetpack_updates() {
72
		$this->sender->do_sync();
73
		$updates = $this->server_replica_storage->get_callable( 'updates' );
74
		$this->assertEqualsObject( Jetpack::get_updates(), $updates, 'The updates object should match' );
75
	}
76
77
78
	function test_wp_version_is_synced() {
79
		global $wp_version;
80
		$this->sender->do_sync();
81
		$synced_value = $this->server_replica_storage->get_callable( 'wp_version' );
82
		$this->assertEquals( $synced_value, $wp_version );
83
	}
84
85
	public function test_sync_callable_whitelist() {
86
		// $this->setSyncClientDefaults();
87
88
		add_filter( 'jetpack_set_available_extensions',  array( $this, 'add_test_block' ) );
89
		Jetpack_Gutenberg::init();
90
		Blocks::jetpack_register_block( 'jetpack/test' );
91
92
		$callables = array(
93
			'wp_max_upload_size'               => wp_max_upload_size(),
94
			'is_main_network'                  => Jetpack::is_multi_network(),
95
			'is_multi_site'                    => is_multisite(),
96
			'main_network_site'                => Urls::main_network_site_url(),
97
			'single_user_site'                 => Jetpack::is_single_user_site(),
98
			'updates'                          => Jetpack::get_updates(),
99
			'home_url'                         => Urls::home_url(),
100
			'site_url'                         => Urls::site_url(),
101
			'has_file_system_write_access'     => Functions::file_system_write_access(),
102
			'is_version_controlled'            => Functions::is_version_controlled(),
103
			'taxonomies'                       => Functions::get_taxonomies(),
104
			'post_types'                       => Functions::get_post_types(),
105
			'post_type_features'               => Functions::get_post_type_features(),
106
			'rest_api_allowed_post_types'      => Functions::rest_api_allowed_post_types(),
107
			'rest_api_allowed_public_metadata' => Functions::rest_api_allowed_public_metadata(),
108
			'sso_is_two_step_required'         => Jetpack_SSO_Helpers::is_two_step_required(),
109
			'sso_should_hide_login_form'       => Jetpack_SSO_Helpers::should_hide_login_form(),
110
			'sso_match_by_email'               => Jetpack_SSO_Helpers::match_by_email(),
111
			'sso_new_user_override'            => Jetpack_SSO_Helpers::new_user_override(),
112
			'sso_bypass_default_login_form'    => Jetpack_SSO_Helpers::bypass_login_forward_wpcom(),
113
			'wp_version'                       => Functions::wp_version(),
114
			'get_plugins'                      => Functions::get_plugins(),
115
			'get_plugins_action_links'         => Functions::get_plugins_action_links(),
116
			'active_modules'                   => Jetpack::get_active_modules(),
117
			'hosting_provider'                 => Functions::get_hosting_provider(),
118
			'locale'                           => get_locale(),
119
			'site_icon_url'                    => Functions::site_icon_url(),
120
			'shortcodes'                       => Functions::get_shortcodes(),
121
			'roles'                            => Functions::roles(),
122
			'timezone'                         => Functions::get_timezone(),
123
			'available_jetpack_blocks'         => Jetpack_Gutenberg::get_availability(),
124
			'paused_themes'                    => Functions::get_paused_themes(),
125
			'paused_plugins'                   => Functions::get_paused_plugins(),
126
			'main_network_site_wpcom_id'       => Functions::main_network_site_wpcom_id(),
127
			'theme_support'                    => Functions::get_theme_support(),
128
			'wp_get_environment_type'          => wp_get_environment_type(),
129
		);
130
131
		if ( function_exists( 'wp_cache_is_enabled' ) ) {
132
			$callables['wp_super_cache_globals'] = WP_Super_Cache::get_wp_super_cache_globals();
133
		}
134
135
		if ( is_multisite() ) {
136
			$callables['network_name']                        = Jetpack::network_name();
137
			$callables['network_allow_new_registrations']     = Jetpack::network_allow_new_registrations();
138
			$callables['network_add_new_users']               = Jetpack::network_add_new_users();
139
			$callables['network_site_upload_space']           = Jetpack::network_site_upload_space();
140
			$callables['network_upload_file_types']           = Jetpack::network_upload_file_types();
141
			$callables['network_enable_administration_menus'] = Jetpack::network_enable_administration_menus();
142
		}
143
144
		$this->sender->do_sync();
145
146
		foreach ( $callables as $name => $value ) {
147
			// TODO: figure out why _sometimes_ the 'support' value of
148
			// the post_types value is being removed from the output
149
			if ( $name === 'post_types' ) {
150
				continue;
151
			}
152
153
			$this->assertCallableIsSynced( $name, $value );
154
		}
155
156
		$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...
157
		$callables_keys = array_keys( $callables );
158
159
		// Are we testing all the callables in the defaults?
160
		$whitelist_and_callable_keys_difference = array_diff( $whitelist_keys, $callables_keys );
161
		$this->assertTrue( empty( $whitelist_and_callable_keys_difference ), 'Some whitelisted options don\'t have a test: ' . print_r( $whitelist_and_callable_keys_difference, 1 ) );
162
163
		// Are there any duplicate keys?
164
		$unique_whitelist = array_unique( $whitelist_keys );
165
		$this->assertEquals( count( $unique_whitelist ), count( $whitelist_keys ), 'The duplicate keys are: ' . print_r( array_diff_key( $whitelist_keys, array_unique( $whitelist_keys ) ), 1 ) );
166
167
		remove_filter( 'jetpack_set_available_extensions',  array( $this, 'add_test_block' ) );
168
		Jetpack_Gutenberg::reset();
169
	}
170
171
	public function add_test_block() {
172
		return array( 'test' );
173
	}
174
175
	function assertCallableIsSynced( $name, $value ) {
176
		$this->assertEqualsObject( $value, $this->server_replica_storage->get_callable( $name ), 'Function ' . $name . ' didn\'t have the expected value of ' . json_encode( $value ) );
177
	}
178
179
	function test_white_listed_callables_doesnt_get_synced_twice() {
180
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
181
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
182
		$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...
183
		$this->sender->do_sync();
184
185
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
186
		$this->assertEquals( 'bar', $synced_value );
187
188
		$this->server_replica_storage->reset();
189
190
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
191
		$this->sender->do_sync();
192
193
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'jetpack_foo' ) );
194
	}
195
196
	/**
197
	 * Tests that calling unlock_sync_callable_next_tick works as expected.
198
	 *
199
	 * Return null
200
	 */
201
	public function test_white_listed_callable_sync_on_next_tick() {
202
		// Setup...
203
		$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...
204
		$this->sender->do_sync();
205
		$initial_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
206
207
		// Action happends that should has the correct data only on the next page load.
208
		$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...
209
		$this->sender->do_sync();
210
		$should_be_initial_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
211
		$this->assertEquals( $initial_value, $should_be_initial_value );
212
213
		// Next tick...
214
		$this->sender->do_sync(); // This sync sends the updated data...
215
		$new_value = $this->server_replica_storage->get_callable( 'jetpack_foo' );
216
		$this->assertNotEquals( $initial_value, $new_value );
217
	}
218
219
	/**
220
	 * Tests that updating the theme should result in the no callabled transient being set.
221
	 *
222
	 * Return null
223
	 */
224
	public function test_updating_stylesheet_sends_the_theme_data() {
225
226
		// Make sure we don't already use this theme.
227
		$this->assertNotEquals( 'twentythirteen', get_option( 'stylesheet' ) );
228
229
		switch_theme( 'twentythirteen' );
230
		$this->sender->do_sync();
231
232
		// Since we can load up the data to see if new data will get send
233
		// this tests if we remove the transiant so that the data can get synced on the next tick.
234
		$this->assertFalse( get_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME ) );
235
	}
236
237
	function test_sync_always_sync_changes_to_modules_right_away() {
238
		Jetpack::update_active_modules( array( 'stats' ) );
239
240
		$this->sender->do_sync();
241
242
		$synced_value = $this->server_replica_storage->get_callable( 'active_modules' );
243
		$this->assertEquals(  array( 'stats' ), $synced_value  );
244
245
		$this->server_replica_storage->reset();
246
247
		Jetpack::update_active_modules( array( 'json-api' ) );
248
		$this->sender->do_sync();
249
250
		$synced_value = $this->server_replica_storage->get_callable( 'active_modules' );
251
		$this->assertEquals( array( 'json-api' ), $synced_value );
252
	}
253
254
	function test_sync_always_sync_changes_to_home_siteurl_right_away() {
255
		$original_home_option    = get_option( 'home' );
256
		$original_siteurl_option = get_option( 'siteurl' );
257
258
		// Let's see if the original values get synced
259
		$this->sender->do_sync();
260
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
261
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
262
263
		$this->assertEquals( $original_home_option, $synced_home_url );
264
		$this->assertEquals( $original_siteurl_option, $synced_site_url );
265
266
		$this->server_replica_storage->reset();
267
268
		$updated_home_option    = 'http://syncrocks.com';
269
		$updated_siteurl_option = 'http://syncrocks.com';
270
271
		update_option( 'home', $updated_home_option );
272
		update_option( 'siteurl', $updated_siteurl_option );
273
274
		$this->sender->do_sync();
275
276
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
277
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
278
279
		$this->assertEquals( $updated_home_option, $synced_home_url );
280
		$this->assertEquals( $updated_siteurl_option, $synced_site_url );
281
282
		// Cleanup
283
		update_option( 'home', $original_home_option );
284
		update_option( 'siteurl', $original_siteurl_option );
285
	}
286
287
	function test_sync_jetpack_sync_unlock_sync_callable_action_allows_syncing_siteurl_changes() {
288
		$original_home_option    = get_option( 'home' );
289
		$original_siteurl_option = get_option( 'siteurl' );
290
291
		// Let's see if the original values get synced. This will also set the await transient.
292
		$this->sender->do_sync();
293
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
294
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
295
296
		$this->assertEquals( $original_home_option, $synced_home_url );
297
		$this->assertEquals( $original_siteurl_option, $synced_site_url );
298
299
		$this->server_replica_storage->reset();
300
301
		update_option( 'home', $this->return_https_site_com_blog() );
302
		update_option( 'siteurl', $this->return_https_site_com_blog() );
303
304
		/**
305
		 * Used to signal that the callables await transient should be cleared. Clearing the await transient is useful
306
		 * in cases where we need to sync values to WordPress.com sooner than the default wait time.
307
		 *
308
		 * @since 4.4.0
309
		 */
310
		do_action( 'jetpack_sync_unlock_sync_callable' );
311
312
		$_SERVER['HTTPS'] = 'on';
313
314
		$this->sender->do_sync();
315
316
		$synced_home_url = $this->server_replica_storage->get_callable( 'home_url' );
317
		$synced_site_url = $this->server_replica_storage->get_callable( 'site_url' );
318
319
		$this->assertEquals( $this->return_https_site_com_blog(), $synced_home_url );
320
		$this->assertEquals( $this->return_https_site_com_blog(), $synced_site_url );
321
322
		// Cleanup
323
		unset( $_SERVER['HTTPS'] );
324
325
		update_option( 'home', $original_home_option );
326
		update_option( 'siteurl', $original_siteurl_option );
327
	}
328
329
	function test_home_site_urls_synced_while_migrate_for_idc_set() {
330
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
331
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
332
333
		$home_option    = get_option( 'home' );
334
		$siteurl_option = get_option( 'siteurl' );
335
		$main_network   = network_site_url();
336
337
		// First, let's see if the original values get synced
338
		$this->sender->do_sync();
339
340
		$this->assertEquals( $home_option,  $this->server_replica_storage->get_callable( 'home_url' ) );
341
		$this->assertEquals( $siteurl_option, $this->server_replica_storage->get_callable( 'site_url' ) );
342
		$this->assertEquals( $main_network, $this->server_replica_storage->get_callable( 'main_network_site' ) );
343
344
		// Second, let's make sure that values don't get synced again if the migrate_for_idc option is not set
345
		$this->server_replica_storage->reset();
346
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
347
		$this->sender->do_sync();
348
349
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'home_url' ) );
350
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'site_url' ) );
351
		$this->assertEquals( null, $this->server_replica_storage->get_callable( 'main_network_site' ) );
352
353
		// Third, let's test that values get syncd with the option set
354
		Jetpack_Options::update_option( 'migrate_for_idc', true );
355
356
		$this->server_replica_storage->reset();
357
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
358
		$this->sender->do_sync();
359
360
		$this->assertEquals( $home_option,  $this->server_replica_storage->get_callable( 'home_url' ) );
361
		$this->assertEquals( $siteurl_option, $this->server_replica_storage->get_callable( 'site_url' ) );
362
		$this->assertEquals( $main_network, $this->server_replica_storage->get_callable( 'main_network_site' ) );
363
364
		Jetpack_Options::delete_option( 'migrate_for_idc' );
365
	}
366
367
	function return_example_com() {
368
		return 'http://example.com';
369
	}
370
371
	function return_example_com_blog() {
372
		return 'http://example.com/blog';
373
	}
374
375
	function return_https_example_com() {
376
		return 'https://example.com';
377
	}
378
379
	function return_https_example_org() {
380
		return 'https://example.org';
381
	}
382
383
	function return_site_com() {
384
		return 'http://site.com';
385
	}
386
387
	function return_https_site_com() {
388
		return 'https://site.com';
389
	}
390
391
	function return_https_site_com_blog() {
392
		return 'https://site.com/blog';
393
	}
394
395
	function return_https_www_example_com() {
396
		return 'https://www.example.com';
397
	}
398
399
	function return_https_foo_example_com() {
400
		return 'https://foo.example.com';
401
	}
402
403
	function test_get_protocol_normalized_url_works_with_no_history() {
404
		$callable_type = 'home_url';
405
		$option_key    = Urls::HTTPS_CHECK_OPTION_PREFIX . $callable_type;
406
		delete_option( $option_key );
407
408
		$this->assertStringStartsWith(
409
			'http://',
410
			Urls::get_protocol_normalized_url( $callable_type, $this->return_example_com() )
411
		);
412
413
		delete_option( $option_key );
414
415
		$this->assertStringStartsWith(
416
			'https://',
417
			Urls::get_protocol_normalized_url( $callable_type, $this->return_https_example_com() )
418
		);
419
420
		$this->assertCount( 1, get_option( $option_key ) );
421
422
		delete_option( $option_key );
423
	}
424
425
	function test_get_protocol_normalized_url_stores_max_history() {
426
		$callable_type = 'home_url';
427
		$option_key    = Urls::HTTPS_CHECK_OPTION_PREFIX . $callable_type;
428
		delete_option( $option_key );
429
		for ( $i = 0; $i < 20; $i++ ) {
430
			Urls::get_protocol_normalized_url( $callable_type, $this->return_example_com() );
431
		}
432
433
		$this->assertCount( Urls::HTTPS_CHECK_HISTORY, get_option( $option_key ) );
434
		delete_option( $option_key );
435
	}
436
437
	function test_get_protocol_normalized_url_returns_http_when_https_falls_off() {
438
		$callable_type = 'home_url';
439
		$option_key    = Urls::HTTPS_CHECK_OPTION_PREFIX . $callable_type;
440
		delete_option( $option_key );
441
442
		// Start with one https scheme
443
		$this->assertStringStartsWith(
444
			'https://',
445
			Urls::get_protocol_normalized_url( $callable_type, $this->return_https_example_com() )
446
		);
447
448
		// Now add enough http schemes to fill up the history
449
		for ( $i = 1; $i < Urls::HTTPS_CHECK_HISTORY; $i++ ) {
450
			$this->assertStringStartsWith(
451
				'https://',
452
				Urls::get_protocol_normalized_url( $callable_type, $this->return_example_com() )
453
			);
454
		}
455
456
		// Now that the history is full, this one should cause the function to return false.
457
		$this->assertStringStartsWith(
458
			'http://',
459
			Urls::get_protocol_normalized_url( $callable_type, $this->return_example_com() )
460
		);
461
	}
462
463
	function test_get_protocol_normalized_url_returns_new_value_cannot_parse() {
464
		$test_url = 'http:///example.com';
465
		$this->assertEquals(
466
			$test_url,
467
			Urls::get_protocol_normalized_url( 'home_url', $test_url )
468
		);
469
	}
470
471
	function test_get_protocol_normalized_url_cleared_on_reset_data() {
472
		Urls::get_protocol_normalized_url( 'home_url', get_home_url() );
473
		Urls::get_protocol_normalized_url( 'site_url', get_site_url() );
474
		Urls::get_protocol_normalized_url( 'main_network_site_url', network_site_url() );
475
476
		$url_callables = array( 'home_url', 'site_url', 'main_network_site_url' );
477
		foreach( $url_callables as $callable ) {
478
			$this->assertInternalType( 'array', get_option( Urls::HTTPS_CHECK_OPTION_PREFIX . $callable ) );
479
		}
480
481
		Sender::get_instance()->uninstall();
482
483
		foreach( $url_callables as $callable ) {
484
			$this->assertFalse( get_option( Urls::HTTPS_CHECK_OPTION_PREFIX . $callable ) );
485
		}
486
	}
487
488
	function test_subdomain_switching_to_www_does_not_cause_sync() {
489
		// a lot of sites accept www.domain.com or just domain.com, and we want to prevent lots of
490
		// switching back and forth, so we force the domain to be the one in the siteurl option
491
		$this->setSyncClientDefaults();
492
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
493
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
494
495
		$original_site_url = site_url();
496
497
		// sync original value
498
		$this->sender->do_sync();
499
500
		$this->assertEquals( $original_site_url, $this->server_replica_storage->get_callable( 'site_url' ) );
501
502
		add_filter( 'site_url', array( $this, 'add_www_subdomain_to_siteurl' ) );
503
504
		delete_transient( Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
505
		delete_option( Callables::CALLABLES_CHECKSUM_OPTION_NAME );
506
		$this->sender->do_sync();
507
508
		$this->assertEquals( $original_site_url, $this->server_replica_storage->get_callable( 'site_url' ) );
509
	}
510
511 View Code Duplication
	function test_sync_limited_set_of_callables_if_cron() {
512
		$all_callables = array_keys( Defaults::get_callable_whitelist() );
513
		$always_updated = Callables::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS;
514
515
		foreach ( $always_updated as $key => $option ) {
516
			if ( array_key_exists( $option, Callables::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
517
				$always_updated[ $key ] = Callables::OPTION_NAMES_TO_CALLABLE_NAMES[ $option ];
518
			}
519
520
		}
521
522
		// non-admin
523
		set_current_screen( 'front' );
524
		Settings::set_doing_cron( true );
525
526
		$this->sender->do_sync();
527
528
		foreach ( $all_callables as $callable ) {
529
			if ( in_array( $callable, $always_updated, true ) ) {
530
				$this->assertNotNull( $this->server_replica_storage->get_callable( $callable ) );
531
			} else {
532
				$this->assertEquals( null, $this->server_replica_storage->get_callable( $callable ) );
533
			}
534
		}
535
536
		Settings::set_doing_cron( false );
537
	}
538
539 View Code Duplication
	function test_sync_limited_set_of_callables_if_wp_cli() {
540
		$all_callables = array_keys( Defaults::get_callable_whitelist() );
541
		$always_updated = Callables::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS;
542
543
		foreach ( $always_updated as $key => $option ) {
544
			if ( array_key_exists( $option, Callables::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
545
				$always_updated[ $key ] = Callables::OPTION_NAMES_TO_CALLABLE_NAMES[ $option ];
546
			}
547
548
		}
549
550
		// non-admin
551
		set_current_screen( 'front' );
552
		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...
553
554
		$this->sender->do_sync();
555
556
		foreach ( $all_callables as $callable ) {
557
			if ( in_array( $callable, $always_updated, true ) ) {
558
				$this->assertNotNull( $this->server_replica_storage->get_callable( $callable ) );
559
			} else {
560
				$this->assertEquals( null, $this->server_replica_storage->get_callable( $callable ) );
561
			}
562
		}
563
564
		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...
565
	}
566
567
	function test_site_icon_url_returns_false_when_no_site_icon() {
568
		delete_option( 'jetpack_site_icon_url' );
569
		$this->sender->do_sync();
570
		$this->assertFalse( $this->server_replica_storage->get_callable( 'site_icon_url' ) );
571
	}
572
573
	function test_site_icon_url_returns_core_site_icon_url_when_set() {
574
		$attachment_id = $this->factory->post->create( array(
575
			'post_type'      => 'attachment',
576
			'post_mime_type' => 'image/png',
577
		) );
578
		add_post_meta( $attachment_id, '_wp_attached_file', '2016/09/core_site_icon_url.png' );
579
		update_option( 'site_icon', $attachment_id );
580
		update_option( 'jetpack_site_icon_url', 'http://website.com/wp-content/uploads/2016/09/jetpack_site_icon.png' );
581
582
		$this->sender->do_sync();
583
584
		$this->assertContains( 'core_site_icon_url', $this->server_replica_storage->get_callable( 'site_icon_url' ) );
585
586
		delete_option( 'site_icon' );
587
	}
588
589
	function test_site_icon_url_fallback_to_jetpack_site_icon_url() {
590
		delete_option( 'site_icon' );
591
		update_option( 'jetpack_site_icon_url', 'http://website.com/wp-content/uploads/2016/09/jetpack_site_icon.png' );
592
		$this->sender->do_sync();
593
594
		$this->assertContains( 'jetpack_site_icon', $this->server_replica_storage->get_callable( 'site_icon_url' ) );
595
	}
596
597
	function test_calling_taxonomies_do_not_modify_global() {
598
		global $wp_taxonomies;
599
		// adds taxonomies.
600
		$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...
601
		$this->setSyncClientDefaults();
602
		$sync_callable_taxonomies = Functions::get_taxonomies();
603
604
		$this->assertNull( $sync_callable_taxonomies['example']->update_count_callback );
605
		$this->assertNull( $sync_callable_taxonomies['example']->meta_box_cb );
606
607
		$this->assertNotNull( $wp_taxonomies['example']->update_count_callback );
608
		$this->assertNotNull( $wp_taxonomies['example']->meta_box_cb );
609
610
	}
611
612
	function test_sanitize_sync_taxonomies_method() {
613
614
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'meta_box_cb' => 'post_tags_meta_box' ) );
615
		$this->assertEquals( $sanitized->meta_box_cb, 'post_tags_meta_box' );
616
617
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'meta_box_cb' => 'post_categories_meta_box' ) );
618
		$this->assertEquals( $sanitized->meta_box_cb, 'post_categories_meta_box' );
619
620
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'meta_box_cb' => 'banana' ) );
621
		$this->assertEquals( $sanitized->meta_box_cb, null );
622
623
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'update_count_callback' => 'banana' ) );
624
		$this->assertFalse( isset( $sanitized->update_count_callback ) );
625
626
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'rest_controller_class' => 'banana' ) );
627
		$this->assertEquals( $sanitized->rest_controller_class, null );
628
629
		$sanitized = Functions::sanitize_taxonomy( (object) array( 'rest_controller_class' => 'WP_REST_Terms_Controller' ) );
630
631
		$this->assertEquals( $sanitized->rest_controller_class, 'WP_REST_Terms_Controller' );
632
	}
633
634 View Code Duplication
	function test_sanitize_sync_post_type_method_default() {
635
		$label = 'foo_default';
636
		$post_type_object = new WP_Post_Type( $label );
637
		$post_type_object->add_supports();
638
		$post_type_object->add_rewrite_rules();
639
		$post_type_object->register_meta_boxes();
640
		$post_type_object->add_hooks();
641
		$post_type_object->register_taxonomies();
642
643
		$sanitized = Functions::sanitize_post_type( $post_type_object );
644
		$this->assert_sanitized_post_type_default( $sanitized, $label );
645
646
	}
647
648 View Code Duplication
	function test_sanitize_sync_post_type_method_remove_unknown_values_set() {
649
		$label = 'foo_strange';
650
		$post_type_object = new WP_Post_Type( $label, array( 'foo' => 'bar' ) );
651
		$post_type_object->add_supports();
652
		$post_type_object->add_rewrite_rules();
653
		$post_type_object->register_meta_boxes();
654
		$post_type_object->add_hooks();
655
		$post_type_object->register_taxonomies();
656
657
		$sanitized = Functions::sanitize_post_type( $post_type_object );
658
		$this->assert_sanitized_post_type_default( $sanitized, $label );
659
	}
660
661
	function assert_sanitized_post_type_default( $sanitized, $label ) {
662
		$this->assertEquals( $label, $sanitized->name );
663
		$this->assertEquals( 'Posts', $sanitized->label );
664
		$this->assertEquals( '', $sanitized->description );
665
		$this->assertEquals( $label, $sanitized->rewrite['slug'] );
666
		$this->assertEquals( $label, $sanitized->query_var );
667
		$this->assertEquals( 'post', $sanitized->capability_type );
668
		$this->assertEquals( array(), $sanitized->taxonomies );
669
		$this->assertEquals( array(), $sanitized->supports );
670
		$this->assertEquals( '', $sanitized->_edit_link );
671
672
		$this->assertFalse( $sanitized->public );
673
		$this->assertFalse( $sanitized->has_archive );
674
		$this->assertFalse( $sanitized->publicly_queryable );
675
		$this->assertFalse( $sanitized->hierarchical );
676
		$this->assertFalse( $sanitized->show_ui );
677
		$this->assertFalse( $sanitized->show_in_menu );
678
		$this->assertFalse( $sanitized->show_in_nav_menus );
679
		$this->assertFalse( $sanitized->show_in_admin_bar );
680
		$this->assertFalse( $sanitized->rest_base );
681
		$this->assertFalse( $sanitized->_builtin );
682
683
		$this->assertTrue( $sanitized->exclude_from_search );
684
		$this->assertTrue( $sanitized->can_export );
685
		$this->assertTrue( $sanitized->map_meta_cap );
686
		$this->assertTrue( is_object( $sanitized->labels ) );
687
		$this->assertTrue( is_array( $sanitized->rewrite ) );
688
		$this->assertTrue( is_object( $sanitized->cap ) );
689
690
	}
691
692
	function test_sanitize_sync_post_type_method_all_values_set() {
693
		$args = array(
694
			'labels'                => array(
695
				'stuff' => 'apple',
696
			),
697
			'description'           => 'banana',
698
			'public'                => true,
699
			'hierarchical'          => true,
700
			'exclude_from_search'   => false,
701
			'publicly_queryable'    => true,
702
			'show_ui'               => true,
703
			'show_in_menu'          => true,
704
			'show_in_nav_menus'     => true,
705
			'show_in_admin_bar'     => true,
706
			'menu_position'         => 10,
707
			'menu_icon'             => 'jetpack',
708
			'capability_type'       => 'foo',
709
			'capabilities'          => array( 'banana' => true ),
710
			'map_meta_cap'          => false,
711
			'supports'              => array( 'everything' ),
712
			'taxonomies'            => array( 'orange'),
713
			'has_archive'           => true,
714
			'rewrite'               => false,
715
			'query_var'             => 'foo_all_stuff',
716
			'can_export'            => false,
717
			'delete_with_user'      => true,
718
			'show_in_rest'          => true,
719
			'rest_base'             => 'foo_all_stuffing',
720
		);
721
		$post_type_object = new WP_Post_Type( 'foo_all', $args );
722
		$post_type_object->add_supports();
723
		$post_type_object->add_rewrite_rules();
724
		$post_type_object->register_meta_boxes();
725
		$post_type_object->add_hooks();
726
		$post_type_object->register_taxonomies();
727
728
		$sanitized = Functions::sanitize_post_type( $post_type_object );
729
		foreach( $args as $arg_key => $arg_value ) {
730
			//
731
			if ( in_array( $arg_key, array( 'labels', 'capabilities', 'supports' ) ) ) {
732
				continue;
733
			}
734
			$this->assertEquals( $arg_value, $sanitized->{ $arg_key }, 'Value for ' . $arg_key . 'not as expected' );
735
		}
736
	}
737
738
	function test_get_post_types_method() {
739
		global $wp_post_types;
740
		$synced = Functions::get_post_types();
741
		foreach( $wp_post_types as $post_type => $post_type_object ) {
742
			$post_type_object->rest_controller_class = false;
743
			$post_type_object->rest_controller       = null;
744
			if ( ! isset( $post_type_object->supports ) ) {
745
				$post_type_object->supports = array();
746
			}
747
			$synced_post_type = Functions::expand_synced_post_type( $synced[ $post_type ], $post_type );
748
			$this->assertEqualsObject( $post_type_object, $synced_post_type, 'POST TYPE :'. $post_type . ' not equal' );
749
		}
750
	}
751
752
	function test_register_post_types_callback_error() {
753
		register_post_type( 'testing', array( 'register_meta_box_cb' => function() {} ) );
754
		$this->sender->do_sync();
755
756
		$post_types =  $this->server_replica_storage->get_callable( 'post_types' );
757
		$this->assertTrue( isset( $post_types['testing'] ) );
758
	}
759
760
	function test_get_raw_url_by_option_bypasses_filters() {
761
		add_filter( 'option_home', array( $this, 'return_filtered_url' ) );
762
		$this->assertTrue( 'http://filteredurl.com' !== Urls::get_raw_url( 'home' ) );
763
		remove_filter( 'option_home', array( $this, 'return_filtered_url' ) );
764
	}
765
766
	function test_get_raw_url_by_constant_bypasses_filters() {
767
		Constants::set_constant( 'WP_HOME', 'http://constanturl.com' );
768
		Constants::set_constant( 'WP_SITEURL', 'http://constanturl.com' );
769
		add_filter( 'option_home', array( $this, 'return_filtered_url' ) );
770
		add_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
771
772
		if ( is_multisite() ) {
773
			$this->assertTrue( $this->return_filtered_url() !== Urls::get_raw_url( 'home' ) );
774
			$this->assertTrue( $this->return_filtered_url() !== Urls::get_raw_url( 'siteurl' ) );
775
		} else {
776
			$this->assertEquals( 'http://constanturl.com', Urls::get_raw_url( 'home' ) );
777
			$this->assertEquals( 'http://constanturl.com', Urls::get_raw_url( 'siteurl' ) );
778
		}
779
780
		remove_filter( 'option_home', array( $this, 'return_filtered_url' ) );
781
		remove_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
782
		Constants::clear_constants();
783
	}
784
785 View Code Duplication
	function test_get_raw_url_returns_with_http_if_is_ssl() {
786
		$home_option = get_option( 'home' );
787
788
		// Test without https first
789
		$this->assertEquals( $home_option, Urls::get_raw_url( 'home' ) );
790
791
		// Now, with https
792
		$_SERVER['HTTPS'] = 'on';
793
		$this->assertEquals(
794
			set_url_scheme( $home_option, 'http' ),
795
			Urls::get_raw_url( 'home' )
796
		);
797
		unset( $_SERVER['HTTPS'] );
798
	}
799
800 View Code Duplication
	function test_raw_home_url_is_https_when_is_ssl() {
801
		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...
802
803
		$home_option = get_option( 'home' );
804
805
		// Test without https first
806
		$this->assertEquals(
807
			$home_option,
808
			Urls::home_url()
809
		);
810
811
		// Now, with https
812
		$_SERVER['HTTPS'] = 'on';
813
		$this->assertEquals(
814
			set_url_scheme( $home_option, 'https' ),
815
			Urls::home_url()
816
		);
817
		unset( $_SERVER['HTTPS'] );
818
	}
819
820
	function test_user_can_stop_raw_urls() {
821
		add_filter( 'option_home', array( $this, 'return_filtered_url' ) );
822
		add_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
823
824
		// Test with constant first
825
		$this->assertTrue( 'http://filteredurl.com' !== Urls::home_url() );
826
827
		// Now, without, which should return the filtered URL
828
		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...
829
		$this->assertEquals( $this->return_filtered_url(), Urls::home_url() );
830
		Constants::clear_constants();
831
832
		remove_filter( 'option_home', array( $this, 'return_filtered_url' ) );
833
		remove_filter( 'option_siteurl', array( $this, 'return_filtered_url' ) );
834
	}
835
836
	function test_plugin_action_links_get_synced() {
837
		// Makes sure that we start fresh
838
		delete_transient( 'jetpack_plugin_api_action_links_refresh' );
839
		$helper_all = new Jetpack_Sync_Test_Helper();
840
841
		$helper_all->array_override = array( '<a href="fun.php">fun</a>' );
842
		add_filter( 'plugin_action_links', array( $helper_all, 'filter_override_array' ), 10 );
843
844
		$helper_jetpack = new Jetpack_Sync_Test_Helper();
845
		$helper_jetpack->array_override = array( '<a href="settings.php">settings</a>', '<a href="https://jetpack.com/support">support</a>' );
846
		add_filter( 'plugin_action_links_jetpack/jetpack.php', array( $helper_jetpack, 'filter_override_array' ), 10 );
847
848
		set_current_screen( 'banana' );
849
		// Let's see if the original values get synced
850
		$this->sender->do_sync();
851
852
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
853
854
		$expected_array = array(
855
			'hello.php' => array(
856
				'fun' => admin_url( 'fun.php' )
857
			),
858
			'jetpack/jetpack.php' => array(
859
				'settings' => admin_url( 'settings.php' ),
860
				'support' => 'https://jetpack.com/support'
861
			)
862
		);
863
864
		$this->assertEquals( $expected_array, $this->extract_plugins_we_are_testing( $plugins_action_links )  );
865
866
		$helper_all->array_override = array( '<a href="not-fun.php">not fun</a>' );
867
868
		$this->resetCallableAndConstantTimeouts();
869
870
		set_current_screen( 'banana' );
871
		$this->sender->do_sync();
872
873
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
874
875
		// Nothing should have changed since we cache the results.
876
		$this->assertEquals( $this->extract_plugins_we_are_testing( $plugins_action_links ), $expected_array );
877
878
		if ( file_exists( WP_PLUGIN_DIR . '/hello.php' ) ) {
879
			activate_plugin('hello.php', '', false, true );
880
		}
881
		if ( file_exists( WP_PLUGIN_DIR . '/hello-dolly/hello.php' ) ) {
882
			activate_plugin('hello-dolly/hello.php', '', false, true );
883
		}
884
885
		$this->resetCallableAndConstantTimeouts();
886
		set_current_screen( 'banana' );
887
		$this->sender->do_sync();
888
889
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
890
891
		// Links should have changes now since we activated the plugin.
892
		$expected_array['hello.php'] = array( 'not fun' => admin_url( 'not-fun.php' ) );
893
		$this->assertEquals( $this->extract_plugins_we_are_testing( $plugins_action_links ), $expected_array, 'Array was not updated to the new value as expected' );
894
	}
895
896
	function extract_plugins_we_are_testing( $plugins_action_links ) {
897
		$only_plugins_we_care_about = array();
898
		if ( isset( $plugins_action_links['hello.php'] ) ) {
899
			$only_plugins_we_care_about['hello.php'] = isset( $plugins_action_links['hello.php'] ) ? $plugins_action_links['hello.php'] : '';
900
		} else {
901
			$only_plugins_we_care_about['hello.php'] = isset( $plugins_action_links['hello-dolly/hello.php'] ) ? $plugins_action_links['hello-dolly/hello.php'] : '';
902
		}
903
904
		$only_plugins_we_care_about['jetpack/jetpack.php'] = isset( $plugins_action_links['jetpack/jetpack.php'] ) ? $plugins_action_links['jetpack/jetpack.php'] : '';
905
		return $only_plugins_we_care_about;
906
	}
907
908
	function cause_fatal_error( $actions ) {
909
		unset( $actions['activate'] );
910
		$actions[] = '<a href="/hello">world</a>';
911
		return $actions;
912
	}
913
914
	function test_fixes_fatal_error( ) {
915
916
		delete_transient( 'jetpack_plugin_api_action_links_refresh' );
917
		add_filter( 'plugin_action_links', array( $this, 'cause_fatal_error' ) );
918
919
		set_current_screen( 'plugins' );
920
921
		$this->resetCallableAndConstantTimeouts();
922
		set_current_screen( 'plugins' );
923
		$this->sender->do_sync();
924
		$plugins_action_links = $this->server_replica_storage->get_callable( 'get_plugins_action_links' );
925
		$plugins_action_links = $this->extract_plugins_we_are_testing( $plugins_action_links );
926
		$this->assertTrue( isset( $plugins_action_links['hello.php']['world'] ), 'World is not set' );
927
	}
928
929
	/**
930
	 * Return "http://filteredurl.com".
931
	 *
932
	 * @return string
933
	 */
934
	public function return_filtered_url() {
935
		return 'http://filteredurl.com';
936
	}
937
938
	/**
939
	 * Add a "www" subdomain to a URL.
940
	 *
941
	 * @param string $url URL.
942
	 * @return string
943
	 */
944
	public function add_www_subdomain_to_siteurl( $url ) {
945
		$parsed_url = wp_parse_url( $url );
946
947
		return "{$parsed_url['scheme']}://www.{$parsed_url['host']}";
948
	}
949
950
	/**
951
	 * Test "taxonomies_objects_do_not_have_meta_box_callback".
952
	 */
953
	public function test_taxonomies_objects_do_not_have_meta_box_callback() {
954
955
		new ABC_FOO_TEST_Taxonomy_Example();
956
		$taxonomies = Functions::get_taxonomies();
957
		$taxonomy   = $taxonomies['example'];
958
959
		$this->assertInternalType( 'object', $taxonomy );
960
		// Did we get rid of the expected attributes?
961
		$this->assertNull( $taxonomy->update_count_callback, 'example has the update_count_callback attribute, which should be removed since it is a callback' );
962
		$this->assertNull( $taxonomy->meta_box_cb, 'example has the meta_box_cb attribute, which should be removed since it is a callback' );
963
		$this->assertNull( $taxonomy->rest_controller_class );
964
		// Did we preserve the expected attributes?
965
		$check_object_vars = array(
966
			'labels',
967
			'description',
968
			'public',
969
			'publicly_queryable',
970
			'hierarchical',
971
			'show_ui',
972
			'show_in_menu',
973
			'show_in_nav_menus',
974
			'show_tagcloud',
975
			'show_in_quick_edit',
976
			'show_admin_column',
977
			'rewrite',
978
		);
979
		foreach ( $check_object_vars as $test ) {
980
			$this->assertObjectHasAttribute( $test, $taxonomy, "Taxonomy does not have expected {$test} attribute." );
981
		}
982
	}
983
984
	/**
985
	 * Test "force_sync_callable_on_plugin_update".
986
	 */
987
	public function test_force_sync_callable_on_plugin_update() {
988
		// fake the cron so that we really prevent the callables from being called.
989
		Settings::$is_doing_cron = true;
990
991
		$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...
992
		$this->sender->do_sync();
993
		$this->server_replica_storage->get_callable( 'jetpack_foo' );
994
995
		$this->server_replica_storage->reset();
996
997
		$synced_value2 = $this->server_replica_storage->get_callable( 'jetpack_foo' );
998
		$this->assertEmpty( $synced_value2 );
999
1000
		$upgrader = (object) array(
1001
			'skin' => (object) array(
1002
				'result' => new WP_Error( 'fail', 'Fail' ),
1003
			),
1004
		);
1005
1006
		do_action(
1007
			'upgrader_process_complete',
1008
			$upgrader,
1009
			array(
1010
				'action'  => 'update',
1011
				'type'    => 'plugin',
1012
				'bulk'    => true,
1013
				'plugins' => array( 'the/the.php' ),
1014
			)
1015
		);
1016
1017
		$this->sender->do_sync();
1018
		$synced_value3           = $this->server_replica_storage->get_callable( 'jetpack_foo' );
1019
		Settings::$is_doing_cron = false;
1020
		$this->assertNotEmpty( $synced_value3, 'value is empty!' );
1021
1022
	}
1023
1024
	/**
1025
	 * Test "xml_rpc_request_callables_has_actor".
1026
	 */
1027
	public function test_xml_rpc_request_callables_has_actor() {
1028
		$this->server_event_storage->reset();
1029
		$user = wp_get_current_user();
1030
		wp_set_current_user( 0 ); //
1031
		$this->sender->do_sync();
1032
		$event = $this->server_event_storage->get_most_recent_event( 'jetpack_sync_callable' );
1033
		$this->assertEquals( $event->user_id, 0, ' Callables user_id is null' );
1034
1035
		$this->resetCallableAndConstantTimeouts();
1036
		$this->mock_authenticated_xml_rpc(); // mock requet
1037
		$this->sender->do_sync();
1038
1039
		$event = $this->server_event_storage->get_most_recent_event( 'jetpack_sync_callable' );
1040
		// clean up by unsetting globals, etc. set previously by $this->mock_authenticated_xml_rpc()
1041
		$this->mock_authenticated_xml_rpc_cleanup( $user->ID );
1042
1043
		$this->assertEquals( $event->user_id, self::$admin_id, ' Callables XMLRPC_Reqeust not equal to event user_id' );
1044
	}
1045
1046
	/**
1047
	 * Mock authenticated XML RPC.
1048
	 */
1049
	public function mock_authenticated_xml_rpc() {
1050
		self::$admin_id = $this->factory->user->create( array(
1051
			'role' => 'administrator',
1052
		) );
1053
1054
		add_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10, 2 );
1055
		$_GET['token']     = 'pretend_this_is_valid:1:' . self::$admin_id;
1056
		$_GET['timestamp'] = (string) time();
1057
		$_GET['nonce']     = 'testing123';
1058
1059
		$_SERVER['REQUEST_URI']        = '/xmlrpc.php';
1060
		$_GET['body']                  = 'abc';
1061
		$_GET['body-hash']             = base64_encode( sha1( 'abc', true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1062
		$GLOBALS['HTTP_RAW_POST_DATA'] = 'abc'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1063
		$_SERVER['REQUEST_METHOD']     = 'POST';
1064
1065
		$normalized_request_pieces = array(
1066
			$_GET['token'],
1067
			$_GET['timestamp'],
1068
			$_GET['nonce'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1069
			$_GET['body-hash'],
1070
			'POST',
1071
			'example.org',
1072
			'80',
1073
			'/xmlrpc.php',
1074
		);
1075
		$normalize                 = join( "\n", $normalized_request_pieces ) . "\n";
1076
1077
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1078
		$_GET['signature'] = base64_encode( hash_hmac( 'sha1', $normalize, 'secret', true ) );
1079
1080
		// call one of the authenticated endpoints
1081
		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...
1082
		Jetpack::init();
1083
		$connection = Jetpack::connection();
1084
		$connection->xmlrpc_methods( array() );
1085
		$connection->require_jetpack_authentication();
1086
		$connection->verify_xml_rpc_signature();
1087
	}
1088
1089
	/**
1090
	 * Mock authenticated XML RPC cleanup.
1091
	 *
1092
	 * @param int $user_id User ID.
1093
	 */
1094
	public function mock_authenticated_xml_rpc_cleanup( $user_id ) {
1095
		Constants::clear_constants();
1096
		remove_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 );
1097
1098
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
1099
		unset( $_GET['token'] );
1100
		unset( $_GET['timestamp'] );
1101
		unset( $_GET['nonce'] );
1102
		$_SERVER['REQUEST_URI'] = '';
1103
		unset( $_GET['body'] );
1104
		unset( $_GET['body-hash'] );
1105
		unset( $GLOBALS['HTTP_RAW_POST_DATA'] );
1106
		unset( $_SERVER['REQUEST_METHOD'] );
1107
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
1108
1109
		Connection_Rest_Authentication::init()->reset_saved_auth_state();
1110
		Jetpack::connection()->reset_raw_post_data();
1111
		wp_set_current_user( $user_id );
1112
		self::$admin_id = null;
1113
	}
1114
1115
	/**
1116
	 * Mock Jetpack private options.
1117
	 */
1118 View Code Duplication
	public function mock_jetpack_private_options() {
1119
		$user_tokens                    = array();
1120
		$user_tokens[ self::$admin_id ] = 'pretend_this_is_valid.secret.' . self::$admin_id;
1121
		return array(
1122
			'user_tokens' => $user_tokens,
1123
		);
1124
	}
1125
1126
	/**
1127
	 * Test "get_timezone_from_timezone_string".
1128
	 */
1129
	public function test_get_timezone_from_timezone_string() {
1130
		update_option( 'timezone_string', 'America/Rankin_Inlet' );
1131
		update_option( 'gmt_offset', '' );
1132
		$this->assertEquals( 'America/Rankin Inlet', Functions::get_timezone() );
1133
	}
1134
1135
	/**
1136
	 * Test "get_timezone_from_gmt_offset_zero".
1137
	 */
1138
	public function test_get_timezone_from_gmt_offset_zero() {
1139
		update_option( 'timezone_string', '' );
1140
		update_option( 'gmt_offset', '0' );
1141
		$this->assertEquals( 'UTC+0', Functions::get_timezone() );
1142
	}
1143
1144
	/**
1145
	 * Test "get_timezone_from_gmt_offset_plus".
1146
	 */
1147
	public function test_get_timezone_from_gmt_offset_plus() {
1148
		update_option( 'timezone_string', '' );
1149
		update_option( 'gmt_offset', '1' );
1150
		$this->assertEquals( 'UTC+1', Functions::get_timezone() );
1151
	}
1152
1153
	/**
1154
	 * Test "get_timezone_from_gmt_offset_fractions".
1155
	 */
1156
	public function test_get_timezone_from_gmt_offset_fractions() {
1157
		update_option( 'timezone_string', '' );
1158
		update_option( 'gmt_offset', '5.5' );
1159
		$this->assertEquals( 'UTC+5:30', Functions::get_timezone() );
1160
	}
1161
1162
	/**
1163
	 * Test "get_timezone_from_gmt_offset_minus".
1164
	 */
1165
	public function test_get_timezone_from_gmt_offset_minus() {
1166
		update_option( 'timezone_string', '' );
1167
		update_option( 'gmt_offset', '-1' );
1168
		$this->assertEquals( 'UTC-1', Functions::get_timezone() );
1169
	}
1170
1171
	/**
1172
	 * Test "sync_callable_recursive_gets_checksum".
1173
	 */
1174 View Code Duplication
	public function test_sync_callable_recursive_gets_checksum() {
1175
1176
		$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...
1177
		$this->sender->do_sync();
1178
		$synced_value = $this->server_replica_storage->get_callable( 'jetpack_banana' );
1179
		$this->assertTrue( ! empty( $synced_value ), 'We couldn\'t synced a value!' );
1180
	}
1181
1182
	/**
1183
	 * Test get_hosting_provider() callable to ensure that known hosts have the
1184
	 * right hosting provider returned.
1185
	 *
1186
	 * @return void
1187
	 */
1188
	public function test_get_hosting_provider_callable_with_unknown_host() {
1189
		$this->assertEquals( Functions::get_hosting_provider(), 'unknown' );
1190
	}
1191
1192
	/**
1193
	 * Test getting a hosting provider by a known constant
1194
	 *
1195
	 * @return void
1196
	 */
1197
	public function test_get_hosting_provider_by_known_constant() {
1198
		$functions = new Functions();
1199
		Constants::set_constant( 'GD_SYSTEM_PLUGIN_DIR', 'set' );
1200
		$this->assertEquals( $functions->get_hosting_provider_by_known_constant(), 'gd-managed-wp' );
1201
		Constants::clear_constants();
1202
1203
		Constants::set_constant( 'UNKNOWN', 'set' );
1204
		$this->assertFalse( $functions->get_hosting_provider_by_known_constant() );
1205
		Constants::clear_constants();
1206
	}
1207
1208
	/**
1209
	 * Test getting a hosting provider by a known class
1210
	 *
1211
	 * @return void
1212
	 */
1213
	public function test_get_hosting_provider_by_known_class() {
1214
		$functions = new Functions();
1215
1216
		$this->assertFalse( $functions->get_hosting_provider_by_known_class() );
1217
1218
		$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...
1219
					->getMock(); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1220
1221
		$this->assertEquals( $functions->get_hosting_provider_by_known_class(), 'gd-managed-wp' );
1222
1223
	}
1224
1225
	/**
1226
	 * Test getting a hosting provider by a known function
1227
	 *
1228
	 * @return bool
1229
	 */
1230
	public function test_get_hosting_provider_by_known_function() {
1231
1232
		/**
1233
		 * Stub is_wpe for testing function exists
1234
		 *
1235
		 * @return boolean
1236
		 */
1237
		function is_wpe() { // phpcs:ignore MediaWiki.Usage.NestedFunctions.NestedFunction
1238
			return true;
1239
		}
1240
1241
		$functions = new Functions();
1242
1243
		// Get hosting provider by known function.
1244
		$this->assertEquals( $functions->get_hosting_provider_by_known_function(), 'wpe' );
1245
	}
1246
1247
	/**
1248
	 * Test getting the main network site wpcom ID in multisite installs
1249
	 *
1250
	 * @return void
1251
	 */
1252
	public function test_get_main_network_site_wpcom_id_multisite() {
1253
		if ( ! is_multisite() ) {
1254
			$this->markTestSkipped( 'Only used on multisite' );
1255
		}
1256
1257
		// set the Jetpack ID for this site.
1258
		$main_network_wpcom_id = 12345;
1259
		\Jetpack_Options::update_option( 'id', $main_network_wpcom_id );
1260
1261
		$user_id = $this->factory->user->create();
1262
1263
		// NOTE this is necessary because WPMU causes certain assumptions about transients.
1264
		// to be wrong, and tests to explode. @see: https://github.com/sheabunge/WordPress/commit/ff4f1bb17095c6af8a0f35ac304f79074f3c3ff6 .
1265
		global $wpdb;
1266
1267
		$suppress      = $wpdb->suppress_errors();
1268
		$other_blog_id = wpmu_create_blog( 'foo.com', '', 'My Blog', $user_id );
1269
		$wpdb->suppress_errors( $suppress );
1270
1271
		switch_to_blog( $other_blog_id );
1272
1273
		$functions = new Functions();
1274
		$this->assertEquals( $main_network_wpcom_id, $functions->main_network_site_wpcom_id() );
1275
1276
		restore_current_blog();
1277
	}
1278
1279
	/**
1280
	 * Verify get_check_sum is consistent for differently ordered arrays.
1281
	 */
1282 View Code Duplication
	public function test_sync_does_not_send_updates_if_array_order_is_only_change() {
1283
		$plugins = Functions::get_plugins();
1284
1285
		// Let's see if the original values get synced.
1286
		$this->sender->do_sync();
1287
		$plugins_synced = $this->server_replica_storage->get_callable( 'get_plugins' );
1288
1289
		$this->assertEquals( $plugins, $plugins_synced );
1290
1291
		add_filter( 'all_plugins', array( $this, 'reorder_array_keys' ), 100, 1 );
1292
		do_action( 'jetpack_sync_unlock_sync_callable' );
1293
		$this->server_event_storage->reset();
1294
		$this->sender->do_sync();
1295
1296
		$event = $this->server_event_storage->get_most_recent_event( 'jetpack_sync_callable' );
1297
		$this->assertFalse( $event );
1298
	}
1299
1300
	/**
1301
	 * Verify that all options are returned by get_objects_by_id
1302
	 */
1303
	public function test_get_objects_by_id_all() {
1304
		$module        = Modules::get_module( 'functions' );
1305
		$all_callables = $module->get_objects_by_id( 'callable', array( 'all' ) );
1306
		$this->assertEquals( $module->get_all_callables(), $all_callables );
1307
	}
1308
1309
	/**
1310
	 * Verify that get_object_by_id returns a allowed option
1311
	 */
1312 View Code Duplication
	public function test_get_objects_by_id_singular() {
1313
		$module       = Modules::get_module( 'functions' );
1314
		$callables    = $module->get_all_callables();
1315
		$get_callable = $module->get_objects_by_id( 'callable', array( 'has_file_system_write_access' ) );
1316
		$this->assertEquals( $callables['has_file_system_write_access'], $get_callable['has_file_system_write_access'] );
1317
	}
1318
1319
	/**
1320
	 * Reorder the get_plugins array keys.
1321
	 *
1322
	 * @param array $plugins array of plugins.
1323
	 *
1324
	 * @return array
1325
	 */
1326
	public function reorder_array_keys( $plugins ) {
1327
		// First plugin in array.
1328
		$plugin_key = array_keys( $plugins )[0];
1329
1330
		// reverse the 1st plugin's array entries.
1331
		$plugins[ $plugin_key ] = array_reverse( $plugins[ $plugin_key ] );
1332
1333
		// reverse the full array.
1334
		return array_reverse( $plugins );
1335
	}
1336
1337
	/**
1338
	 * Test getting the main network site wpcom ID in single site installs
1339
	 *
1340
	 * @return void
1341
	 */
1342
	public function test_get_main_network_site_wpcom_id_single() {
1343
		// set the Jetpack ID for this site.
1344
		$main_network_wpcom_id = 7891011;
1345
		\Jetpack_Options::update_option( 'id', $main_network_wpcom_id );
1346
1347
		$functions = new Functions();
1348
		$this->assertEquals( $main_network_wpcom_id, $functions->main_network_site_wpcom_id() );
1349
	}
1350
1351
}
1352
1353
/**
1354
 * Create a recursive object.
1355
 *
1356
 * @return object
1357
 */
1358
function jetpack_recursive_banana() {
1359
	$banana        = new stdClass();
1360
	$banana->arr   = array();
1361
	$banana->arr[] = $banana;
1362
	return $banana;
1363
}
1364
1365
/**
1366
 * Return a "random" number.
1367
 *
1368
 * Previously just returned `rand()`. I'm guessing something is trying to test
1369
 * caching or cache busting by having a different value returned each time, so
1370
 * let's do that reliably.
1371
 *
1372
 * @return int
1373
 */
1374
function jetpack_foo_is_callable_random() {
1375
	static $value = null;
1376
1377
	if ( null === $value ) {
1378
		$value = wp_rand();
1379
	}
1380
1381
	return $value++;
1382
}
1383
1384
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
1385
/**
1386
 * Example Test Taxonomy
1387
 */
1388
class ABC_FOO_TEST_Taxonomy_Example {
1389
1390
	/**
1391
	 * Constructor. Duh.
1392
	 */
1393
	public function __construct() {
1394
1395
		register_taxonomy(
1396
			'example',
1397
			'posts',
1398
			array(
1399
				'meta_box_cb'           => 'bob',
1400
				'update_count_callback' => array( $this, 'callback_update_count_callback_tags' ), // phpcs:ignore WordPress.Arrays.CommaAfterArrayItem.NoComma
1401
				'rest_controller_class' => 'tom',
1402
			)
1403
		);
1404
	}
1405
1406
	/**
1407
	 * `update_count_callback` callback.
1408
	 *
1409
	 * @return int
1410
	 */
1411
	public function callback_update_count_callback_tags() {
1412
		return 123;
1413
	}
1414
1415
	/**
1416
	 * Prevent this class being used as part of a Serialization injection attack
1417
	 */
1418
	public function __clone() {
1419
		wp_die( 'Please don\'t __clone ' . __CLASS__ );
1420
	}
1421
1422
	/**
1423
	 * Prevent this class being used as part of a Serialization injection attack
1424
	 */
1425
	public function __wakeup() {
1426
		wp_die( 'Please don\'t __wakeup ' . __CLASS__ );
1427
	}
1428
}
1429