Completed
Push — fix/queue-up-full-sync-on-netw... ( 7e7a40...fcd777 )
by
unknown
223:05 queued 215:49
created

Jetpack_Sync_Actions::do_cron_full_sync()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 25
Code Lines 16

Duplication

Lines 25
Ratio 100 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 5
nop 0
dl 25
loc 25
rs 6.7272
c 0
b 0
f 0
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;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $sender.

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 $listener = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $listener.

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
	const DEFAULT_SYNC_CRON_INTERVAL_NAME = 'jetpack_sync_interval';
13
	const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
14
	
15
	const UPDATE_RAMP_UP_CRON_NAME = 'jetpack_network_version_up_cron';
16
	const UPDATE_RAMP_UP_NETWORK_OPTION = 'jetpack_sync_network_upgrade_ramp_up';
17
	const UPDATE_RAMP_UP_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
18
	const UPDATE_RAMP_UP_INCREMENT = 10; // Percentage of sites that the intervals increments by.
19
20
	static function init() {
21
22
		// everything below this point should only happen if we're a valid sync site
23
		if ( ! self::sync_allowed() ) {
24
			return;
25
		}
26
27
		if ( self::sync_via_cron_allowed() ) {
28
			self::init_sync_cron_jobs();
29
		} else if ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
30
			wp_clear_scheduled_hook( 'jetpack_sync_cron' );
31
			wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
32
		}
33
		if ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
34
			// Add cron action that bump ups the avalue.
35
			add_action( self::UPDATE_RAMP_UP_CRON_NAME, array( __CLASS__, 'jetpack_network_ramp_up_bump' ), 10, 1 );
36
37
			// Did we update the option already?
38
			if ( self::can_do_initial_sync() ) {
39
				self::do_initial_sync( JETPACK__VERSION, Jetpack_Options::get_option( 'jetpack_network_version', 0 ), true );
40
			}
41
		}
42
43
		// On jetpack authorization, schedule a full sync
44
		add_action( 'jetpack_client_authorized', array( __CLASS__, 'do_full_sync' ), 10, 0 );
45
46
		// When importing via cron, do not sync
47
		add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
48
49
		// Sync connected user role changes to .com
50
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-users.php';
51
52
		// publicize filter to prevent publicizing blacklisted post types
53
		add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
54
55
		/**
56
		 * Fires on every request before default loading sync listener code.
57
		 * Return false to not load sync listener code that monitors common
58
		 * WP actions to be serialized.
59
		 *
60
		 * By default this returns true for cron jobs, non-GET-requests, or requests where the
61
		 * user is logged-in.
62
		 *
63
		 * @since 4.2.0
64
		 *
65
		 * @param bool should we load sync listener code for this request
66
		 */
67
		if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
68
			self::initialize_listener();
69
		}
70
71
		add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
72
73
	}
74
75
	static function can_do_initial_sync( $current_blog_id = null, $ramp_up = null ) {
76
		$network_version = Jetpack_Options::get_option( 'jetpack_network_version', 0 );
77
78
		if ( $network_version === JETPACK__VERSION ) {
79
			return false;
80
		}
81
82
		if ( empty( $ramp_up ) ) {
83
			$ramp_up = get_site_option( self::UPDATE_RAMP_UP_NETWORK_OPTION );
84
		}
85
86
		if ( $current_blog_id === null ) {
87
			$current_blog_id = get_current_blog_id();
88
		}
89
		// if $ramp_up is 10 - (0 - 10), (100 - 110) , (200 - 210)  etc.
90
		// So about 10% of sites should be allowed to update
91
		// It doesm't depended on the total nubver of sites.
92
		if ( ( $current_blog_id % 100 ) <= $ramp_up ) {
93
			return true;
94
		}
95
96
		return false;
97
	}
98
99
	static function add_sender_shutdown() {
100
		/**
101
		 * Fires on every request before default loading sync sender code.
102
		 * Return false to not load sync sender code that serializes pending
103
		 * data and sends it to WPCOM for processing.
104
		 *
105
		 * By default this returns true for cron jobs, POST requests, admin requests, or requests
106
		 * by users who can manage_options.
107
		 *
108
		 * @since 4.2.0
109
		 *
110
		 * @param bool should we load sync sender code for this request
111
		 */
112
		if ( apply_filters( 'jetpack_sync_sender_should_load',
113
			(
114
				( isset( $_SERVER["REQUEST_METHOD"] ) && 'POST' === $_SERVER['REQUEST_METHOD'] )
115
				||
116
				current_user_can( 'manage_options' )
117
				||
118
				is_admin()
119
				||
120
				defined( 'PHPUNIT_JETPACK_TESTSUITE' )
121
			)
122
		) ) {
123
			self::initialize_sender();
124
			add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
125
			add_action( 'shutdown', array( self::$sender, 'do_full_sync' ) );
126
		}
127
	}
128
129
	static function sync_allowed() {
130
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
131
		return ( ! Jetpack_Sync_Settings::get_setting( 'disable' ) && Jetpack::is_active() && ! ( Jetpack::is_development_mode() || Jetpack::is_staging_site() ) )
132
			   || defined( 'PHPUNIT_JETPACK_TESTSUITE' );
133
	}
134
135
	static function sync_via_cron_allowed() {
136
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
137
		return ( Jetpack_Sync_Settings::get_setting( 'sync_via_cron' ) );
138
	}
139
140
	static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
141
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
142
		if ( in_array( $post->post_type, Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) ) ) {
143
			return false;
144
		}
145
146
		return $should_publicize;
147
	}
148
149
	static function set_is_importing_true() {
150
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
151
		Jetpack_Sync_Settings::set_importing( true );
152
	}
153
154
	static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
155
		Jetpack::load_xml_rpc_client();
156
157
		$query_args = array(
158
			'sync'      => '1',             // add an extra parameter to the URL so we can tell it's a sync action
159
			'codec'     => $codec_name,     // send the name of the codec used to encode the data
160
			'timestamp' => $sent_timestamp, // send current server time so we can compensate for clock differences
161
			'queue'     => $queue_id,       // sync or full_sync
162
			'home'      => get_home_url(),  // Send home url option to check for Identity Crisis server-side
163
			'siteurl'   => get_site_url(),  // Send siteurl option to check for Identity Crisis server-side
164
			'cd'        => sprintf( '%.4f', $checkout_duration),   // Time spent retrieving queue items from the DB
165
			'pd'        => sprintf( '%.4f', $preprocess_duration), // Time spent converting queue items into data to send
166
		);
167
168
		// Has the site opted in to IDC mitigation?
169
		if ( Jetpack::sync_idc_optin() ) {
170
			$query_args['idc'] = true;
171
		}
172
173
		if ( Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
174
			$query_args['migrate_for_idc'] = true;
175
		}
176
177
		$query_args['timeout'] = Jetpack_Sync_Settings::is_doing_cron() ? 30 : 15;
178
179
		$url = add_query_arg( $query_args, Jetpack::xmlrpc_api_url() );
180
181
		$rpc = new Jetpack_IXR_Client( array(
182
			'url'     => $url,
183
			'user_id' => JETPACK_MASTER_USER,
184
			'timeout' => $query_args['timeout'],
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',
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( $new_version = null, $old_version = null, $network_site = false ) {
221
		$initial_sync_config = self::get_update_full_sync_config();
222
223
		if ( $old_version && ( version_compare( $old_version, '4.2', '<' ) ) ) {
224
			$initial_sync_config['users'] = 'initial';
225
		}
226
227
		if ( is_plugin_active_for_network( 'jetpack/jetpack.php' ) && ! $network_site ) {
228
229
			// Set up Ramp up of Jetpack Full Sync.
230
			if ( is_main_site() ) {
231
				update_site_option( self::UPDATE_RAMP_UP_NETWORK_OPTION, self::get_ramp_up_increment() );
232
233
				// By default the value is set to 10
234
				if ( self::get_ramp_up_increment() < 100 ) {
235
					wp_schedule_single_event( time() + self::get_next_ramp_up_interval() , self::UPDATE_RAMP_UP_CRON_NAME );
236
				}
237
238
				// Sync the main site right away
239
				self::do_full_sync( $initial_sync_config );
240
				Jetpack_Options::update_option( 'jetpack_network_version', JETPACK__VERSION );
241
242
			}
243
		} else {
244
			self::do_full_sync( $initial_sync_config );
245
			Jetpack_Options::update_option( 'jetpack_network_version', JETPACK__VERSION );
246
		}
247
	}
248
249
	static function get_update_full_sync_config() {
250
		return array(
251
			'options' => true,
252
			'network_options' => true,
253
			'functions' => true,
254
			'constants' => true,
255
		);
256
	}
257
258
	static function get_ramp_up_increment() {
259
		/**
260
		 * Allows you to change the percentage at which the update ramp up happens.
261
		 * This value should be a integer less then 100
262
		 *
263
		 * Default is self::UPDATE_RAMP_UP_INCREMENT
264
		 * @since 4.5.0
265
		 * @param int increment value
266
		 */
267
		$increment = (int) apply_filters( 'jetpack_sync_network_upgrade_ramp_up_increment', self::UPDATE_RAMP_UP_INCREMENT );
268
		return ( $increment >= 1 && $increment <= 100 ) ? $increment : self::UPDATE_RAMP_UP_INCREMENT;
269
	}
270
271
	static function get_next_ramp_up_interval() {
272
		/**
273
		 * Allows you to change the percentage at which the update ramp up happens.
274
		 * This value should be a integer less then 100
275
		 *
276
		 * Default is self::UPDATE_RAMP_UP_INCREMENT
277
		 * @since 4.5.0
278
		 * @param int interval value in seconds at which point the next ramp up is supposed to happen.
279
		 */
280
		return (int) apply_filters( 'jetpack_sync_network_upgrade_ramp_up_interval', self::UPDATE_RAMP_UP_INTERVAL_VALUE );
281
	}
282
283
	static function jetpack_network_ramp_up_bump() {
284
		$ramp_up = get_site_option( self::UPDATE_RAMP_UP_NETWORK_OPTION );
285
		if ( $ramp_up < 100 ) {
286
			update_site_option( self::UPDATE_RAMP_UP_NETWORK_OPTION, $ramp_up + self::get_ramp_up_increment() );
287
			wp_schedule_single_event( time() + self::get_next_ramp_up_interval(), self::UPDATE_RAMP_UP_CRON_NAME );
288
		}
289
290
	}
291
292
	static function do_full_sync( $modules = null ) {
293
		if ( ! self::sync_allowed() ) {
294
			return false;
295
		}
296
		self::initialize_listener();
297
		Jetpack_Sync_Modules::get_module( 'full-sync' )->start( $modules );
298
299
		return true;
300
	}
301
302
	static function jetpack_cron_schedule( $schedules ) {
303
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
304
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
305
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
306
				'display' => sprintf(
307
					esc_html__( 'Every %d minutes', 'jetpack' ),
308
					self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60
309
				)
310
			);
311
		}
312
		return $schedules;
313
	}
314
315
	// try to send actions until we run out of things to send,
316
	// or have to wait more than 15s before sending again,
317
	// or we hit a lock or some other sending issue
318 View Code Duplication
	static function do_cron_sync() {
319
		if ( ! self::sync_allowed() ) {
320
			return;
321
		}
322
323
		self::initialize_sender();
324
325
		$time_limit = Jetpack_Sync_Settings::get_setting( 'cron_sync_time_limit' );
326
		$start_time = time();
327
328
		do {
329
			$next_sync_time = self::$sender->get_next_sync_time( 'sync' );
330
331
			if ( $next_sync_time ) {
332
				$delay = $next_sync_time - time() + 1;
333
				if ( $delay > 15 ) {
334
					break;
335
				} elseif ( $delay > 0 ) {
336
					sleep( $delay );
337
				}
338
			}
339
340
			$result = self::$sender->do_sync();
341
		} while ( $result && ( $start_time + $time_limit ) > time() );
342
	}
343
344 View Code Duplication
	static function do_cron_full_sync() {
345
		if ( ! self::sync_allowed() ) {
346
			return;
347
		}
348
349
		self::initialize_sender();
350
351
		$time_limit = Jetpack_Sync_Settings::get_setting( 'cron_sync_time_limit' );
352
		$start_time = time();
353
354
		do {
355
			$next_sync_time = self::$sender->get_next_sync_time( 'full_sync' );
356
357
			if ( $next_sync_time ) {
358
				$delay = $next_sync_time - time() + 1;
359
				if ( $delay > 15 ) {
360
					break;
361
				} elseif ( $delay > 0 ) {
362
					sleep( $delay );
363
				}
364
			}
365
366
			$result = self::$sender->do_full_sync();
367
		} while ( $result && ( $start_time + $time_limit ) > time() );
368
	}
369
370
	static function initialize_listener() {
371
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php';
372
		self::$listener = Jetpack_Sync_Listener::get_instance();
373
	}
374
375
	static function initialize_sender() {
376
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-sender.php';
377
		self::$sender = Jetpack_Sync_Sender::get_instance();
378
379
		// bind the sending process
380
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 6 );
381
	}
382
383
	static function sanitize_filtered_sync_cron_schedule( $schedule ) {
384
		$schedule = sanitize_key( $schedule );
385
		$schedules = wp_get_schedules();
386
387
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
388
		if ( isset( $schedules[ $schedule ] ) ) {
389
			return $schedule;
390
		}
391
392
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
393
	}
394
395
	static function maybe_schedule_sync_cron( $schedule, $hook ) {
396
		if ( ! $hook ) {
397
			return;
398
		}
399
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
400
401
		if ( ! wp_next_scheduled( $hook ) ) {
402
			// Schedule a job to send pending queue items once a minute
403
			wp_schedule_event( time(), $schedule, $hook );
404
		} else if ( $schedule != wp_get_schedule( $hook ) ) {
405
			// If the schedule has changed, update the schedule
406
			wp_clear_scheduled_hook( $hook );
407
			wp_schedule_event( time(), $schedule, $hook );
408
		}
409
	}
410
411
	static function init_sync_cron_jobs() {
412
		// Add a custom "every minute" cron schedule
413
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) );
414
415
		// cron hooks
416
		add_action( 'jetpack_sync_full', array( __CLASS__, 'do_full_sync' ), 10, 1 );
417
418
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
419
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
420
421
		/**
422
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
423
		 *
424
		 * @since 4.3.2
425
		 *
426
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
427
		 */
428
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
429
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
430
431
		/**
432
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
433
		 *
434
		 * @since 4.3.2
435
		 *
436
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
437
		 */
438
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
439
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
440
	}
441
442
	static function cleanup_on_upgrade() {
443
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
444
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
445
		}
446
	}
447
448
	static function get_sync_status() {
449
		self::initialize_sender();
450
451
		$sync_module = Jetpack_Sync_Modules::get_module( 'full-sync' );
452
		$queue       = self::$sender->get_sync_queue();
453
		$full_queue  = self::$sender->get_full_sync_queue();
454
		$cron_timestamps = array_keys( _get_cron_array() );
455
		$next_cron = $cron_timestamps[0] - time();
456
457
		return array_merge(
458
			$sync_module->get_status(),
459
			array(
460
				'cron_size'             => count( $cron_timestamps ),
461
				'next_cron'             => $next_cron,
462
				'queue_size'            => $queue->size(),
463
				'queue_lag'             => $queue->lag(),
464
				'queue_next_sync'       => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
465
				'full_queue_size'       => $full_queue->size(),
466
				'full_queue_lag'        => $full_queue->lag(),
467
				'full_queue_next_sync'  => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
468
			)
469
		);
470
	}
471
}
472
473
/**
474
 * If the site is using alternate cron, we need to init the listener and sender before cron
475
 * runs. So, we init at a priority of 9.
476
 *
477
 * If the site is using a regular cron job, we init at a priority of 90 which gives plugins a chance
478
 * to add filters before we initialize.
479
 */
480
add_action( 'plugins_loaded', array( 'Jetpack_Sync_Actions', 'init' ), 90 );
481
482
// We need to define this here so that it's hooked before `updating_jetpack_version` is called
483
add_action( 'updating_jetpack_version', array( 'Jetpack_Sync_Actions', 'do_initial_sync' ), 10, 2 );
484
add_action( 'updating_jetpack_version', array( 'Jetpack_Sync_Actions', 'cleanup_on_upgrade' ) );
485