Completed
Push — add/sync-rest-2 ( 88d6a7...6a7bf2 )
by
unknown
10:06
created

Jetpack_Sync_Client::get_all_callables()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
require_once dirname( __FILE__ ) . '/class.jetpack-sync-deflate-codec.php';
3
require_once dirname( __FILE__ ) . '/class.jetpack-sync-queue.php';
4
require_once dirname( __FILE__ ) . '/class.jetpack-sync-full.php';
5
6
class Jetpack_Sync_Client {
7
	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...
8
	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...
9
	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...
10
	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...
11
	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...
12
	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...
13
	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...
14
15
	private $sync_queue;
16
	private $full_sync_client;
17
	private $codec;
18
	private $options_whitelist;
19
	private $constants_whitelist;
20
	private $meta_types = array( 'post' );
21
	private $callable_whitelist;
22
	private $network_options_whitelist;
23
24
	// singleton functions
25
	private static $instance;
26
27
	public static function getInstance() {
28
		if ( null === self::$instance ) {
29
			self::$instance = new self();
30
		}
31
		return self::$instance;
32
	}
33
34
	// this is necessary because you can't use "new" when you declare instance properties >:(
35
	protected function __construct() {
36
		$this->sync_queue          = new Jetpack_Sync_Queue( 'sync', self::$default_send_buffer_size );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 10 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...
37
		$this->set_full_sync_client( new Jetpack_Sync_Full( $this->sync_queue ) );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Sync_Full::__construct() has too many arguments starting with $this->sync_queue.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
38
		$this->codec               = new Jetpack_Sync_Deflate_Codec();
39
		$this->constants_whitelist = self::$default_constants_whitelist;
40
		$this->options_whitelist   = self::$default_options_whitelist;
41
		$this->callable_whitelist  = self::$default_callable_whitelist;
42
		$this->network_options_whitelist = self::$default_network_options_whitelist;
43
		$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...
44
		$this->init();
45
	}
46
47
	private function init() {
48
49
		$handler = array( $this, 'action_handler' );
50
51
		// constants
52
		add_action( 'jetpack_sync_current_constants', $handler, 10 );
53
54
		// functions
55
		add_action( 'jetpack_sync_current_callables', $handler, 10 );
56
57
		// posts
58
		add_action( 'wp_insert_post', $handler, 10, 3 );
59
		add_action( 'deleted_post', $handler, 10 );
60
61
		// comments
62
		add_action( 'wp_insert_comment', $handler, 10, 2 );
63
		add_action( 'deleted_comment', $handler, 10 );
64
		add_action( 'trashed_comment', $handler, 10 );
65
		add_action( 'spammed_comment', $handler, 10 );
66
67
		// even though it's messy, we implement these hooks because 
68
		// the edit_comment hook doesn't include the data
69
		// so this saves us a DB read for every comment event
70
		foreach ( array( '', 'trackback', 'pingback' ) as $comment_type ) {
71
			foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
72
				add_action( "comment_{$comment_status}_{$comment_type}", $handler, 10, 2 );
73
			}
74
		}
75
76
		// options
77
		add_action( 'added_option', $handler, 10, 2 );
78
		add_action( 'updated_option', $handler, 10, 3 );
79
		add_action( 'deleted_option', $handler, 10, 1 );
80
81
		// themes
82
		add_action( 'jetpack_sync_current_theme_support', $handler, 10 ); // custom hook, see meta-hooks below
83
84
		// post-meta, and in the future - other meta?
85
		foreach ( $this->meta_types as $meta_type ) {
86
			// we need to make sure we don't commit before we receive these,
87
			// because they're invoked after meta changes are saved to the DB
88
			add_action( "added_{$meta_type}_meta", $handler, 99, 4 );
89
			add_action( "updated_{$meta_type}_meta", $handler, 99, 4 );
90
			add_action( "deleted_{$meta_type}_meta", $handler, 99, 4 );
91
		}
92
93
		// synthetic actions for full sync
94
		add_action( 'jp_full_sync_start', $handler, 10 );
95
		add_action( 'jp_full_sync_posts', $handler );
96
		add_action( 'jp_full_sync_comments', $handler );
97
98
		/**
99
		 * Other hooks - fire synthetic hooks for all the properties we need to sync,
100
		 * e.g. when a theme changes
101
		 */
102
103
		// themes
104
		add_action( 'switch_theme', array( $this, 'switch_theme_handler' ) );
105
106
		add_action( 'set_site_transient_update_plugins', $handler, 10, 1 );
107
		add_action( 'set_site_transient_update_themes', $handler, 10, 1 );
108
		add_action( 'set_site_transient_update_core', $handler, 10, 1 );
109
110
		// multi site network options
111
		if ( $this->is_multisite ) {
112
			add_action( 'add_site_option', $handler, 10, 2 );
113
			add_action( 'update_site_option', $handler, 10, 3 );
114
			add_action( 'delete_site_option', $handler, 10, 1 );
115
		}
116
117
		/**
118
		 * Sync all pending actions with server
119
		 */
120
		add_action( 'jetpack_sync_actions', array( $this, 'do_sync' ) );
121
	}
122
123
	function set_options_whitelist( $options ) {
124
		$this->options_whitelist = $options;
125
	}
126
127
	function set_constants_whitelist( $constants ) {
128
		$this->constants_whitelist = $constants;
129
	}
130
131
	function set_callable_whitelist( $functions ) {
132
		$this->callable_whitelist = $functions;
133
	}
134
135
	function set_network_options_whitelist( $options ) {
136
		$this->network_options_whitelist = $options;
137
	}
138
139
	function set_send_buffer_size( $size ) {
140
		$this->sync_queue->set_checkout_size( $size );
141
	}
142
143
	function is_whitelisted_option( $option ) {
144
		foreach ( $this->options_whitelist as $whitelisted_option ) {
145
			if ( $whitelisted_option[0] === '/' && preg_match( $whitelisted_option, $option ) ) {
146
				return true;
147
			} elseif ( $whitelisted_option === $option ) {
148
				return true;
149
			}
150
		}
151
152
		return false;
153
	}
154
155
	function is_whitelisted_network_option( $option ) {
156
		return $this->is_multisite && in_array( $option, $this->network_options_whitelist );
157
	}
158
159
	function set_codec( iJetpack_Sync_Codec $codec ) {
160
		$this->codec = $codec;
161
	}
162
163
	function set_full_sync_client( $full_sync_client ) {
164
		if ( $this->full_sync_client ) {
165
			remove_action( 'jetpack_sync_full', array( $this->full_sync_client, 'start' ) );
166
		}
167
168
		$this->full_sync_client = $full_sync_client;
169
170
		/**
171
		 * Sync all objects in the database with the server
172
		 */
173
		add_action( 'jetpack_sync_full', array( $this->full_sync_client, 'start' ) );
174
	}
175
176
	function action_handler() {
177
		// TODO: it's really silly to have this function here - it should be 
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...
178
		// wherever we initialize the action listeners or we're just wasting cycles
179
		if ( Jetpack::is_development_mode() || Jetpack::is_staging_site() ) {
180
			return false;
181
		}
182
183
		$current_filter = current_filter();
184
		$args           = func_get_args();
185
186
		if ( $current_filter === 'wp_insert_post' && $args[1]->post_type === 'revision' ) {
187
			return;
188
		}
189
190
		if ( in_array( $current_filter, array( 'deleted_option', 'added_option', 'updated_option' ) )
191
		     &&
192
		     ! $this->is_whitelisted_option( $args[0] )
193
		) {
194
			return;
195
		}
196
197
		if ( in_array( $current_filter, array( 'delete_site_option', 'add_site_option', 'update_site_option' ) )
198
		     &&
199
		     ! $this->is_whitelisted_network_option( $args[0] )
200
		) {
201
			return;
202
		}
203
204
		$this->sync_queue->add( array(
205
			$current_filter,
206
			$args
207
		) );
208
	}
209
210
	function switch_theme_handler() {
211
		global $_wp_theme_features;
212
213
		do_action( 'jetpack_sync_current_theme_support', $_wp_theme_features );
214
	}
215
216
	function do_sync() {
217
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
218
			$this->schedule_sync( "+1 minute" );
219
			return false;
220
		}
221
222
		$this->maybe_sync_constants();
223
		$this->maybe_sync_callables();
224
225
		$buffer = $this->sync_queue->checkout();
226
227
		if ( ! $buffer ) {
228
			// buffer has no items
229
			return;
230
		}
231
232
		if ( is_wp_error( $buffer ) ) {
233
			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...
234
			return;
235
		}
236
237
		$data = $this->codec->encode( $buffer->get_items() );
238
239
		/**
240
		 * Fires when data is ready to send to the server.
241
		 * Return false or WP_Error to abort the sync (e.g. if there's an error)
242
		 * The items will be automatically re-sent later
243
		 *
244
		 * @since 4.1
245
		 *
246
		 * @param array $data The action buffer
247
		 */
248
		$result = apply_filters( 'jetpack_sync_client_send_data', $data );
249
250
		if ( ! $result || is_wp_error( $result ) ) {
251
			$this->sync_queue->checkin( $buffer );
252
			// try again in 1 minute
253
			$this->schedule_sync( "+1 minute" );
254
		} else {
255
			$this->sync_queue->close( $buffer );
256
			// check if there are any more events in the buffer
257
			// if so, schedule a cron job to happen soon
258
			if ( $this->sync_queue->has_any_items() ) {
259
				$this->schedule_sync( "+1 minute" );
260
			}
261
		}
262
	}
263
264
	private function schedule_sync( $when ) {
265
		wp_schedule_single_event( strtotime( $when ), 'jetpack_sync_actions' );
266
	}
267
268
	function force_sync_constants() {
269
		delete_option( self::$constants_checksum_option_name );
270
		$this->maybe_sync_constants();
271
	}
272
273 View Code Duplication
	private function maybe_sync_constants() {
274
		$constants = $this->get_all_constants();
275
		if ( empty( $constants ) ) {
276
			return;
277
		}
278
		$constants_check_sum = $this->get_check_sum( $constants );
279
		if ( $constants_check_sum !== get_option( self::$constants_checksum_option_name ) ) {
280
			do_action( 'jetpack_sync_current_constants', $constants );
281
			update_option( self::$constants_checksum_option_name, $constants_check_sum );
282
		}
283
	}
284
285
	private function get_all_constants() {
286
		return array_combine(
287
			$this->constants_whitelist,
288
			array_map( array( $this, 'get_constant' ), $this->constants_whitelist )
289
		);
290
	}
291
292
	private function get_constant( $constant ) {
293
		if ( defined( $constant ) ) {
294
			return constant( $constant );
295
		}
296
297
		return null;
298
	}
299
300 View Code Duplication
	private function maybe_sync_callables() {
301
		$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...
302
		if ( empty( $callables ) ) {
303
			return;
304
		}
305
		$callables_check_sum = $this->get_check_sum( $callables );
306
307
		if ( $callables_check_sum !== get_option( self::$functions_checksum_option_name ) ) {
308
			do_action( 'jetpack_sync_current_callables', $callables  );
309
			update_option( self::$functions_checksum_option_name, $callables_check_sum );
310
		}
311
	}
312
313
	private function get_all_callables() {
314
		return array_combine(
315
			$this->callable_whitelist,
316
			array_map( array( $this, 'get_callable' ), $this->callable_whitelist )
317
		);
318
	}
319
	
320
	private function get_callable( $callable ) {
321
		return call_user_func( $callable );
322
	}
323
324
	private function get_check_sum( $values ) {
325
		return crc32( serialize( $values ) );
326
	}
327
328
	function get_actions() {
329
		// 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...
330
		return $this->sync_queue->flush_all();
331
	}
332
333
	function get_all_actions() {
334
		return $this->sync_queue->get_all();
335
	}
336
337
	function get_sync_queue() {
338
		return $this->sync_queue;
339
	}
340
341
	function reset_sync_queue() {
342
		$this->sync_queue->reset();
343
	}
344
345
	function reset_state() {
346
		$this->codec               = new Jetpack_Sync_Deflate_Codec();
347
		$this->constants_whitelist = self::$default_constants_whitelist;
348
		$this->options_whitelist   = self::$default_options_whitelist;
349
		$this->set_send_buffer_size( self::$default_send_buffer_size );
350
		delete_option( self::$constants_checksum_option_name );
351
		$this->reset_sync_queue();
352
	}
353
}
354