Completed
Push — try/jetpack-stories-block-mobi... ( 2fea66 )
by
unknown
126:35 queued 116:47
created

Full_Sync::get_status()   B

Complexity

Conditions 7
Paths 18

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 18
nop 0
dl 0
loc 45
rs 8.2666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Full sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Listener;
11
use Automattic\Jetpack\Sync\Lock;
12
use Automattic\Jetpack\Sync\Modules;
13
use Automattic\Jetpack\Sync\Queue;
14
use Automattic\Jetpack\Sync\Settings;
15
16
/**
17
 * This class does a full resync of the database by
18
 * enqueuing an outbound action for every single object
19
 * that we care about.
20
 *
21
 * This class, and its related class Jetpack_Sync_Module, contain a few non-obvious optimisations that should be explained:
22
 * - we fire an action called jetpack_full_sync_start so that WPCOM can erase the contents of the cached database
23
 * - for each object type, we page through the object IDs and enqueue them by firing some monitored actions
24
 * - we load the full objects for those IDs in chunks of Jetpack_Sync_Module::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
25
 * - we fire a trigger for the entire array which the Automattic\Jetpack\Sync\Listener then serializes and queues.
26
 */
27
class Full_Sync extends Module {
28
	/**
29
	 * Prefix of the full sync status option name.
30
	 *
31
	 * @var string
32
	 */
33
	const STATUS_OPTION_PREFIX = 'jetpack_sync_full_';
34
35
	/**
36
	 * Enqueue Lock name.
37
	 *
38
	 * @var string
39
	 */
40
	const ENQUEUE_LOCK_NAME = 'full_sync_enqueue';
41
42
	/**
43
	 * Sync module name.
44
	 *
45
	 * @access public
46
	 *
47
	 * @return string
48
	 */
49
	public function name() {
50
		return 'full-sync';
51
	}
52
53
	/**
54
	 * Initialize action listeners for full sync.
55
	 *
56
	 * @access public
57
	 *
58
	 * @param callable $callable Action handler callable.
59
	 */
60
	public function init_full_sync_listeners( $callable ) {
61
		// Synthetic actions for full sync.
62
		add_action( 'jetpack_full_sync_start', $callable, 10, 3 );
63
		add_action( 'jetpack_full_sync_end', $callable, 10, 2 );
64
		add_action( 'jetpack_full_sync_cancelled', $callable );
65
	}
66
67
	/**
68
	 * Initialize the module in the sender.
69
	 *
70
	 * @access public
71
	 */
72
	public function init_before_send() {
73
		// This is triggered after actions have been processed on the server.
74
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
75
	}
76
77
	/**
78
	 * Start a full sync.
79
	 *
80
	 * @access public
81
	 *
82
	 * @param array $module_configs Full sync configuration for all sync modules.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $module_configs not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
83
	 * @return bool Always returns true at success.
84
	 */
85
	public function start( $module_configs = null ) {
86
		$was_already_running = $this->is_started() && ! $this->is_finished();
87
88
		// Remove all evidence of previous full sync items and status.
89
		$this->reset_data();
90
91
		if ( $was_already_running ) {
92
			/**
93
			 * Fires when a full sync is cancelled.
94
			 *
95
			 * @since 4.2.0
96
			 */
97
			do_action( 'jetpack_full_sync_cancelled' );
98
		}
99
100
		$this->update_status_option( 'started', time() );
101
		$this->update_status_option( 'params', $module_configs );
102
103
		$enqueue_status   = array();
104
		$full_sync_config = array();
105
		$include_empty    = false;
106
		$empty            = array();
107
108
		// Default value is full sync.
109
		if ( ! is_array( $module_configs ) ) {
110
			$module_configs = array();
111
			$include_empty  = true;
112
			foreach ( Modules::get_modules() as $module ) {
113
				$module_configs[ $module->name() ] = true;
114
			}
115
		}
116
117
		// Set default configuration, calculate totals, and save configuration if totals > 0.
118
		foreach ( Modules::get_modules() as $module ) {
119
			$module_name   = $module->name();
120
			$module_config = isset( $module_configs[ $module_name ] ) ? $module_configs[ $module_name ] : false;
121
122
			if ( ! $module_config ) {
123
				continue;
124
			}
125
126
			if ( 'users' === $module_name && 'initial' === $module_config ) {
127
				$module_config = $module->get_initial_sync_user_config();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Automattic\Jetpack\Sync\Modules\Module as the method get_initial_sync_user_config() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Users. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
128
			}
129
130
			$enqueue_status[ $module_name ] = false;
131
132
			$total_items = $module->estimate_full_sync_actions( $module_config );
133
134
			// If there's information to process, configure this module.
135
			if ( ! is_null( $total_items ) && $total_items > 0 ) {
136
				$full_sync_config[ $module_name ] = $module_config;
137
				$enqueue_status[ $module_name ]   = array(
138
					$total_items,   // Total.
139
					0,              // Queued.
140
					false,          // Current state.
141
				);
142
			} elseif ( $include_empty && 0 === $total_items ) {
143
				$empty[ $module_name ] = true;
144
			}
145
		}
146
147
		$this->set_config( $full_sync_config );
148
		$this->set_enqueue_status( $enqueue_status );
149
150
		$range = $this->get_content_range( $full_sync_config );
151
		/**
152
		 * Fires when a full sync begins. This action is serialized
153
		 * and sent to the server so that it knows a full sync is coming.
154
		 *
155
		 * @since 4.2.0
156
		 * @since 7.3.0 Added $range arg.
157
		 * @since 7.4.0 Added $empty arg.
158
		 *
159
		 * @param array $full_sync_config Sync configuration for all sync modules.
160
		 * @param array $range            Range of the sync items, containing min and max IDs for some item types.
161
		 * @param array $empty            The modules with no items to sync during a full sync.
162
		 */
163
		do_action( 'jetpack_full_sync_start', $full_sync_config, $range, $empty );
164
165
		$this->continue_enqueuing( $full_sync_config );
166
167
		return true;
168
	}
169
170
	/**
171
	 * Enqueue the next items to sync.
172
	 *
173
	 * @access public
174
	 *
175
	 * @param array $configs Full sync configuration for all sync modules.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $configs not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
176
	 */
177 View Code Duplication
	public function continue_enqueuing( $configs = null ) {
178
		if ( ! $this->is_started() || ! ( new Lock() )->attempt( self::ENQUEUE_LOCK_NAME ) || $this->get_status_option( 'queue_finished' ) ) {
179
			return;
180
		}
181
182
		$this->enqueue( $configs );
183
184
		( new Lock() )->remove( self::ENQUEUE_LOCK_NAME );
185
	}
186
187
	/**
188
	 * Get Modules that are configured to Full Sync and haven't finished enqueuing
189
	 *
190
	 * @param array $configs Full sync configuration for all sync modules.
191
	 *
192
	 * @return array
193
	 */
194
	public function get_remaining_modules_to_enqueue( $configs ) {
195
		$enqueue_status = $this->get_enqueue_status();
196
		return array_filter(
197
			Modules::get_modules(),
198
			/**
199
			 * Select configured and not finished modules.
200
			 *
201
			 * @var $module Module
202
			 * @return bool
203
			 */
204
			function ( $module ) use ( $configs, $enqueue_status ) {
205
				// Skip module if not configured for this sync or module is done.
206
				if ( ! isset( $configs[ $module->name() ] ) ) {
207
					return false;
208
				}
209
				if ( ! $configs[ $module->name() ] ) {
210
					return false;
211
				}
212
				if ( isset( $enqueue_status[ $module->name() ][2] ) ) {
213
					if ( true === $enqueue_status[ $module->name() ][2] ) {
214
						return false;
215
					}
216
				}
217
218
				return true;
219
			}
220
		);
221
	}
222
223
	/**
224
	 * Enqueue the next items to sync.
225
	 *
226
	 * @access public
227
	 *
228
	 * @param array $configs Full sync configuration for all sync modules.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $configs not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
229
	 */
230
	public function enqueue( $configs = null ) {
231
		if ( ! $configs ) {
232
			$configs = $this->get_config();
233
		}
234
235
		$enqueue_status        = $this->get_enqueue_status();
236
		$full_sync_queue       = new Queue( 'full_sync' );
237
		$available_queue_slots = Settings::get_setting( 'max_queue_size_full_sync' ) - $full_sync_queue->size();
238
239
		if ( $available_queue_slots <= 0 ) {
240
			return;
241
		}
242
243
		$remaining_items_to_enqueue = min( Settings::get_setting( 'max_enqueue_full_sync' ), $available_queue_slots );
244
245
		/**
246
		 * If a module exits early (e.g. because it ran out of full sync queue slots, or we ran out of request time)
247
		 * then it should exit early
248
		 */
249
		foreach ( $this->get_remaining_modules_to_enqueue( $configs ) as $module ) {
250
			list( $items_enqueued, $next_enqueue_state ) = $module->enqueue_full_sync_actions( $configs[ $module->name() ], $remaining_items_to_enqueue, $enqueue_status[ $module->name() ][2] );
251
252
			$enqueue_status[ $module->name() ][2] = $next_enqueue_state;
253
254
			// If items were processed, subtract them from the limit.
255
			if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) {
256
				$enqueue_status[ $module->name() ][1] += $items_enqueued;
257
				$remaining_items_to_enqueue           -= $items_enqueued;
258
			}
259
260
			if ( 0 >= $remaining_items_to_enqueue || true !== $next_enqueue_state ) {
261
				$this->set_enqueue_status( $enqueue_status );
262
				return;
263
			}
264
		}
265
266
		$this->queue_full_sync_end( $configs );
267
		$this->set_enqueue_status( $enqueue_status );
268
	}
269
270
	/**
271
	 * Enqueue 'jetpack_full_sync_end' and update 'queue_finished' status.
272
	 *
273
	 * @access public
274
	 *
275
	 * @param array $configs Full sync configuration for all sync modules.
276
	 */
277
	public function queue_full_sync_end( $configs ) {
278
		$range = $this->get_content_range( $configs );
279
280
		/**
281
		 * Fires when a full sync ends. This action is serialized
282
		 * and sent to the server.
283
		 *
284
		 * @since 4.2.0
285
		 * @since 7.3.0 Added $range arg.
286
		 *
287
		 * @param string $checksum Deprecated since 7.3.0 - @see https://github.com/Automattic/jetpack/pull/11945/
288
		 * @param array  $range    Range of the sync items, containing min and max IDs for some item types.
289
		 */
290
		do_action( 'jetpack_full_sync_end', '', $range );
291
292
		// Setting autoload to true means that it's faster to check whether we should continue enqueuing.
293
		$this->update_status_option( 'queue_finished', time(), true );
294
	}
295
296
	/**
297
	 * Get the range (min ID, max ID and total items) of items to sync.
298
	 *
299
	 * @access public
300
	 *
301
	 * @param string $type Type of sync item to get the range for.
302
	 * @return array Array of min ID, max ID and total items in the range.
303
	 */
304 View Code Duplication
	public function get_range( $type ) {
305
		global $wpdb;
306
		if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
307
			return array();
308
		}
309
310
		switch ( $type ) {
311
			case 'posts':
312
				$table     = $wpdb->posts;
313
				$id        = 'ID';
314
				$where_sql = Settings::get_blacklisted_post_types_sql();
315
316
				break;
317
			case 'comments':
318
				$table     = $wpdb->comments;
319
				$id        = 'comment_ID';
320
				$where_sql = Settings::get_comments_filter_sql();
321
				break;
322
		}
323
324
		// TODO: Call $wpdb->prepare on the following query.
325
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
326
		$results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
0 ignored issues
show
Bug introduced by
The variable $id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $where_sql does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
327
		if ( isset( $results[0] ) ) {
328
			return $results[0];
329
		}
330
331
		return array();
332
	}
333
334
	/**
335
	 * Get the range for content (posts and comments) to sync.
336
	 *
337
	 * @access private
338
	 *
339
	 * @param array $config Full sync configuration for this all sync modules.
340
	 * @return array Array of range (min ID, max ID, total items) for all content types.
341
	 */
342 View Code Duplication
	private function get_content_range( $config ) {
343
		$range = array();
344
		// Only when we are sending the whole range do we want to send also the range.
345
		if ( true === isset( $config['posts'] ) && $config['posts'] ) {
346
			$range['posts'] = $this->get_range( 'posts' );
347
		}
348
349
		if ( true === isset( $config['comments'] ) && $config['comments'] ) {
350
			$range['comments'] = $this->get_range( 'comments' );
351
		}
352
		return $range;
353
	}
354
355
	/**
356
	 * Update the progress after sync modules actions have been processed on the server.
357
	 *
358
	 * @access public
359
	 *
360
	 * @param array $actions Actions that have been processed on the server.
361
	 */
362
	public function update_sent_progress_action( $actions ) {
363
		// Quick way to map to first items with an array of arrays.
364
		$actions_with_counts = array_count_values( array_filter( array_map( array( $this, 'get_action_name' ), $actions ) ) );
365
366
		// Total item counts for each action.
367
		$actions_with_total_counts = $this->get_actions_totals( $actions );
368
369
		if ( ! $this->is_started() || $this->is_finished() ) {
370
			return;
371
		}
372
373
		if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
374
			$this->update_status_option( 'send_started', time() );
375
		}
376
377
		foreach ( Modules::get_modules() as $module ) {
378
			$module_actions     = $module->get_full_sync_actions();
379
			$status_option_name = "{$module->name()}_sent";
380
			$total_option_name  = "{$status_option_name}_total";
381
			$items_sent         = $this->get_status_option( $status_option_name, 0 );
382
			$items_sent_total   = $this->get_status_option( $total_option_name, 0 );
383
384
			foreach ( $module_actions as $module_action ) {
385
				if ( isset( $actions_with_counts[ $module_action ] ) ) {
386
					$items_sent += $actions_with_counts[ $module_action ];
387
				}
388
389
				if ( ! empty( $actions_with_total_counts[ $module_action ] ) ) {
390
					$items_sent_total += $actions_with_total_counts[ $module_action ];
391
				}
392
			}
393
394
			if ( $items_sent > 0 ) {
395
				$this->update_status_option( $status_option_name, $items_sent );
396
			}
397
398
			if ( 0 !== $items_sent_total ) {
399
				$this->update_status_option( $total_option_name, $items_sent_total );
400
			}
401
		}
402
403
		if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
404
			$this->update_status_option( 'finished', time() );
405
		}
406
	}
407
408
	/**
409
	 * Returns the progress percentage of a full sync.
410
	 *
411
	 * @access public
412
	 *
413
	 * @return int|null
414
	 */
415
	public function get_sync_progress_percentage() {
416
		if ( ! $this->is_started() || $this->is_finished() ) {
417
			return null;
418
		}
419
		$status = $this->get_status();
420
		if ( ! $status['queue'] || ! $status['sent'] || ! $status['total'] ) {
421
			return null;
422
		}
423
		$queued_multiplier = 0.1;
424
		$sent_multiplier   = 0.9;
425
		$count_queued      = array_reduce(
426
			$status['queue'],
427
			function ( $sum, $value ) {
428
				return $sum + $value;
429
			},
430
			0
431
		);
432
		$count_sent        = array_reduce(
433
			$status['sent'],
434
			function ( $sum, $value ) {
435
				return $sum + $value;
436
			},
437
			0
438
		);
439
		$count_total       = array_reduce(
440
			$status['total'],
441
			function ( $sum, $value ) {
442
				return $sum + $value;
443
			},
444
			0
445
		);
446
		$percent_queued    = ( $count_queued / $count_total ) * $queued_multiplier * 100;
447
		$percent_sent      = ( $count_sent / $count_total ) * $sent_multiplier * 100;
448
		return ceil( $percent_queued + $percent_sent );
449
	}
450
451
	/**
452
	 * Get the name of the action for an item in the sync queue.
453
	 *
454
	 * @access public
455
	 *
456
	 * @param array $queue_item Item of the sync queue.
457
	 * @return string|boolean Name of the action, false if queue item is invalid.
458
	 */
459
	public function get_action_name( $queue_item ) {
460
		if ( is_array( $queue_item ) && isset( $queue_item[0] ) ) {
461
			return $queue_item[0];
462
		}
463
		return false;
464
	}
465
466
	/**
467
	 * Retrieve the total number of items we're syncing in a particular queue item (action).
468
	 * `$queue_item[1]` is expected to contain chunks of items, and `$queue_item[1][0]`
469
	 * represents the first (and only) chunk of items to sync in that action.
470
	 *
471
	 * @access public
472
	 *
473
	 * @param array $queue_item Item of the sync queue that corresponds to a particular action.
474
	 * @return int Total number of items in the action.
475
	 */
476
	public function get_action_totals( $queue_item ) {
477
		if ( is_array( $queue_item ) && isset( $queue_item[1][0] ) ) {
478
			if ( is_array( $queue_item[1][0] ) ) {
479
				// Let's count the items we sync in this action.
480
				return count( $queue_item[1][0] );
481
			}
482
			// -1 indicates that this action syncs all items by design.
483
			return -1;
484
		}
485
		return 0;
486
	}
487
488
	/**
489
	 * Retrieve the total number of items for a set of actions, grouped by action name.
490
	 *
491
	 * @access public
492
	 *
493
	 * @param array $actions An array of actions.
494
	 * @return array An array, representing the total number of items, grouped per action.
495
	 */
496
	public function get_actions_totals( $actions ) {
497
		$totals = array();
498
499
		foreach ( $actions as $action ) {
500
			$name          = $this->get_action_name( $action );
501
			$action_totals = $this->get_action_totals( $action );
502
			if ( ! isset( $totals[ $name ] ) ) {
503
				$totals[ $name ] = 0;
504
			}
505
			$totals[ $name ] += $action_totals;
506
		}
507
508
		return $totals;
509
	}
510
511
	/**
512
	 * Whether full sync has started.
513
	 *
514
	 * @access public
515
	 *
516
	 * @return boolean
517
	 */
518
	public function is_started() {
519
		return (bool) $this->get_status_option( 'started' );
520
	}
521
522
	/**
523
	 * Whether full sync has finished.
524
	 *
525
	 * @access public
526
	 *
527
	 * @return boolean
528
	 */
529
	public function is_finished() {
530
		return (bool) $this->get_status_option( 'finished' );
531
	}
532
533
	/**
534
	 * Retrieve the status of the current full sync.
535
	 *
536
	 * @access public
537
	 *
538
	 * @return array Full sync status.
539
	 */
540
	public function get_status() {
541
		$status = array(
542
			'started'        => $this->get_status_option( 'started' ),
543
			'queue_finished' => $this->get_status_option( 'queue_finished' ),
544
			'send_started'   => $this->get_status_option( 'send_started' ),
545
			'finished'       => $this->get_status_option( 'finished' ),
546
			'sent'           => array(),
547
			'sent_total'     => array(),
548
			'queue'          => array(),
549
			'config'         => $this->get_status_option( 'params' ),
550
			'total'          => array(),
551
		);
552
553
		$enqueue_status = $this->get_enqueue_status();
554
555
		foreach ( Modules::get_modules() as $module ) {
556
			$name = $module->name();
557
558
			if ( ! isset( $enqueue_status[ $name ] ) ) {
559
				continue;
560
			}
561
562
			list( $total, $queued ) = $enqueue_status[ $name ];
563
564
			if ( $total ) {
565
				$status['total'][ $name ] = $total;
566
			}
567
568
			if ( $queued ) {
569
				$status['queue'][ $name ] = $queued;
570
			}
571
572
			$sent = $this->get_status_option( "{$name}_sent" );
573
			if ( $sent ) {
574
				$status['sent'][ $name ] = $sent;
575
			}
576
577
			$sent_total = $this->get_status_option( "{$name}_sent_total" );
578
			if ( $sent_total ) {
579
				$status['sent_total'][ $name ] = $sent_total;
580
			}
581
		}
582
583
		return $status;
584
	}
585
586
	/**
587
	 * Clear all the full sync status options.
588
	 *
589
	 * @access public
590
	 */
591
	public function clear_status() {
592
		$prefix = self::STATUS_OPTION_PREFIX;
593
		\Jetpack_Options::delete_raw_option( "{$prefix}_started" );
594
		\Jetpack_Options::delete_raw_option( "{$prefix}_params" );
595
		\Jetpack_Options::delete_raw_option( "{$prefix}_queue_finished" );
596
		\Jetpack_Options::delete_raw_option( "{$prefix}_send_started" );
597
		\Jetpack_Options::delete_raw_option( "{$prefix}_finished" );
598
599
		$this->delete_enqueue_status();
600
601
		foreach ( Modules::get_modules() as $module ) {
602
			\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent" );
603
			\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent_total" );
604
		}
605
	}
606
607
	/**
608
	 * Clear all the full sync data.
609
	 *
610
	 * @access public
611
	 */
612
	public function reset_data() {
613
		$this->clear_status();
614
		$this->delete_config();
615
		( new Lock() )->remove( self::ENQUEUE_LOCK_NAME );
616
617
		$listener = Listener::get_instance();
618
		$listener->get_full_sync_queue()->reset();
619
	}
620
621
	/**
622
	 * Get the value of a full sync status option.
623
	 *
624
	 * @access private
625
	 *
626
	 * @param string $name    Name of the option.
627
	 * @param mixed  $default Default value of the option.
628
	 * @return mixed Option value.
629
	 */
630
	private function get_status_option( $name, $default = null ) {
631
		$value = \Jetpack_Options::get_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $default );
632
633
		return is_numeric( $value ) ? (int) $value : $value;
634
	}
635
636
	/**
637
	 * Update the value of a full sync status option.
638
	 *
639
	 * @access private
640
	 *
641
	 * @param string  $name     Name of the option.
642
	 * @param mixed   $value    Value of the option.
643
	 * @param boolean $autoload Whether the option should be autoloaded at the beginning of the request.
644
	 */
645
	private function update_status_option( $name, $value, $autoload = false ) {
646
		\Jetpack_Options::update_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $value, $autoload );
647
	}
648
649
	/**
650
	 * Set the full sync enqueue status.
651
	 *
652
	 * @access private
653
	 *
654
	 * @param array $new_status The new full sync enqueue status.
655
	 */
656
	private function set_enqueue_status( $new_status ) {
657
		\Jetpack_Options::update_raw_option( 'jetpack_sync_full_enqueue_status', $new_status );
658
	}
659
660
	/**
661
	 * Delete full sync enqueue status.
662
	 *
663
	 * @access private
664
	 *
665
	 * @return boolean Whether the status was deleted.
666
	 */
667
	private function delete_enqueue_status() {
668
		return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_enqueue_status' );
669
	}
670
671
	/**
672
	 * Retrieve the current full sync enqueue status.
673
	 *
674
	 * @access private
675
	 *
676
	 * @return array Full sync enqueue status.
677
	 */
678
	public function get_enqueue_status() {
679
		return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_enqueue_status' );
680
	}
681
682
	/**
683
	 * Set the full sync enqueue configuration.
684
	 *
685
	 * @access private
686
	 *
687
	 * @param array $config The new full sync enqueue configuration.
688
	 */
689
	private function set_config( $config ) {
690
		\Jetpack_Options::update_raw_option( 'jetpack_sync_full_config', $config );
691
	}
692
693
	/**
694
	 * Delete full sync configuration.
695
	 *
696
	 * @access private
697
	 *
698
	 * @return boolean Whether the configuration was deleted.
699
	 */
700
	private function delete_config() {
701
		return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_config' );
702
	}
703
704
	/**
705
	 * Retrieve the current full sync enqueue config.
706
	 *
707
	 * @access private
708
	 *
709
	 * @return array Full sync enqueue config.
710
	 */
711
	private function get_config() {
712
		return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
713
	}
714
715
}
716