Completed
Push — add/sync-rest-2 ( 85c3b4...1b25af )
by
unknown
24:20 queued 15:11
created

Jetpack_Sync_Client   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 304
Duplicated Lines 7.57 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 26
Bugs 5 Features 6
Metric Value
wmc 51
c 26
b 5
f 6
lcom 2
cbo 4
dl 23
loc 304
rs 8.3206

25 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 6 2
A __construct() 0 10 1
B init() 0 68 5
A set_options_whitelist() 0 3 1
A set_constants_whitelist() 0 3 1
A set_callable_whitelist() 0 3 1
A set_network_options_whitelist() 0 3 1
A set_send_buffer_size() 0 3 1
B is_whitelisted_option() 0 11 5
A is_whitelisted_network_option() 0 3 2
A set_codec() 0 3 1
C action_handler() 0 28 7
A switch_theme_handler() 0 5 1
B do_sync() 0 43 6
A schedule_sync() 0 3 1
A maybe_sync_constants() 11 11 3
A get_all_constants() 0 6 1
A get_constant() 0 7 2
A maybe_sync_callables() 12 12 3
A get_all_callables() 0 6 1
A get_callable() 0 3 1
A get_check_sum() 0 3 1
A get_actions() 0 4 1
A get_all_actions() 0 3 1
A reset_state() 0 8 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 Jetpack_Sync_Client 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 Jetpack_Sync_Client, and based on these observations, apply Extract Interface, too.

1
<?php
2
require_once dirname( __FILE__ ) . '/class.jetpack-sync-deflate-codec.php';
3
require_once dirname( __FILE__ ) . '/class.jetpack-sync-queue.php';
4
5
class Jetpack_Sync_Client {
6
	static $default_options_whitelist = array( 'stylesheet', '/^theme_mods_.*$/' );
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $default_options_whitelist.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
7
	static $default_constants_whitelist = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $default_constants_whitelist.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
8
	static $default_callable_whitelist = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $default_callable_whitelist.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
9
	static $default_network_options_whitelist = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $default_network_options_whitelist.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
10
	static $constants_checksum_option_name = 'jetpack_constants_sync_checksum';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $constants_checksum_option_name.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
11
	static $functions_checksum_option_name = 'jetpack_functions_sync_checksum';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $functions_checksum_option_name.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
12
	static $default_send_buffer_size = 20;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $default_send_buffer_size.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
13
14
	private $sync_queue;
15
	private $codec;
16
	private $options_whitelist;
17
	private $constants_whitelist;
18
	private $meta_types = array( 'post' );
19
	private $callable_whitelist;
20
	private $network_options_whitelist;
21
22
	// singleton functions
23
	private static $instance;
24
25
	public static function getInstance() {
26
		if ( null === self::$instance ) {
27
			self::$instance = new self();
28
		}
29
		return self::$instance;
30
	}
31
32
	// this is necessary because you can't use "new" when you declare instance properties >:(
33
	protected function __construct() {
34
		$this->sync_queue          = new Jetpack_Sync_Queue( 'sync', self::$default_send_buffer_size );
35
		$this->codec               = new Jetpack_Sync_Deflate_Codec();
36
		$this->constants_whitelist = self::$default_constants_whitelist;
37
		$this->options_whitelist   = self::$default_options_whitelist;
38
		$this->callable_whitelist  = self::$default_callable_whitelist;
39
		$this->network_options_whitelist = self::$default_network_options_whitelist;
40
		$this->is_multisite = is_multisite();
0 ignored issues
show
Bug introduced by
The property is_multisite does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
41
		$this->init();
42
	}
43
44
	private function init() {
45
		$handler = array( $this, 'action_handler' );
46
47
		// constants
48
		add_action( 'jetpack_sync_current_constants', $handler, 10 );
49
50
		// functions
51
		add_action( 'jetpack_sync_current_callables', $handler, 10 );
52
53
		// posts
54
		add_action( 'wp_insert_post', $handler, 10, 3 );
55
		add_action( 'deleted_post', $handler, 10 );
56
57
		// comments
58
		add_action( 'wp_insert_comment', $handler, 10, 2 );
59
		add_action( 'deleted_comment', $handler, 10 );
60
		add_action( 'trashed_comment', $handler, 10 );
61
		add_action( 'spammed_comment', $handler, 10 );
62
63
		// even though it's messy, we implement these hooks because 
64
		// the edit_comment hook doesn't include the data
65
		// so this saves us a DB read for every comment event
66
		foreach ( array( '', 'trackback', 'pingback' ) as $comment_type ) {
67
			foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
68
				add_action( "comment_{$comment_status}_{$comment_type}", $handler, 10, 2 );
69
			}
70
		}
71
72
		// options
73
		add_action( 'added_option', $handler, 10, 2 );
74
		add_action( 'updated_option', $handler, 10, 3 );
75
		add_action( 'deleted_option', $handler, 10, 1 );
76
77
		// themes
78
		add_action( 'jetpack_sync_current_theme_support', $handler, 10 ); // custom hook, see meta-hooks below
79
80
		// post-meta, and in the future - other meta?
81
		foreach ( $this->meta_types as $meta_type ) {
82
			// we need to make sure we don't commit before we receive these,
83
			// because they're invoked after meta changes are saved to the DB
84
			add_action( "added_{$meta_type}_meta", $handler, 99, 4 );
85
			add_action( "updated_{$meta_type}_meta", $handler, 99, 4 );
86
			add_action( "deleted_{$meta_type}_meta", $handler, 99, 4 );
87
		}
88
89
		/**
90
		 * Other hooks - fire synthetic hooks for all the properties we need to sync,
91
		 * e.g. when a theme changes
92
		 */
93
94
		// themes
95
		add_action( 'switch_theme', array( $this, 'switch_theme_handler' ) );
96
97
		add_action( 'set_site_transient_update_plugins', $handler, 10, 1 );
98
		add_action( 'set_site_transient_update_themes', $handler, 10, 1 );
99
		add_action( 'set_site_transient_update_core', $handler, 10, 1 );
100
		// multi site network options
101
		if ( $this->is_multisite ) {
102
			add_action( 'add_site_option', $handler, 10, 2 );
103
			add_action( 'update_site_option', $handler, 10, 3 );
104
			add_action( 'delete_site_option', $handler, 10, 1 );
105
		}
106
107
		/**
108
		 * Sync all actions with server
109
		 */
110
		add_action( 'jetpack_sync_actions', array( $this, 'do_sync' ) );
111
	}
112
113
	function set_options_whitelist( $options ) {
114
		$this->options_whitelist = $options;
115
	}
116
117
	function set_constants_whitelist( $constants ) {
118
		$this->constants_whitelist = $constants;
119
	}
120
121
	function set_callable_whitelist( $functions ) {
122
		$this->callable_whitelist = $functions;
123
	}
124
125
	function set_network_options_whitelist( $options ) {
126
		$this->network_options_whitelist = $options;
127
	}
128
129
	function set_send_buffer_size( $size ) {
130
		$this->sync_queue->set_checkout_size( $size );
131
	}
132
133
	function is_whitelisted_option( $option ) {
134
		foreach ( $this->options_whitelist as $whitelisted_option ) {
135
			if ( $whitelisted_option[0] === '/' && preg_match( $whitelisted_option, $option ) ) {
136
				return true;
137
			} elseif ( $whitelisted_option === $option ) {
138
				return true;
139
			}
140
		}
141
142
		return false;
143
	}
144
145
	function is_whitelisted_network_option( $option ) {
146
		return $this->is_multisite && in_array( $option, $this->network_options_whitelist );
147
	}
148
149
	function set_codec( iJetpack_Sync_Codec $codec ) {
150
		$this->codec = $codec;
151
	}
152
153
	function action_handler() {
154
		$current_filter = current_filter();
155
		$args           = func_get_args();
156
157
		if ( $current_filter === 'wp_insert_post' && $args[1]->post_type === 'revision' ) {
158
			return;
159
		}
160
161
		if ( in_array( $current_filter, array( 'deleted_option', 'added_option', 'updated_option' ) )
162
		     &&
163
		     ! $this->is_whitelisted_option( $args[0] )
164
		) {
165
			return;
166
		}
167
168
		if ( in_array( $current_filter, array( 'delete_site_option', 'add_site_option', 'update_site_option' ) )
169
		     &&
170
		     ! $this->is_whitelisted_network_option( $args[0] )
171
		) {
172
			return;
173
		}
174
175
		Jetpack_Sync::schedule_sync();
176
		$this->sync_queue->add( array(
177
			$current_filter,
178
			$args
179
		) );
180
	}
181
182
	function switch_theme_handler() {
183
		global $_wp_theme_features;
184
185
		do_action( 'jetpack_sync_current_theme_support', $_wp_theme_features );
186
	}
187
188
	function do_sync() {
189
		$this->maybe_sync_constants();
190
		$this->maybe_sync_callables();
191
192
		$buffer = $this->sync_queue->checkout();
193
194
		if ( ! $buffer ) {
195
			// buffer has no items
196
			return;
197
		}
198
199
		if ( is_wp_error( $buffer ) ) {
200
			error_log( "Error fetching buffer: " . $buffer->get_error_message() );
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<Jetpack_Sync_Queue_Buffer>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
201
202
			return;
203
		}
204
205
		$data = $this->codec->encode( $buffer->get_items() );
206
207
		/**
208
		 * Fires when data is ready to send to the server.
209
		 * Return false or WP_Error to abort the sync (e.g. if there's an error)
210
		 * The items will be automatically re-sent later
211
		 *
212
		 * @since 4.1
213
		 *
214
		 * @param array $data The action buffer
215
		 */
216
		$result = apply_filters( 'jetpack_sync_client_send_data', $data );
217
218
		if ( ! $result || is_wp_error( $result ) ) {
219
			$this->sync_queue->checkin( $buffer );
220
			// try again in 1 minute
221
			$this->schedule_sync( "+1 minute" );
222
		} else {
223
			$this->sync_queue->close( $buffer );
224
			// check if there are any more events in the buffer
225
			// if so, schedule a cron job to happen soon
226
			if ( $this->sync_queue->has_any_items() ) {
227
				$this->schedule_sync( "+1 minute" );
228
			}
229
		}
230
	}
231
232
	private function schedule_sync( $when ) {
233
		wp_schedule_single_event( strtotime( $when ), 'jetpack_sync_actions' );
234
	}
235
236 View Code Duplication
	private function maybe_sync_constants() {
237
		$constants           = $this->get_all_constants();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 11 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
238
		if ( empty( $constants ) ) {
239
			return;
240
		}
241
		$constants_check_sum = $this->get_check_sum( $constants );
242
		if ( $constants_check_sum !== get_option( self::$constants_checksum_option_name ) ) {
243
			do_action( 'jetpack_sync_current_constants', $constants );
244
			update_option( self::$constants_checksum_option_name, $constants_check_sum );
245
		}
246
	}
247
248
	private function get_all_constants() {
249
		return array_combine(
250
			$this->constants_whitelist,
251
			array_map( array( $this, 'get_constant' ), $this->constants_whitelist )
252
		);
253
	}
254
255
	private function get_constant( $constant ) {
256
		if ( defined( $constant ) ) {
257
			return constant( $constant );
258
		}
259
260
		return null;
261
	}
262
263 View Code Duplication
	private function maybe_sync_callables() {
264
		$callables           = $this->get_all_callables();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 11 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
265
		if ( empty( $callables ) ) {
266
			return;
267
		}
268
		$callables_check_sum = $this->get_check_sum( $callables );
269
270
		if ( $callables_check_sum !== get_option( self::$functions_checksum_option_name ) ) {
271
			do_action( 'jetpack_sync_current_callables', $callables  );
272
			update_option( self::$functions_checksum_option_name, $callables_check_sum );
273
		}
274
	}
275
276
	private function get_all_callables() {
277
		return array_combine(
278
			$this->callable_whitelist,
279
			array_map( array( $this, 'get_callable' ), $this->callable_whitelist )
280
		);
281
	}
282
	
283
	private function get_callable( $callable ) {
284
		return call_user_func( $callable );
285
	}
286
287
	private function get_check_sum( $values ) {
288
		return crc32( serialize( $values ) );
289
	}
290
291
	function get_actions() {
292
		// TODO: we should only send a bit at a time, flush_all sends everything
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
293
		return $this->sync_queue->flush_all();
294
	}
295
296
	function get_all_actions() {
297
		return $this->sync_queue->get_all();
298
	}
299
300
	function reset_state() {
301
		$this->codec               = new Jetpack_Sync_Deflate_Codec();
302
		$this->constants_whitelist = self::$default_constants_whitelist;
303
		$this->options_whitelist   = self::$default_options_whitelist;
304
		$this->set_send_buffer_size( self::$default_send_buffer_size );
305
		delete_option( self::$constants_checksum_option_name );
306
		$this->sync_queue->reset();
307
	}
308
}
309