PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-sync/src/modules/class-full-sync-immediately.php +206 -97 12.5.2 → 16.3-beta View file →
@@ -12,8 +12,12 @@
12 12 use Automattic\Jetpack\Sync\Lock;
13 13 use Automattic\Jetpack\Sync\Modules;
14 14 use Automattic\Jetpack\Sync\Settings;
15 15
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
16 20 /**
17 21 * This class does a full resync of the database by
18 22 * sending an outbound action for every single object
19 23 * that we care about.
@@ -59,29 +63,36 @@
59 63 *
60 64 * @access public
61 65 *
62 66 * @param array $full_sync_config Full sync configuration.
67 + * @param mixed $context The context where the full sync was initiated from.
63 68 *
64 69 * @return bool Always returns true at success.
65 70 */
66 - public function start( $full_sync_config = null ) {
67 - // There was a full sync in progress.
68 - if ( $this->is_started() && ! $this->is_finished() ) {
69 - /**
70 - * Fires when a full sync is cancelled.
71 - *
72 - * @since 1.6.3
73 - * @since-jetpack 4.2.0
74 - */
75 - do_action( 'jetpack_full_sync_cancelled' );
76 - $this->send_action( 'jetpack_full_sync_cancelled' );
77 - }
78 -
71 + public function start( $full_sync_config = null, $context = null ) {
72 + // Check if there was a full sync in progress already before resetting the data.
73 + $should_process_cancelled_action = $this->get_status()['start_action_processed'] && ! $this->is_finished();
79 74 // Remove all evidence of previous full sync items and status.
80 75 $this->reset_data();
81 76
77 + // Update status to indicate that a new full sync is starting and need to cancel previous one.
78 + if ( $should_process_cancelled_action ) {
79 + $this->update_status(
80 + array(
81 + 'cancelled_action_processed' => false,
82 + )
83 + );
84 + }
85 +
82 86 if ( ! is_array( $full_sync_config ) ) {
83 - $full_sync_config = Defaults::$default_full_sync_config;
87 + /*
88 + * Filter default sync config to allow injecting custom configuration.
89 + *
90 + * @param array $full_sync_config Sync configuration for all sync modules.
91 + *
92 + * @since 3.10.0
93 + */
94 + $full_sync_config = apply_filters( 'jetpack_full_sync_config', Defaults::$default_full_sync_config );
84 95 if ( is_multisite() ) {
85 96 $full_sync_config['network_options'] = 1;
86 97 }
87 98 }
@@ -86,36 +97,21 @@
86 97 }
87 98 }
88 99
89 100 if ( isset( $full_sync_config['users'] ) && 'initial' === $full_sync_config['users'] ) {
90 - $full_sync_config['users'] = Modules::get_module( 'users' )->get_initial_sync_user_config();
101 + $users_module = Modules::get_module( 'users' );
102 + '@phan-var Users $users_module';
103 + $full_sync_config['users'] = $users_module->get_initial_sync_user_config();
91 104 }
92 105
93 106 $this->update_status(
94 107 array(
95 - 'started' => time(),
96 - 'config' => $full_sync_config,
97 - 'progress' => $this->get_initial_progress( $full_sync_config ),
108 + 'started' => time(),
109 + 'config' => $full_sync_config,
110 + 'context' => $context,
98 111 )
99 112 );
100 113
101 - $range = $this->get_content_range();
102 - /**
103 - * Fires when a full sync begins. This action is serialized
104 - * and sent to the server so that it knows a full sync is coming.
105 - *
106 - * @param array $full_sync_config Sync configuration for all sync modules.
107 - * @param array $range Range of the sync items, containing min and max IDs for some item types.
108 - * @param array $empty The modules with no items to sync during a full sync.
109 - *
110 - * @since 1.6.3
111 - * @since-jetpack 4.2.0
112 - * @since-jetpack 7.3.0 Added $range arg.
113 - * @since-jetpack 7.4.0 Added $empty arg.
114 - */
115 - do_action( 'jetpack_full_sync_start', $full_sync_config, $range );
116 - $this->send_action( 'jetpack_full_sync_start', array( $full_sync_config, $range ) );
117 -
118 114 return true;
119 115 }
120 116
121 117 /**
@@ -137,12 +133,15 @@
137 133 * @return array Full sync status.
138 134 */
139 135 public function get_status() {
140 136 $default = array(
141 - 'started' => false,
142 - 'finished' => false,
143 - 'progress' => array(),
144 - 'config' => array(),
137 + 'start_action_processed' => false,
138 + 'cancelled_action_processed' => true, // true by default to avoid sending the action when there is no need,
139 + 'started' => false,
140 + 'finished' => false,
141 + 'progress' => array(),
142 + 'config' => array(),
143 + 'context' => null,
145 144 );
146 145
147 146 return wp_parse_args( \Jetpack_Options::get_raw_option( self::STATUS_OPTION ), $default );
148 147 }
@@ -238,12 +237,13 @@
238 237 /**
239 238 * Given an initial Full Sync configuration get the initial status.
240 239 *
241 240 * @param array $full_sync_config Full sync configuration.
241 + * @param array $range Range of the sync items, containing min, max and count IDs for some item types.
242 242 *
243 243 * @return array Initial Sent status.
244 244 */
245 - public function get_initial_progress( $full_sync_config ) {
245 + public function get_initial_progress( $full_sync_config, $range = null ) {
246 246 // Set default configuration, calculate totals, and save configuration if totals > 0.
247 247 $status = array();
248 248 foreach ( $full_sync_config as $name => $config ) {
249 249 $module = Modules::get_module( $name );
@@ -250,9 +250,10 @@
250 250 if ( ! $module ) {
251 251 continue;
252 252 }
253 253 $status[ $name ] = array(
254 - 'total' => $module->total( $config ),
254 + // If we have a range for the module, use the count from the range to avoid querying the database again.
255 + 'total' => $range[ $name ]->count ?? $module->total( $config ),
255 256 'sent' => 0,
256 257 'finished' => false,
257 258 );
258 259 }
@@ -264,22 +265,28 @@
264 265 * Get the range for content (posts and comments) to sync.
265 266 *
266 267 * @access private
267 268 *
269 + * @param array $full_sync_config Full sync configuration.
270 + *
268 271 * @return array Array of range (min ID, max ID, total items) for all content types.
269 272 */
270 - private function get_content_range() {
271 - $range = array();
272 - $config = $this->get_status()['config'];
273 - // Add range only when syncing all objects.
274 - if ( true === isset( $config['posts'] ) && $config['posts'] ) {
275 - $range['posts'] = $this->get_range( 'posts' );
273 + private function get_content_range( $full_sync_config ) {
274 + $range = array();
275 + foreach ( $full_sync_config as $module_name => $config ) {
276 + // Calculate ranges only for modules that get chunked.
277 + if ( in_array( $module_name, array( 'constants', 'functions', 'network_options', 'options', 'themes', 'updates' ), true ) ) {
278 + continue;
279 + }
280 + $module = Modules::get_module( $module_name );
281 + if ( ! $module ) {
282 + continue;
283 + }
284 + if ( $config ) {
285 + $range[ $module_name ] = $this->get_range( $module_name );
286 + }
276 287 }
277 288
278 - if ( true === isset( $config['comments'] ) && $config['comments'] ) {
279 - $range['comments'] = $this->get_range( 'comments' );
280 - }
281 -
282 289 return $range;
283 290 }
284 291
285 292 /**
@@ -292,25 +299,19 @@
292 299 * @return array Array of min ID, max ID and total items in the range.
293 300 */
294 301 public function get_range( $type ) {
295 302 global $wpdb;
296 - if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
303 + $module = Modules::get_module( $type );
304 + if ( ! $module ) {
297 305 return array();
298 306 }
299 307
300 - switch ( $type ) {
301 - case 'posts':
302 - $table = $wpdb->posts;
303 - $id = 'ID';
304 - $where_sql = Settings::get_blacklisted_post_types_sql();
305 -
306 - break;
307 - case 'comments':
308 - $table = $wpdb->comments;
309 - $id = 'comment_ID';
310 - $where_sql = Settings::get_comments_filter_sql();
311 - break;
308 + $table = $module->table();
309 + $id = $module->id_field();
310 + if ( 'terms' === $module ) { // Terms module relies on the term_taxonomy and term_taxonomy_id for the where sql, let's use term_id instead.
311 + $id = 'term_id';
312 312 }
313 + $where_sql = $module->get_where_sql( array() );
313 314
314 315 // TODO: Call $wpdb->prepare on the following query.
315 316 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
316 317 $results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
@@ -374,8 +375,16 @@
374 375 *
375 376 * @access public
376 377 */
377 378 public function send() {
379 +
380 + if ( ! $this->maybe_send_cancelled_action() ) {
381 + return false;
382 + }
383 +
384 + if ( ! $this->maybe_send_full_sync_start() ) {
385 + return false;
386 + }
378 387 $config = $this->get_status()['config'];
379 388
380 389 $max_duration = Settings::get_setting( 'full_sync_send_duration' );
381 390 $send_until = microtime( true ) + $max_duration;
@@ -381,15 +390,37 @@
381 390 $send_until = microtime( true ) + $max_duration;
382 391
383 392 $progress = $this->get_status()['progress'];
384 393
385 - foreach ( $this->get_remaining_modules_to_send() as $module ) {
386 - $progress[ $module->name() ] = $module->send_full_sync_actions( $config[ $module->name() ], $progress[ $module->name() ], $send_until );
387 - if ( isset( $progress[ $module->name() ]['error'] ) ) {
388 - unset( $progress[ $module->name() ]['error'] );
389 - $this->update_status( array( 'progress' => $progress ) );
394 + $started = $this->get_status()['started'];
395 +
396 + $remaining_modules = $this->get_remaining_modules_to_send();
397 +
398 + foreach ( $remaining_modules as $module ) {
399 + $module_name = $module->name();
400 + if ( array_key_exists( $module_name, $progress ) && array_key_exists( $module_name, $config ) ) {
401 + $progress[ $module_name ] = $module->send_full_sync_actions( $config[ $module_name ], $progress[ $module_name ], $send_until, $started );
402 + if ( isset( $progress[ $module_name ]['error'] ) ) {
403 + unset( $progress[ $module_name ]['error'] );
404 + $this->update_status( array( 'progress' => $progress ) );
405 + return false;
406 + } elseif ( ! $progress[ $module_name ]['finished'] ) {
407 + $this->update_status( array( 'progress' => $progress ) );
408 + return true;
409 + }
410 + }
411 +
412 + if ( $this->get_status()['started'] !== $started ) {
413 + // Full sync was restarted, stop sending.
390 414 return false;
391 - } elseif ( ! $progress[ $module->name() ]['finished'] ) {
415 + }
416 + }
417 +
418 + // Check that all remaining modules in progress are actually finished.
419 + // If a module was skipped in the main loop (due to being unfinished), but still exists in progress, we shouldn't mark the sync as complete.
420 + foreach ( $remaining_modules as $module ) {
421 + $name = $module->name();
422 + if ( array_key_exists( $name, $progress ) && empty( $progress[ $name ]['finished'] ) ) {
392 423 $this->update_status( array( 'progress' => $progress ) );
393 424 return true;
394 425 }
395 426 }
@@ -404,45 +435,125 @@
404 435 *
405 436 * @return array
406 437 */
407 438 public function get_remaining_modules_to_send() {
439 + $status = $this->get_status();
440 + $remaining_modules = array();
441 + foreach ( array_keys( $status['config'] ) as $module_name ) {
442 + $module = Modules::get_module( $module_name );
443 + if ( ! $module ) {
444 + continue;
445 + }
446 + if ( isset( $status['progress'][ $module_name ]['finished'] ) &&
447 + true === $status['progress'][ $module_name ]['finished'] ) {
448 + continue;
449 + }
450 + // Ensure that 'constants', 'options', and 'callables' are sent first.
451 + if ( in_array( $module_name, array( 'network_options', 'options', 'functions', 'constants' ), true ) ) {
452 + array_unshift( $remaining_modules, $module );
453 + } else {
454 + $remaining_modules[] = $module;
455 + }
456 + }
457 + return $remaining_modules;
458 + }
459 +
460 + /**
461 + * Sends the `jetpack_full_sync_start` action if it hasn't been processed yet.
462 + *
463 + * Prepares the full sync start action, sends it to WordPress.com, fires the local action,
464 + * and updates the sync status to reflect that the start action has been processed.
465 + *
466 + * @return bool True if the action was successfully sent or already processed, false on failure.
467 + */
468 + private function maybe_send_full_sync_start() {
408 469 $status = $this->get_status();
409 470
410 - return array_filter(
411 - Modules::get_modules(),
412 - /**
413 - * Select configured and not finished modules.
414 - *
415 - * @return bool
416 - * @var $module Module
417 - */
418 - function ( $module ) use ( $status ) {
419 - // Skip module if not configured for this sync or module is done.
420 - if ( ! isset( $status['config'][ $module->name() ] ) ) {
421 - return false;
422 - }
423 - if ( ! $status['config'][ $module->name() ] ) {
424 - return false;
425 - }
426 - if ( isset( $status['progress'][ $module->name() ]['finished'] ) ) {
427 - if ( true === $status['progress'][ $module->name() ]['finished'] ) {
428 - return false;
429 - }
430 - }
471 + // If already processed, nothing to do.
472 + if ( true === $status['start_action_processed'] ) {
473 + return true;
474 + }
431 475
432 - return true;
433 - }
476 + $config = $status['config'];
477 + $context = $status['context'];
478 + $range = $this->get_content_range( $config );
479 +
480 + $result = $this->send_action( 'jetpack_full_sync_start', array( $config, $range, $context ) );
481 +
482 + // If the action failed on WordPress.com, return false.
483 + if ( is_wp_error( $result ) ) {
484 + return false;
485 + }
486 +
487 + /**
488 + * Fires when a full sync begins. This action is serialized
489 + * and sent to the server so that it knows a full sync is coming.
490 + *
491 + * @param array $config Sync configuration for all sync modules.
492 + * @param array $range Range of the sync items, containing min and max IDs for some item types.
493 + * @param mixed $context The context where the full sync was initiated from.
494 + *
495 + * @since 1.6.3
496 + * @since-jetpack 4.2.0
497 + * @since-jetpack 7.3.0 Added $range arg.
498 + * @since 4.4.0 Added $context arg.
499 + */
500 + do_action( 'jetpack_full_sync_start', $config, $range );
501 +
502 + $this->update_status(
503 + array(
504 + 'start_action_processed' => true,
505 + 'progress' => $this->get_initial_progress( $config, $range ),
506 + )
434 507 );
508 +
509 + return true;
435 510 }
436 511
437 512 /**
438 - * Send 'jetpack_full_sync_end' and update 'finished' status.
513 + * Sends the `jetpack_full_sync_cancelled` action if it hasn't been processed yet.
439 514 *
515 + * @return bool True if the action was successfully sent or already processed, false on failure.
516 + */
517 + private function maybe_send_cancelled_action() {
518 + $status = $this->get_status();
519 +
520 + if ( true === $status['cancelled_action_processed'] ) {
521 + return true;
522 + }
523 +
524 + $result = $this->send_action( 'jetpack_full_sync_cancelled' );
525 +
526 + if ( is_wp_error( $result ) ) {
527 + return false;
528 + }
529 +
530 + /**
531 + * Fires when a full sync is cancelled.
532 + *
533 + * @since 1.6.3
534 + * @since-jetpack 4.2.0
535 + */
536 + do_action( 'jetpack_full_sync_cancelled' );
537 + $this->update_status( array( 'cancelled_action_processed' => true ) );
538 + return true;
539 + }
540 +
541 + /**
542 + * Sends the `jetpack_full_sync_end` action and updates the status when the full sync end action is processed.
543 + *
440 544 * @access public
441 545 */
442 546 public function send_full_sync_end() {
443 - $range = $this->get_content_range();
547 + $status = $this->get_status();
548 + $range = $this->get_content_range( $status['config'] );
549 + $context = $status['context'];
444 550
551 + $result = $this->send_action( 'jetpack_full_sync_end', array( '', $range, $context ) );
552 +
553 + if ( is_wp_error( $result ) ) { // Do not set finished status if we get an error.
554 + return;
555 + }
445 556 /**
446 557 * Fires when a full sync ends. This action is serialized
447 558 * and sent to the server.
448 559 *
@@ -453,9 +564,8 @@
453 564 * @since-jetpack 4.2.0
454 565 * @since-jetpack 7.3.0 Added $range arg.
455 566 */
456 567 do_action( 'jetpack_full_sync_end', '', $range );
457 - $this->send_action( 'jetpack_full_sync_end', array( '', $range ) );
458 568
459 569 // Setting autoload to true means that it's faster to check whether we should continue enqueuing.
460 570 $this->update_status( array( 'finished' => time() ) );
461 571 }
@@ -465,6 +575,5 @@
465 575 *
466 576 * @param array $actions an array of actions, ignored for queueless sync.
467 577 */
468 578 public function update_sent_progress_action( $actions ) { } // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
469 -
470 579 }