Completed
Push — add/sync-rest-2 ( e3ecb6...96002c )
by
unknown
48:39 queued 39:11
created

Jetpack_Sync_Client::save_term_handler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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