Completed
Push — try/statically-access-asset-to... ( e50fad...74c9e7 )
by
unknown
126:59 queued 118:11
created

Actions::clear_sync_cron_jobs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Automattic\Jetpack\Sync;
4
5
use Automattic\Jetpack\Constants;
6
7
/**
8
 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
9
 * when to send, when to perform a full sync, etc.
10
 *
11
 * It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
12
 */
13
class Actions {
14
	static $sender                         = null;
15
	static $listener                       = null;
16
	const DEFAULT_SYNC_CRON_INTERVAL_NAME  = 'jetpack_sync_interval';
17
	const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
18
19
	static function init() {
20
		// everything below this point should only happen if we're a valid sync site
21
		if ( ! self::sync_allowed() ) {
22
			return;
23
		}
24
25
		if ( self::sync_via_cron_allowed() ) {
26
			self::init_sync_cron_jobs();
27
		} elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
28
			self::clear_sync_cron_jobs();
29
		}
30
		// When importing via cron, do not sync
31
		add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
32
33
		// Sync connected user role changes to .com
34
		Users::init();
35
36
		// publicize filter to prevent publicizing blacklisted post types
37
		add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
38
39
		/**
40
		 * Fires on every request before default loading sync listener code.
41
		 * Return false to not load sync listener code that monitors common
42
		 * WP actions to be serialized.
43
		 *
44
		 * By default this returns true for cron jobs, non-GET-requests, or requests where the
45
		 * user is logged-in.
46
		 *
47
		 * @since 4.2.0
48
		 *
49
		 * @param bool should we load sync listener code for this request
50
		 */
51
		if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
52
			self::initialize_listener();
53
		}
54
55
		add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
56
	}
57
58
	static function add_sender_shutdown() {
59
		/**
60
		 * Fires on every request before default loading sync sender code.
61
		 * Return false to not load sync sender code that serializes pending
62
		 * data and sends it to WPCOM for processing.
63
		 *
64
		 * By default this returns true for cron jobs, POST requests, admin requests, or requests
65
		 * by users who can manage_options.
66
		 *
67
		 * @since 4.2.0
68
		 *
69
		 * @param bool should we load sync sender code for this request
70
		 */
71
		if ( apply_filters(
72
			'jetpack_sync_sender_should_load',
73
			self::should_initialize_sender()
74
		) ) {
75
			self::initialize_sender();
76
			add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
77
			add_action( 'shutdown', array( self::$sender, 'do_full_sync' ) );
78
		}
79
	}
80
81
	static function should_initialize_sender() {
82
		if ( Constants::is_true( 'DOING_CRON' ) ) {
83
			return self::sync_via_cron_allowed();
84
		}
85
86
		if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
87
			return true;
88
		}
89
90
		if ( current_user_can( 'manage_options' ) ) {
91
			return true;
92
		}
93
94
		if ( is_admin() ) {
95
			return true;
96
		}
97
98
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
99
			return true;
100
		}
101
102
		return false;
103
	}
104
105
	static function sync_allowed() {
106
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
107
			return true;
108
		}
109
		if ( ! \Jetpack_Sync_Settings::is_sync_enabled() ) {
110
			return false;
111
		}
112
		if ( \Jetpack::is_development_mode() ) {
113
			return false;
114
		}
115
		if ( \Jetpack::is_staging_site() ) {
116
			return false;
117
		}
118
		if ( ! \Jetpack::is_active() ) {
119
			if ( ! doing_action( 'jetpack_user_authorized' ) ) {
120
				return false;
121
			}
122
		}
123
124
		return true;
125
	}
126
127
	static function sync_via_cron_allowed() {
128
		return ( \Jetpack_Sync_Settings::get_setting( 'sync_via_cron' ) );
129
	}
130
131
	static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
132
		if ( in_array( $post->post_type, \Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) ) ) {
133
			return false;
134
		}
135
136
		return $should_publicize;
137
	}
138
139
	static function set_is_importing_true() {
140
		\Jetpack_Sync_Settings::set_importing( true );
141
	}
142
143
	static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
144
		\Jetpack::load_xml_rpc_client();
145
146
		$query_args = array(
147
			'sync'      => '1',             // add an extra parameter to the URL so we can tell it's a sync action
148
			'codec'     => $codec_name,     // send the name of the codec used to encode the data
149
			'timestamp' => $sent_timestamp, // send current server time so we can compensate for clock differences
150
			'queue'     => $queue_id,       // sync or full_sync
151
			'home'      => Functions::home_url(),  // Send home url option to check for Identity Crisis server-side
152
			'siteurl'   => Functions::site_url(),  // Send siteurl option to check for Identity Crisis server-side
153
			'cd'        => sprintf( '%.4f', $checkout_duration ),   // Time spent retrieving queue items from the DB
154
			'pd'        => sprintf( '%.4f', $preprocess_duration ), // Time spent converting queue items into data to send
155
		);
156
157
		// Has the site opted in to IDC mitigation?
158
		if ( \Jetpack::sync_idc_optin() ) {
159
			$query_args['idc'] = true;
160
		}
161
162
		if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
163
			$query_args['migrate_for_idc'] = true;
164
		}
165
166
		$query_args['timeout'] = \Jetpack_Sync_Settings::is_doing_cron() ? 30 : 15;
167
168
		/**
169
		 * Filters query parameters appended to the Sync request URL sent to WordPress.com.
170
		 *
171
		 * @since 4.7.0
172
		 *
173
		 * @param array $query_args associative array of query parameters.
174
		 */
175
		$query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
176
177
		$url = add_query_arg( $query_args, \Jetpack::xmlrpc_api_url() );
178
179
		$rpc = new \Jetpack_IXR_Client(
180
			array(
181
				'url'     => $url,
182
				'user_id' => JETPACK_MASTER_USER,
183
				'timeout' => $query_args['timeout'],
184
			)
185
		);
186
187
		$result = $rpc->query( 'jetpack.syncActions', $data );
188
189
		if ( ! $result ) {
190
			return $rpc->get_jetpack_error();
191
		}
192
193
		$response = $rpc->getResponse();
194
195
		// Check if WordPress.com IDC mitigation blocked the sync request
196
		if ( is_array( $response ) && isset( $response['error_code'] ) ) {
197
			$error_code              = $response['error_code'];
198
			$allowed_idc_error_codes = array(
199
				'jetpack_url_mismatch',
200
				'jetpack_home_url_mismatch',
201
				'jetpack_site_url_mismatch',
202
			);
203
204
			if ( in_array( $error_code, $allowed_idc_error_codes ) ) {
205
				\Jetpack_Options::update_option(
206
					'sync_error_idc',
207
					\Jetpack::get_sync_error_idc_option( $response )
208
				);
209
			}
210
211
			return new \WP_Error(
212
				'sync_error_idc',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'sync_error_idc'.

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...
213
				esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack' )
214
			);
215
		}
216
217
		return $response;
218
	}
219
220
	static function do_initial_sync() {
221
		// Lets not sync if we are not suppose to.
222
		if ( ! self::sync_allowed() ) {
223
			return false;
224
		}
225
226
		$initial_sync_config = array(
227
			'options'   => true,
228
			'functions' => true,
229
			'constants' => true,
230
			'users'     => array( get_current_user_id() ),
231
		);
232
233
		if ( is_multisite() ) {
234
			$initial_sync_config['network_options'] = true;
235
		}
236
237
		self::do_full_sync( $initial_sync_config );
238
	}
239
240
	static function do_full_sync( $modules = null ) {
241
		if ( ! self::sync_allowed() ) {
242
			return false;
243
		}
244
245
		$full_sync_module = Modules::get_module( 'full-sync' );
246
247
		if ( ! $full_sync_module ) {
248
			return false;
249
		}
250
251
		self::initialize_listener();
252
253
		$full_sync_module->start( $modules );
254
255
		return true;
256
	}
257
258
	static function jetpack_cron_schedule( $schedules ) {
259
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
260
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
261
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
262
				'display'  => sprintf(
263
					esc_html( _n( 'Every minute', 'Every %d minutes', intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 ), 'jetpack' ) ),
264
					intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 )
265
				),
266
			);
267
		}
268
		return $schedules;
269
	}
270
271
	static function do_cron_sync() {
272
		self::do_cron_sync_by_type( 'sync' );
273
	}
274
275
	static function do_cron_full_sync() {
276
		self::do_cron_sync_by_type( 'full_sync' );
277
	}
278
279
	/**
280
	 * Try to send actions until we run out of things to send,
281
	 * or have to wait more than 15s before sending again,
282
	 * or we hit a lock or some other sending issue
283
	 *
284
	 * @param string $type Sync type. Can be `sync` or `full_sync`.
285
	 */
286
	static function do_cron_sync_by_type( $type ) {
287
		if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
288
			return;
289
		}
290
291
		self::initialize_sender();
292
293
		$time_limit = \Jetpack_Sync_Settings::get_setting( 'cron_sync_time_limit' );
294
		$start_time = time();
295
296
		do {
297
			$next_sync_time = self::$sender->get_next_sync_time( $type );
298
299
			if ( $next_sync_time ) {
300
				$delay = $next_sync_time - time() + 1;
301
				if ( $delay > 15 ) {
302
					break;
303
				} elseif ( $delay > 0 ) {
304
					sleep( $delay );
305
				}
306
			}
307
308
			$result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
309
		} while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
310
	}
311
312
	static function initialize_listener() {
313
		self::$listener = Listener::get_instance();
314
	}
315
316
	static function initialize_sender() {
317
		self::$sender = Sender::get_instance();
318
319
		// bind the sending process
320
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 6 );
321
	}
322
323
	static function initialize_woocommerce() {
324
		if ( false === class_exists( 'WooCommerce' ) ) {
325
			return;
326
		}
327
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
328
	}
329
330
	static function add_woocommerce_sync_module( $sync_modules ) {
331
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
332
		return $sync_modules;
333
	}
334
335
	static function initialize_wp_super_cache() {
336
		if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
337
			return;
338
		}
339
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
340
	}
341
342
	static function add_wp_super_cache_sync_module( $sync_modules ) {
343
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
344
		return $sync_modules;
345
	}
346
347
	static function sanitize_filtered_sync_cron_schedule( $schedule ) {
348
		$schedule  = sanitize_key( $schedule );
349
		$schedules = wp_get_schedules();
350
351
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
352
		if ( isset( $schedules[ $schedule ] ) ) {
353
			return $schedule;
354
		}
355
356
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
357
	}
358
359
	static function get_start_time_offset( $schedule = '', $hook = '' ) {
360
		$start_time_offset = is_multisite()
361
			? mt_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
362
			: 0;
363
364
		/**
365
		 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
366
		 * cron jobs across multiple sites in a network.
367
		 *
368
		 * @since 4.5.0
369
		 *
370
		 * @param int    $start_time_offset
371
		 * @param string $hook
372
		 * @param string $schedule
373
		 */
374
		return intval(
375
			apply_filters(
376
				'jetpack_sync_cron_start_time_offset',
377
				$start_time_offset,
378
				$hook,
379
				$schedule
380
			)
381
		);
382
	}
383
384
	static function maybe_schedule_sync_cron( $schedule, $hook ) {
385
		if ( ! $hook ) {
386
			return;
387
		}
388
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
389
390
		$start_time = time() + self::get_start_time_offset( $schedule, $hook );
391
		if ( ! wp_next_scheduled( $hook ) ) {
392
			// Schedule a job to send pending queue items once a minute
393
			wp_schedule_event( $start_time, $schedule, $hook );
394
		} elseif ( $schedule != wp_get_schedule( $hook ) ) {
395
			// If the schedule has changed, update the schedule
396
			wp_clear_scheduled_hook( $hook );
397
			wp_schedule_event( $start_time, $schedule, $hook );
398
		}
399
	}
400
401
	static function clear_sync_cron_jobs() {
402
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
403
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
404
	}
405
406
	static function init_sync_cron_jobs() {
407
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) );
408
409
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
410
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
411
412
		/**
413
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
414
		 *
415
		 * @since 4.3.2
416
		 *
417
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
418
		 */
419
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
420
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
421
422
		/**
423
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
424
		 *
425
		 * @since 4.3.2
426
		 *
427
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
428
		 */
429
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
430
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
431
	}
432
433
	static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
434
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
435
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
436
		}
437
438
		$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
439
		if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
440
			self::clear_sync_cron_jobs();
441
			\Jetpack_Sync_Settings::update_settings(
442
				array(
443
					'render_filtered_content' => \Jetpack_Sync_Defaults::$default_render_filtered_content,
0 ignored issues
show
Bug introduced by
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...
444
				)
445
			);
446
		}
447
	}
448
449
	/**
450
	 * Get the sync status
451
	 *
452
	 * @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
453
	 * @return array
454
	 */
455
	static function get_sync_status( $fields = null ) {
456
		self::initialize_sender();
457
458
		$sync_module     = Modules::get_module( 'full-sync' );
459
		$queue           = self::$sender->get_sync_queue();
460
		$full_queue      = self::$sender->get_full_sync_queue();
461
		$cron_timestamps = array_keys( _get_cron_array() );
462
		$next_cron       = $cron_timestamps[0] - time();
463
464
		$checksums = array();
465
466
		if ( ! empty( $fields ) ) {
467
			$store         = new Replicastore();
468
			$fields_params = array_map( 'trim', explode( ',', $fields ) );
469
470
			if ( in_array( 'posts_checksum', $fields_params, true ) ) {
471
				$checksums['posts_checksum'] = $store->posts_checksum();
472
			}
473
			if ( in_array( 'comments_checksum', $fields_params, true ) ) {
474
				$checksums['comments_checksum'] = $store->comments_checksum();
475
			}
476
			if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
477
				$checksums['post_meta_checksum'] = $store->post_meta_checksum();
478
			}
479
			if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
480
				$checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
481
			}
482
		}
483
484
		$full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
485
486
		return array_merge(
487
			$full_sync_status,
488
			$checksums,
489
			array(
490
				'cron_size'            => count( $cron_timestamps ),
491
				'next_cron'            => $next_cron,
492
				'queue_size'           => $queue->size(),
493
				'queue_lag'            => $queue->lag(),
494
				'queue_next_sync'      => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
495
				'full_queue_size'      => $full_queue->size(),
496
				'full_queue_lag'       => $full_queue->lag(),
497
				'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
498
			)
499
		);
500
	}
501
}
502