Completed
Push — add/sync-rest-2 ( b5463d...83f13e )
by
unknown
219:15 queued 210:11
created

Jetpack_Sync_Client::get_callable_whitelist()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 1
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-functions.php';
5
require_once dirname( __FILE__ ) . '/class.jetpack-sync-full.php';
6
7
class Jetpack_Sync_Client {
8
	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...
9
	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...
10
		'EMPTY_TRASH_DAYS',
11
		'WP_POST_REVISIONS',
12
		'AUTOMATIC_UPDATER_DISABLED',
13
		'ABSPATH',
14
		'WP_CONTENT_DIR',
15
		'FS_METHOD',
16
		'DISALLOW_FILE_EDIT',
17
		'DISALLOW_FILE_MODS',
18
		'WP_AUTO_UPDATE_CORE',
19
		'WP_HTTP_BLOCK_EXTERNAL',
20
		'WP_ACCESSIBLE_HOSTS',
21
		'JETPACK__VERSION'
22
	);
23
	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...
24
		'wp_max_upload_size' => 'wp_max_upload_size',
25
		'is_main_network' => array( 'Jetpack', 'is_multi_network' ),
26
		'is_multi_site' => 'is_multisite',
27
		'main_network_site' => 'network_site_url',
28
		'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
29
		'has_file_system_write_access' => array( 'Jetpack_Sync_Functions', 'file_system_write_access' ),
30
		'is_version_controlled' => array( 'Jetpack_Sync_Functions', 'is_version_controlled' ),
31
		'modules' => array( 'Jetpack_Sync_Functions', 'get_modules' )
32
	);
33
	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...
34
	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...
35
	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...
36
	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...
37
	static $default_taxonomy_whitelist = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $default_taxonomy_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...
38
39
	private $sync_queue;
40
	private $full_sync_client;
41
	private $codec;
42
	private $options_whitelist;
43
	private $constants_whitelist;
44
	private $meta_types = array( 'post' );
45
	private $callable_whitelist;
46
	private $network_options_whitelist;
47
	private $taxonomy_whitelist;
48
49
	// singleton functions
50
	private static $instance;
51
52
	public static function getInstance() {
53
		if ( null === self::$instance ) {
54
			self::$instance = new self();
55
		}
56
57
		return self::$instance;
58
	}
59
60
	// this is necessary because you can't use "new" when you declare instance properties >:(
61
	protected function __construct() {
62
		$this->reset_state();
63
		$this->init();
64
	}
65
66
	private function init() {
67
68
		$handler = array( $this, 'action_handler' );
69
70
		// constants
71
		add_action( 'jetpack_sync_current_constants', $handler, 10 );
72
73
		// functions
74
		add_action( 'jetpack_sync_current_callables', $handler, 10 );
75
76
		// posts
77
		add_action( 'wp_insert_post', $handler, 10, 3 );
78
		add_action( 'deleted_post', $handler, 10 );
79
80
		// comments
81
		add_action( 'wp_insert_comment', $handler, 10, 2 );
82
		add_action( 'deleted_comment', $handler, 10 );
83
		add_action( 'trashed_comment', $handler, 10 );
84
		add_action( 'spammed_comment', $handler, 10 );
85
86
		// even though it's messy, we implement these hooks because 
87
		// the edit_comment hook doesn't include the data
88
		// so this saves us a DB read for every comment event
89
		foreach ( array( '', 'trackback', 'pingback' ) as $comment_type ) {
90
			foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
91
				add_action( "comment_{$comment_status}_{$comment_type}", $handler, 10, 2 );
92
			}
93
		}
94
95
		// options
96
		add_action( 'added_option', $handler, 10, 2 );
97
		add_action( 'updated_option', $handler, 10, 3 );
98
		add_action( 'deleted_option', $handler, 10, 1 );
99
100
		// themes
101
		add_action( 'switch_theme', array( $this, 'send_theme_info' ) );
102
		add_action( 'jetpack_sync_current_theme_support', $handler, 10 ); // custom hook, see meta-hooks below
103
104
		// post-meta, and in the future - other meta?
105
		foreach ( $this->meta_types as $meta_type ) {
106
			// we need to make sure we don't commit before we receive these,
107
			// because they're invoked after meta changes are saved to the DB
108
			add_action( "added_{$meta_type}_meta", $handler, 99, 4 );
109
			add_action( "updated_{$meta_type}_meta", $handler, 99, 4 );
110
			add_action( "deleted_{$meta_type}_meta", $handler, 99, 4 );
111
		}
112
113
		// synthetic actions for full sync
114
		add_action( 'jp_full_sync_start', $handler );
115
		add_action( 'jp_full_sync_posts', $handler );
116
		add_action( 'jp_full_sync_comments', $handler );
117
		add_action( 'jp_full_sync_option', $handler, 10, 2 );
118
		add_action( 'jp_full_sync_postmeta', $handler, 10, 2 );
119
120
		/**
121
		 * Other hooks - fire synthetic hooks for all the properties we need to sync,
122
		 * e.g. when a theme changes
123
		 */
124
125
		// themes
126
		add_action( 'set_site_transient_update_plugins', $handler, 10, 1 );
127
		add_action( 'set_site_transient_update_themes', $handler, 10, 1 );
128
		add_action( 'set_site_transient_update_core', $handler, 10, 1 );
129
130
		// multi site network options
131
		if ( $this->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...
132
			add_action( 'add_site_option', $handler, 10, 2 );
133
			add_action( 'update_site_option', $handler, 10, 3 );
134
			add_action( 'delete_site_option', $handler, 10, 1 );
135
		}
136
137
138
		/**
139
		 * Sync all pending actions with server
140
		 */
141
		add_action( 'jetpack_sync_actions', array( $this, 'do_sync' ) );
142
143
		// terms
144
		add_action( 'created_term', array( $this, 'save_term_handler' ), 10, 3 );
145
		add_action( 'edited_term', array( $this, 'save_term_handler' ), 10, 3 );
146
		add_action( 'jetapack_sync_save_term', $handler, 10, 4 );
147
		add_action( 'delete_term', $handler, 10, 5 );
148
149
150
	}
151
152
	// TODO: Refactor to use one set whitelist function, with one is_whitelisted.
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...
153
	function set_options_whitelist( $options ) {
154
		$this->options_whitelist = $options;
155
	}
156
157
	function set_constants_whitelist( $constants ) {
158
		$this->constants_whitelist = $constants;
159
	}
160
161
	function get_callable_whitelist( $functions ) {
0 ignored issues
show
Unused Code introduced by
The parameter $functions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
		return $this->callable_whitelist;
163
	}
164
165
	function set_callable_whitelist( $functions ) {
166
		$this->callable_whitelist = $functions;
167
	}
168
169
	function set_network_options_whitelist( $options ) {
170
		$this->network_options_whitelist = $options;
171
	}
172
173
	function set_send_buffer_size( $size ) {
174
		$this->sync_queue->set_checkout_size( $size );
175
	}
176
177
	function set_taxonomy_whitelist( $taxonomies ) {
178
		$this->taxonomy_whitelist = $taxonomies;
179
	}
180
181
	function is_whitelisted_option( $option ) {
182
		foreach ( $this->options_whitelist as $whitelisted_option ) {
183
			if ( $whitelisted_option[0] === '/' && preg_match( $whitelisted_option, $option ) ) {
184
				return true;
185
			} elseif ( $whitelisted_option === $option ) {
186
				return true;
187
			}
188
		}
189
190
		return false;
191
	}
192
193
	function is_whitelisted_network_option( $option ) {
194
		return $this->is_multisite && in_array( $option, $this->network_options_whitelist );
195
	}
196
197
	function set_codec( iJetpack_Sync_Codec $codec ) {
198
		$this->codec = $codec;
199
	}
200
201
	function set_full_sync_client( $full_sync_client ) {
202
		if ( $this->full_sync_client ) {
203
			remove_action( 'jetpack_sync_full', array( $this->full_sync_client, 'start' ) );
204
		}
205
206
		$this->full_sync_client = $full_sync_client;
207
208
		/**
209
		 * Sync all objects in the database with the server
210
		 */
211
		add_action( 'jetpack_sync_full', array( $this->full_sync_client, 'start' ) );
212
	}
213
214
	function action_handler() {
215
		// 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...
216
		// wherever we initialize the action listeners or we're just wasting cycles
217
		if ( Jetpack::is_development_mode() || Jetpack::is_staging_site() ) {
218
			return false;
219
		}
220
221
		$current_filter = current_filter();
222
		$args           = func_get_args();
223
224
		if ( $current_filter === 'wp_insert_post' && $args[1]->post_type === 'revision' ) {
225
			return;
226
		}
227
228
		if ( in_array( $current_filter, array( 'deleted_option', 'added_option', 'updated_option' ) )
229
		     &&
230
		     ! $this->is_whitelisted_option( $args[0] )
231
		) {
232
			return;
233
		}
234
235
		if ( in_array( $current_filter, array( 'delete_site_option', 'add_site_option', 'update_site_option' ) )
236
		     &&
237
		     ! $this->is_whitelisted_network_option( $args[0] )
238
		) {
239
			return;
240
		}
241
242
		$this->sync_queue->add( array(
243
			$current_filter,
244
			$args
245
		) );
246
	}
247
248
	function send_theme_info() {
249
		global $_wp_theme_features;
250
		do_action( 'jetpack_sync_current_theme_support', $_wp_theme_features );
251
	}
252
253
	function save_term_handler( $term_id, $tt_id, $taxonomy ) {
254
		$term_object = WP_Term::get_instance( $term_id, $taxonomy );
255
		do_action( 'jetapack_sync_save_term', $term_id, $tt_id, $taxonomy, $term_object );
256
	}
257
258
	function do_sync() {
259
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
260
			$this->schedule_sync( "+1 minute" );
261
262
			return false;
263
		}
264
265
		$this->maybe_sync_constants();
266
		$this->maybe_sync_callables();
267
268
		$buffer = $this->sync_queue->checkout();
269
270
		if ( ! $buffer ) {
271
			// buffer has no items
272
			return;
273
		}
274
275
		if ( is_wp_error( $buffer ) ) {
276
			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...
277
278
			return;
279
		}
280
281
		$data = $this->codec->encode( $buffer->get_items() );
282
283
		/**
284
		 * Fires when data is ready to send to the server.
285
		 * Return false or WP_Error to abort the sync (e.g. if there's an error)
286
		 * The items will be automatically re-sent later
287
		 *
288
		 * @since 4.1
289
		 *
290
		 * @param array $data The action buffer
291
		 */
292
		$result = apply_filters( 'jetpack_sync_client_send_data', $data );
293
294
		if ( ! $result || is_wp_error( $result ) ) {
295
			$this->sync_queue->checkin( $buffer );
296
			// try again in 1 minute
297
			$this->schedule_sync( "+1 minute" );
298
		} else {
299
			$this->sync_queue->close( $buffer );
300
			// check if there are any more events in the buffer
301
			// if so, schedule a cron job to happen soon
302
			if ( $this->sync_queue->has_any_items() ) {
303
				$this->schedule_sync( "+1 minute" );
304
			}
305
		}
306
	}
307
308
309
	private function schedule_sync( $when ) {
310
		wp_schedule_single_event( strtotime( $when ), 'jetpack_sync_actions' );
311
	}
312
313
	function force_sync_constants() {
314
		delete_option( self::$constants_checksum_option_name );
315
		$this->maybe_sync_constants();
316
	}
317
318 View Code Duplication
	private function maybe_sync_constants() {
319
		$constants = $this->get_all_constants();
320
		if ( empty( $constants ) ) {
321
			return;
322
		}
323
		$constants_check_sum = $this->get_check_sum( $constants );
324
		if ( $constants_check_sum !== get_option( self::$constants_checksum_option_name ) ) {
325
			do_action( 'jetpack_sync_current_constants', $constants );
326
			update_option( self::$constants_checksum_option_name, $constants_check_sum );
327
		}
328
	}
329
330
	private function get_all_constants() {
331
		return array_combine(
332
			$this->constants_whitelist,
333
			array_map( array( $this, 'get_constant' ), $this->constants_whitelist )
334
		);
335
	}
336
337
	private function get_constant( $constant ) {
338
		if ( defined( $constant ) ) {
339
			return constant( $constant );
340
		}
341
342
		return null;
343
	}
344
345
	public function force_sync_callables() {
346
		delete_option( self::$functions_checksum_option_name );
347
		$this->maybe_sync_callables();
348
	}
349
350 View Code Duplication
	private function maybe_sync_callables() {
351
		$callables = $this->get_all_callables();
352
		if ( empty( $callables ) ) {
353
			return;
354
		}
355
		$callables_check_sum = $this->get_check_sum( $callables );
356
357
		if ( $callables_check_sum !== get_option( self::$functions_checksum_option_name ) ) {
358
			do_action( 'jetpack_sync_current_callables', $callables );
359
			update_option( self::$functions_checksum_option_name, $callables_check_sum );
360
		}
361
	}
362
363
	private function get_all_callables() {
364
		return array_combine(
365
			array_keys( $this->callable_whitelist ),
366
			array_map( array( $this, 'get_callable' ), array_values( $this->callable_whitelist ) )
367
		);
368
	}
369
370
	private function get_callable( $callable ) {
371
		return call_user_func( $callable );
372
	}
373
374
	private function get_check_sum( $values ) {
375
		return crc32( serialize( $values ) );
376
	}
377
378
	function get_actions() {
379
		// 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...
380
		return $this->sync_queue->flush_all();
381
	}
382
383
	function get_all_actions() {
384
		return $this->sync_queue->get_all();
385
	}
386
387
	function get_sync_queue() {
388
		return $this->sync_queue;
389
	}
390
391
	function reset_sync_queue() {
392
		$this->sync_queue->reset();
393
	}
394
395
	function reset_state() {
396
		$this->sync_queue = new Jetpack_Sync_Queue( 'sync', self::$default_send_buffer_size );
397
		$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...
398
		$this->codec                     = new Jetpack_Sync_Deflate_Codec();
399
		$this->constants_whitelist       = self::$default_constants_whitelist;
400
		$this->options_whitelist         = self::$default_options_whitelist;
401
		$this->callable_whitelist        = self::$default_callable_whitelist;
402
		$this->network_options_whitelist = self::$default_network_options_whitelist;
403
		$this->taxonomy_whitelist        = self::$default_taxonomy_whitelist;
404
		$this->is_multisite              = is_multisite();
405
406
		delete_option( self::$constants_checksum_option_name );
407
		$this->reset_sync_queue();
408
	}
409
}
410
411
if ( is_multisite() ) {
412
	$client = Jetpack_Sync_Client::getInstance();
413
	$existing_callables = $client->get_callable_whitelist();
414
	$client->set_callable_whitelist( array_merge( $existing_callables, array(
415
		'network_name'                        => array( 'Jetpack', 'network_name' ),
416
		'network_allow_new_registrations'     => array( 'Jetpack', 'network_allow_new_registrations' ),
417
		'network_add_new_users'               => array( 'Jetpack', 'network_add_new_users' ),
418
		'network_site_upload_space'           => array( 'Jetpack', 'network_site_upload_space' ),
419
		'network_upload_file_types'           => array( 'Jetpack', 'network_upload_file_types' ),
420
		'network_enable_administration_menus' => array( 'Jetpack', 'network_enable_administration_menus' ),
421
	) ) );
422
}
423