Completed
Push — add/sync-rest-2 ( 05b575...7335e9 )
by
unknown
08:43
created

Jetpack_Sync_Client::get_check_sum()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
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
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
13
	private $sync_queue;
14
	private $codec;
15
	private $options_whitelist;
16
	private $constants_whitelist;
17
	private $meta_types = array( 'post' );
18
	private $callable_whitelist;
19
	private $network_options_whitelist;
20
21
	// singleton functions
22
	private static $instance;
23
24
	public static function getInstance() {
25
		if ( null === static::$instance ) {
0 ignored issues
show
Bug introduced by
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
26
			static::$instance = new static();
0 ignored issues
show
Bug introduced by
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
27
		}
28
		return static::$instance;
0 ignored issues
show
Bug introduced by
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
29
	}
30
31
	// this is necessary because you can't use "new" when you declare instance properties >:(
32
	protected function __construct() {
33
		$this->sync_queue          = new Jetpack_Sync_Queue( 'sync', 100 );
34
		$this->codec               = new Jetpack_Sync_Deflate_Codec();
35
		$this->constants_whitelist = self::$default_constants_whitelist;
36
		$this->options_whitelist   = self::$default_options_whitelist;
37
		$this->callable_whitelist  = self::$default_callable_whitelist;
38
		$this->network_options_whitelist = self::$default_network_options_whitelist;
39
		$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...
40
		$this->init();
41
	}
42
43
	private function init() {
44
		$handler = array( $this, 'action_handler' );
45
46
		// constants
47
		add_action( 'jetpack_sync_current_constants', $handler, 10 );
48
49
		// functions
50
		add_action( 'jetpack_sync_current_callables', $handler, 10 );
51
52
		// posts
53
		add_action( 'wp_insert_post', $handler, 10, 3 );
54
		add_action( 'deleted_post', $handler, 10 );
55
56
		// comments
57
		add_action( 'wp_insert_comment', $handler, 10, 2 );
58
		add_action( 'deleted_comment', $handler, 10 );
59
		add_action( 'trashed_comment', $handler, 10 );
60
		add_action( 'spammed_comment', $handler, 10 );
61
62
		// even though it's messy, we implement these hooks because 
63
		// the edit_comment hook doesn't include the data
64
		// so this saves us a DB read for every comment event
65
		foreach ( array( '', 'trackback', 'pingback' ) as $comment_type ) {
66
			foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
67
				add_action( "comment_{$comment_status}_{$comment_type}", $handler, 10, 2 );
68
			}
69
		}
70
71
		// options
72
		add_action( 'added_option', $handler, 10, 2 );
73
		add_action( 'updated_option', $handler, 10, 3 );
74
		add_action( 'deleted_option', $handler, 10, 1 );
75
76
		// themes
77
		add_action( 'jetpack_sync_current_theme_support', $handler, 10 ); // custom hook, see meta-hooks below
78
79
		// post-meta, and in the future - other meta?
80
		foreach ( $this->meta_types as $meta_type ) {
81
			// we need to make sure we don't commit before we receive these,
82
			// because they're invoked after meta changes are saved to the DB
83
			add_action( "added_{$meta_type}_meta", $handler, 99, 4 );
84
			add_action( "updated_{$meta_type}_meta", $handler, 99, 4 );
85
			add_action( "deleted_{$meta_type}_meta", $handler, 99, 4 );
86
		}
87
88
		/**
89
		 * Other hooks - fire synthetic hooks for all the properties we need to sync,
90
		 * e.g. when a theme changes
91
		 */
92
93
		// themes
94
		add_action( 'switch_theme', array( $this, 'switch_theme_handler' ) );
95
96
		add_action( 'set_site_transient_update_plugins', $handler, 10, 1 );
97
		add_action( 'set_site_transient_update_themes', $handler, 10, 1 );
98
		add_action( 'set_site_transient_update_core', $handler, 10, 1 );
99
		// multi site network options
100
		if ( $this->is_multisite ) {
101
			add_action( 'add_site_option', $handler, 10, 2 );
102
			add_action( 'update_site_option', $handler, 10, 3 );
103
			add_action( 'delete_site_option', $handler, 10, 1 );
104
		}
105
106
	}
107
108
	function set_options_whitelist( $options ) {
109
		$this->options_whitelist = $options;
110
	}
111
112
	function set_constants_whitelist( $constants ) {
113
		$this->constants_whitelist = $constants;
114
	}
115
116
	function set_callable_whitelist( $functions ) {
117
		$this->callable_whitelist = $functions;
118
	}
119
120
	function set_network_options_whitelist( $options ) {
121
		$this->network_options_whitelist = $options;
122
	}
123
124
	function is_whitelisted_option( $option ) {
125
		foreach ( $this->options_whitelist as $whitelisted_option ) {
126
			if ( $whitelisted_option[0] === '/' && preg_match( $whitelisted_option, $option ) ) {
127
				return true;
128
			} elseif ( $whitelisted_option === $option ) {
129
				return true;
130
			}
131
		}
132
133
		return false;
134
	}
135
136
	function is_whitelisted_network_option( $option ) {
137
		return $this->is_multisite && in_array( $option, $this->network_options_whitelist );
138
	}
139
140
	function set_codec( iJetpack_Sync_Codec $codec ) {
141
		$this->codec = $codec;
142
	}
143
144
	function set_sync_queue( $queue ) {
145
		$this->sync_queue = $queue;
146
	}
147
148
	function action_handler() {
149
		$current_filter = current_filter();
150
		$args           = func_get_args();
151
152
		if ( $current_filter === 'wp_insert_post' && $args[1]->post_type === 'revision' ) {
153
			return;
154
		}
155
156
		if ( in_array( $current_filter, array( 'deleted_option', 'added_option', 'updated_option' ) )
157
		     &&
158
		     ! $this->is_whitelisted_option( $args[0] )
159
		) {
160
			return;
161
		}
162
163
		if ( in_array( $current_filter, array( 'delete_site_option', 'add_site_option', 'update_site_option' ) )
164
		     &&
165
		     ! $this->is_whitelisted_network_option( $args[0] )
166
		) {
167
			return;
168
		}
169
170
		Jetpack_Sync::schedule_sync();
171
		$this->sync_queue->add( array(
172
			$current_filter,
173
			$args
174
		) );
175
	}
176
177
	function switch_theme_handler() {
178
		global $_wp_theme_features;
179
180
		do_action( 'jetpack_sync_current_theme_support', $_wp_theme_features );
181
	}
182
183
	function do_sync() {
184
		$this->maybe_sync_constants();
185
186
		$this->maybe_sync_callables();
187
188
		// TODO: only send buffer once, then do the rest in a cron job
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...
189
		$iters = 0;
190
		while ( ( $buffer = $this->sync_queue->checkout() ) && $iters < 100 ) {
191
192
			if ( ! $buffer ) {
193
				// buffer has no items
194
				return;
195
			}
196
197
			if ( is_wp_error( $buffer ) ) {
198
				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...
199
200
				return;
201
			}
202
203
			$data = $this->codec->encode( $buffer->get_items() );
204
205
			/**
206
			 * Fires when data is ready to send to the server.
207
			 * Return false or WP_Error to abort the sync (e.g. if there's an error)
208
			 * The items will be automatically re-sent later
209
			 *
210
			 * @since 4.1
211
			 *
212
			 * @param array $data The action buffer
213
			 */
214
			$result = apply_filters( 'jetpack_sync_client_send_data', $data );
215
216
			if ( ! $result || is_wp_error( $result ) ) {
217
				$this->sync_queue->checkin( $buffer );
218
			} else {
219
				$this->sync_queue->close( $buffer );
220
			}
221
			$iters += 1;
222
		}
223
	}
224
225 View Code Duplication
	private function maybe_sync_constants() {
226
		$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...
227
		if ( empty( $constants ) ) {
228
			return;
229
		}
230
		$constants_check_sum = $this->get_check_sum( $constants );
231
		if ( $constants_check_sum !== get_option( self::$constants_checksum_option_name ) ) {
232
			do_action( 'jetpack_sync_current_constants', $constants );
233
			update_option( self::$constants_checksum_option_name, $constants_check_sum );
234
		}
235
	}
236
237
	private function get_all_constants() {
238
		return array_combine(
239
			$this->constants_whitelist,
240
			array_map( array( $this, 'get_constant' ), $this->constants_whitelist )
241
		);
242
	}
243
244
	private function get_constant( $constant ) {
245
		if ( defined( $constant ) ) {
246
			return constant( $constant );
247
		}
248
249
		return null;
250
	}
251
252 View Code Duplication
	private function maybe_sync_callables() {
253
		$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...
254
		if ( empty( $callables ) ) {
255
			return;
256
		}
257
		$callables_check_sum = $this->get_check_sum( $callables );
258
259
		if ( $callables_check_sum !== get_option( self::$functions_checksum_option_name ) ) {
260
			do_action( 'jetpack_sync_current_callables', $callables  );
261
			update_option( self::$functions_checksum_option_name, $callables_check_sum );
262
		}
263
	}
264
265
	private function get_all_callables() {
266
		return array_combine(
267
			$this->callable_whitelist,
268
			array_map( array( $this, 'get_callable' ), $this->callable_whitelist )
269
		);
270
	}
271
	
272
	private function get_callable( $callable ) {
273
		return call_user_func( $callable );
274
	}
275
276
	private function get_check_sum( $values ) {
277
		return crc32( serialize( $values ) );
278
	}
279
280
	function get_actions() {
281
		// 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...
282
		return $this->sync_queue->flush_all();
283
	}
284
285
	function get_all_actions() {
286
		return $this->sync_queue->get_all();
287
	}
288
289
	function reset_state() {
290
		$this->codec               = new Jetpack_Sync_Deflate_Codec();
291
		$this->constants_whitelist = self::$default_constants_whitelist;
292
		$this->options_whitelist   = self::$default_options_whitelist;
293
		delete_option( self::$constants_checksum_option_name );
294
		$this->sync_queue->reset();
295
	}
296
297
298
}
299