Completed
Push — update/remove-disconnect-link ( 4b6a2c )
by
unknown
73:18 queued 63:47
created

sync/class.jetpack-sync-actions.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
5
 * when to send, when to perform a full sync, etc.
6
 *
7
 * It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
8
 */
9
class Jetpack_Sync_Actions {
10
	static $sender = null;
11
	static $listener = null;
12
	const DEFAULT_SYNC_CRON_INTERVAL_NAME = 'jetpack_sync_interval';
13
	const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
14
15
	static function init() {
16
17
		// everything below this point should only happen if we're a valid sync site
18
		if ( ! self::sync_allowed() ) {
19
			return;
20
		}
21
22
		if ( self::sync_via_cron_allowed() ) {
23
			self::init_sync_cron_jobs();
24
		} else if ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
25
			self::clear_sync_cron_jobs();
26
		}
27
28
		// On jetpack authorization, schedule a full sync
29
		add_action( 'jetpack_client_authorized', array( __CLASS__, 'do_full_sync' ), 10, 0 );
30
31
		// When importing via cron, do not sync
32
		add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
33
34
		// Sync connected user role changes to .com
35
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-users.php';
36
37
		// publicize filter to prevent publicizing blacklisted post types
38
		add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
39
40
		/**
41
		 * Fires on every request before default loading sync listener code.
42
		 * Return false to not load sync listener code that monitors common
43
		 * WP actions to be serialized.
44
		 *
45
		 * By default this returns true for cron jobs, non-GET-requests, or requests where the
46
		 * user is logged-in.
47
		 *
48
		 * @since 4.2.0
49
		 *
50
		 * @param bool should we load sync listener code for this request
51
		 */
52
		if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
53
			self::initialize_listener();
54
		}
55
		
56
		add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
57
58
	}
59
60
	static function add_sender_shutdown() {
61
		/**
62
		 * Fires on every request before default loading sync sender code.
63
		 * Return false to not load sync sender code that serializes pending
64
		 * data and sends it to WPCOM for processing.
65
		 *
66
		 * By default this returns true for cron jobs, POST requests, admin requests, or requests
67
		 * by users who can manage_options.
68
		 *
69
		 * @since 4.2.0
70
		 *
71
		 * @param bool should we load sync sender code for this request
72
		 */
73
		if ( apply_filters( 'jetpack_sync_sender_should_load',
74
			(
75
				( isset( $_SERVER["REQUEST_METHOD"] ) && 'POST' === $_SERVER['REQUEST_METHOD'] )
76
				||
77
				current_user_can( 'manage_options' )
78
				||
79
				is_admin()
80
				||
81
				defined( 'PHPUNIT_JETPACK_TESTSUITE' )
82
			)
83
		) ) {
84
			self::initialize_sender();
85
			add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
86
			add_action( 'shutdown', array( self::$sender, 'do_full_sync' ) );
87
		}
88
	}
89
90
	static function sync_allowed() {
91
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
92
		return ( ! Jetpack_Sync_Settings::get_setting( 'disable' ) && Jetpack::is_active() && ! ( Jetpack::is_development_mode() || Jetpack::is_staging_site() ) )
93
			   || defined( 'PHPUNIT_JETPACK_TESTSUITE' );
94
	}
95
96
	static function sync_via_cron_allowed() {
97
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
98
		return ( Jetpack_Sync_Settings::get_setting( 'sync_via_cron' ) );
99
	}
100
101
	static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
102
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
103
		if ( in_array( $post->post_type, Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) ) ) {
104
			return false;
105
		}
106
107
		return $should_publicize;
108
	}
109
110
	static function set_is_importing_true() {
111
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
112
		Jetpack_Sync_Settings::set_importing( true );
113
	}
114
115
	static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
116
		Jetpack::load_xml_rpc_client();
117
118
		$query_args = array(
119
			'sync'      => '1',             // add an extra parameter to the URL so we can tell it's a sync action
120
			'codec'     => $codec_name,     // send the name of the codec used to encode the data
121
			'timestamp' => $sent_timestamp, // send current server time so we can compensate for clock differences
122
			'queue'     => $queue_id,       // sync or full_sync
123
			'home'      => get_home_url(),  // Send home url option to check for Identity Crisis server-side
124
			'siteurl'   => get_site_url(),  // Send siteurl option to check for Identity Crisis server-side
125
			'cd'        => sprintf( '%.4f', $checkout_duration),   // Time spent retrieving queue items from the DB
126
			'pd'        => sprintf( '%.4f', $preprocess_duration), // Time spent converting queue items into data to send
127
		);
128
129
		// Has the site opted in to IDC mitigation?
130
		if ( Jetpack::sync_idc_optin() ) {
131
			$query_args['idc'] = true;
132
		}
133
134
		if ( Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
135
			$query_args['migrate_for_idc'] = true;
136
		}
137
138
		$query_args['timeout'] = Jetpack_Sync_Settings::is_doing_cron() ? 30 : 15;
139
140
		$url = add_query_arg( $query_args, Jetpack::xmlrpc_api_url() );
141
142
		$rpc = new Jetpack_IXR_Client( array(
143
			'url'     => $url,
144
			'user_id' => JETPACK_MASTER_USER,
145
			'timeout' => $query_args['timeout'],
146
		) );
147
148
		$result = $rpc->query( 'jetpack.syncActions', $data );
149
150
		if ( ! $result ) {
151
			return $rpc->get_jetpack_error();
152
		}
153
154
		$response = $rpc->getResponse();
155
156
		// Check if WordPress.com IDC mitigation blocked the sync request
157
		if ( is_array( $response ) && isset( $response['error_code'] ) ) {
158
			$error_code = $response['error_code'];
159
			$allowed_idc_error_codes = array(
160
				'jetpack_url_mismatch',
161
				'jetpack_home_url_mismatch',
162
				'jetpack_site_url_mismatch'
163
			);
164
165
			if ( in_array( $error_code, $allowed_idc_error_codes ) ) {
166
				Jetpack_Options::update_option(
167
					'sync_error_idc',
168
					Jetpack::get_sync_error_idc_option( $response )
169
				);
170
			}
171
172
			return new WP_Error(
173
				'sync_error_idc',
174
				esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack' )
175
			);
176
		}
177
178
		return $response;
179
	}
180
181
	static function do_initial_sync( $new_version = null, $old_version = null ) {
182
		if ( ! empty( $old_version ) && version_compare( $old_version, '4.2', '>=' ) ) {
183
			return;
184
		}
185
186
		$initial_sync_config = array(
187
			'options'         => true,
188
			'network_options' => true,
189
			'functions'       => true,
190
			'constants'       => true,
191
			'users'           => 'initial',
192
		);
193
194
		self::do_full_sync( $initial_sync_config );
195
	}
196
197
	static function do_full_sync( $modules = null ) {
198
		if ( ! self::sync_allowed() ) {
199
			return false;
200
		}
201
202
		self::initialize_listener();
203
		Jetpack_Sync_Modules::get_module( 'full-sync' )->start( $modules );
204
205
		return true;
206
	}
207
208
	static function jetpack_cron_schedule( $schedules ) {
209
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
210
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
211
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
212
				'display' => sprintf(
213
					esc_html__( 'Every %d minutes', 'jetpack' ),
214
					self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60
215
				)
216
			);
217
		}
218
		return $schedules;
219
	}
220
221
	// try to send actions until we run out of things to send,
222
	// or have to wait more than 15s before sending again,
223
	// or we hit a lock or some other sending issue
224 View Code Duplication
	static function do_cron_sync() {
225
		if ( ! self::sync_allowed() ) {
226
			return;
227
		}
228
229
		self::initialize_sender();
230
231
		$time_limit = Jetpack_Sync_Settings::get_setting( 'cron_sync_time_limit' );
232
		$start_time = time();
233
234
		do {
235
			$next_sync_time = self::$sender->get_next_sync_time( 'sync' );
236
237
			if ( $next_sync_time ) {
238
				$delay = $next_sync_time - time() + 1;
239
				if ( $delay > 15 ) {
240
					break;
241
				} elseif ( $delay > 0 ) {
242
					sleep( $delay );
243
				}
244
			}
245
246
			$result = self::$sender->do_sync();
247
		} while ( $result && ( $start_time + $time_limit ) > time() );
248
	}
249
250 View Code Duplication
	static function do_cron_full_sync() {
251
		if ( ! self::sync_allowed() ) {
252
			return;
253
		}
254
255
		self::initialize_sender();
256
257
		$time_limit = Jetpack_Sync_Settings::get_setting( 'cron_sync_time_limit' );
258
		$start_time = time();
259
260
		do {
261
			$next_sync_time = self::$sender->get_next_sync_time( 'full_sync' );
262
263
			if ( $next_sync_time ) {
264
				$delay = $next_sync_time - time() + 1;
265
				if ( $delay > 15 ) {
266
					break;
267
				} elseif ( $delay > 0 ) {
268
					sleep( $delay );
269
				}
270
			}
271
272
			$result = self::$sender->do_full_sync();
273
		} while ( $result && ( $start_time + $time_limit ) > time() );
274
	}
275
276
	static function initialize_listener() {
277
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php';
278
		self::$listener = Jetpack_Sync_Listener::get_instance();
279
	}
280
281
	static function initialize_sender() {
282
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-sender.php';
283
		self::$sender = Jetpack_Sync_Sender::get_instance();
284
285
		// bind the sending process
286
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 6 );
287
	}
288
289
	static function initialize_woocommerce() {
290
		if ( class_exists( 'WooCommerce' ) ) {
291
			add_filter( 'jetpack_sync_modules', array( 'Jetpack_Sync_Actions', 'add_woocommerce_sync_module' ) );
292
		}
293
	}
294
295
	static function add_woocommerce_sync_module( $sync_modules ) {
296
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-module-woocommerce.php';
297
		$sync_modules[] = 'Jetpack_Sync_Module_WooCommerce';
298
		return $sync_modules;
299
	}
300
301
	static function sanitize_filtered_sync_cron_schedule( $schedule ) {
302
		$schedule = sanitize_key( $schedule );
303
		$schedules = wp_get_schedules();
304
305
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
306
		if ( isset( $schedules[ $schedule ] ) ) {
307
			return $schedule;
308
		}
309
310
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
311
	}
312
313
	static function get_start_time_offset( $schedule = '', $hook = '' ) {
314
		$start_time_offset =  is_multisite()
315
			? mt_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
316
			: 0;
317
318
		/**
319
		 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
320
		 * cron jobs across multiple sites in a network.
321
		 *
322
		 * @since 4.5
323
		 *
324
		 * @param int    $start_time_offset
325
		 * @param string $hook
326
		 * @param string $schedule
327
		 */
328
		return intval( apply_filters(
329
			'jetpack_sync_cron_start_time_offset',
330
			$start_time_offset,
331
			$hook,
332
			$schedule
333
		) );
334
	}
335
336
	static function maybe_schedule_sync_cron( $schedule, $hook ) {
337
		if ( ! $hook ) {
338
			return;
339
		}
340
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
341
342
		$start_time = time() + self::get_start_time_offset( $schedule, $hook );
343
		if ( ! wp_next_scheduled( $hook ) ) {
344
			// Schedule a job to send pending queue items once a minute
345
			wp_schedule_event( $start_time, $schedule, $hook );
346
		} else if ( $schedule != wp_get_schedule( $hook ) ) {
347
			// If the schedule has changed, update the schedule
348
			wp_clear_scheduled_hook( $hook );
349
			wp_schedule_event( $start_time, $schedule, $hook );
350
		}
351
	}
352
353
	static function clear_sync_cron_jobs() {
354
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
355
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
356
	}
357
358
	static function init_sync_cron_jobs() {
359
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) );
360
361
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
362
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
363
364
		/**
365
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
366
		 *
367
		 * @since 4.3.2
368
		 *
369
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
370
		 */
371
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
372
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
373
374
		/**
375
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
376
		 *
377
		 * @since 4.3.2
378
		 *
379
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
380
		 */
381
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
382
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
383
	}
384
385
	static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
386
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
387
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
388
		}
389
390
		$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
391
		if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
392
			require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
393
			self::clear_sync_cron_jobs();
394
			Jetpack_Sync_Settings::update_settings( array(
395
				'render_filtered_content' => Jetpack_Sync_Defaults::$default_render_filtered_content
0 ignored issues
show
The property default_render_filtered_content cannot be accessed from this context as it is declared private in class Jetpack_Sync_Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
396
			) );
397
		}
398
	}
399
400
	static function get_sync_status() {
401
		self::initialize_sender();
402
403
		$sync_module = Jetpack_Sync_Modules::get_module( 'full-sync' );
404
		$queue       = self::$sender->get_sync_queue();
405
		$full_queue  = self::$sender->get_full_sync_queue();
406
		$cron_timestamps = array_keys( _get_cron_array() );
407
		$next_cron = $cron_timestamps[0] - time();
408
409
		return array_merge(
410
			$sync_module->get_status(),
411
			array(
412
				'cron_size'             => count( $cron_timestamps ),
413
				'next_cron'             => $next_cron,
414
				'queue_size'            => $queue->size(),
415
				'queue_lag'             => $queue->lag(),
416
				'queue_next_sync'       => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
417
				'full_queue_size'       => $full_queue->size(),
418
				'full_queue_lag'        => $full_queue->lag(),
419
				'full_queue_next_sync'  => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
420
			)
421
		);
422
	}
423
}
424
425
/*
426
 * Init after plugins loaded and before the `init` action. This helps with issues where plugins init
427
 * with a high priority or sites that use alternate cron.
428
 */
429
add_action( 'plugins_loaded', array( 'Jetpack_Sync_Actions', 'init' ), 90 );
430
431
// Check for WooCommerce support
432
add_action( 'plugins_loaded', array( 'Jetpack_Sync_Actions', 'initialize_woocommerce' ), 5 );
433
434
// We need to define this here so that it's hooked before `updating_jetpack_version` is called
435
add_action( 'updating_jetpack_version', array( 'Jetpack_Sync_Actions', 'do_initial_sync' ), 10, 2 );
436
add_action( 'updating_jetpack_version', array( 'Jetpack_Sync_Actions', 'cleanup_on_upgrade' ), 10, 2 );
437