Completed
Push — add/masterbar--my-home ( 5c5e9c...8732fd )
by
unknown
10:54 queued 04:17
created

Full_Sync::continue_enqueuing()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 1
dl 0
loc 9
rs 9.9666
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
	/**
37
	 * Enqueue Lock name.
38
	 *
39
	 * @var string
40
	 */
41
	const ENQUEUE_LOCK_NAME = 'full_sync_enqueue';
42
43
	/**
44
	 * Sync module name.
45
	 *
46
	 * @access public
47
	 *
48
	 * @return string
49
	 */
50
	public function name() {
51
		return 'full-sync';
52
	}
53
54
	/**
55
	 * Initialize action listeners for full sync.
56
	 *
57
	 * @access public
58
	 *
59
	 * @param callable $callable Action handler callable.
60
	 */
61
	public function init_full_sync_listeners( $callable ) {
62
		// Synthetic actions for full sync.
63
		add_action( 'jetpack_full_sync_start', $callable, 10, 3 );
64
		add_action( 'jetpack_full_sync_end', $callable, 10, 2 );
65
		add_action( 'jetpack_full_sync_cancelled', $callable );
66
	}
67
68
	/**
69
	 * Initialize the module in the sender.
70
	 *
71
	 * @access public
72
	 */
73
	public function init_before_send() {
74
		// This is triggered after actions have been processed on the server.
75
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
76
	}
77
78
	/**
79
	 * Start a full sync.
80
	 *
81
	 * @access public
82
	 *
83
	 * @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...
84
	 * @return bool Always returns true at success.
85
	 */
86
	public function start( $module_configs = null ) {
87
		$was_already_running = $this->is_started() && ! $this->is_finished();
88
89
		// Remove all evidence of previous full sync items and status.
90
		$this->reset_data();
91
92
		if ( $was_already_running ) {
93
			/**
94
			 * Fires when a full sync is cancelled.
95
			 *
96
			 * @since 4.2.0
97
			 */
98
			do_action( 'jetpack_full_sync_cancelled' );
99
		}
100
101
		$this->update_status_option( 'started', time() );
102
		$this->update_status_option( 'params', $module_configs );
103
104
		$enqueue_status   = array();
105
		$full_sync_config = array();
106
		$include_empty    = false;
107
		$empty            = array();
108
109
		// Default value is full sync.
110
		if ( ! is_array( $module_configs ) ) {
111
			$module_configs = array();
112
			$include_empty  = true;
113
			foreach ( Modules::get_modules() as $module ) {
0 ignored issues
show
Bug introduced by
The expression \Automattic\Jetpack\Sync\Modules::get_modules() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
114
				$module_configs[ $module->name() ] = true;
115
			}
116
		}
117
118
		// Set default configuration, calculate totals, and save configuration if totals > 0.
119
		foreach ( Modules::get_modules() as $module ) {
0 ignored issues
show
Bug introduced by
The expression \Automattic\Jetpack\Sync\Modules::get_modules() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
120
			$module_name   = $module->name();
121
			$module_config = isset( $module_configs[ $module_name ] ) ? $module_configs[ $module_name ] : false;
122
123
			if ( ! $module_config ) {
124
				continue;
125
			}
126
127
			if ( 'users' === $module_name && 'initial' === $module_config ) {
128
				$module_config = $module->get_initial_sync_user_config();
129
			}
130
131
			$enqueue_status[ $module_name ] = false;
132
133
			$total_items = $module->estimate_full_sync_actions( $module_config );
134
135
			// If there's information to process, configure this module.
136
			if ( ! is_null( $total_items ) && $total_items > 0 ) {
137
				$full_sync_config[ $module_name ] = $module_config;
138
				$enqueue_status[ $module_name ]   = array(
139
					$total_items,   // Total.
140
					0,              // Queued.
141
					false,          // Current state.
142
				);
143
			} elseif ( $include_empty && 0 === $total_items ) {
144
				$empty[ $module_name ] = true;
145
			}
146
		}
147
148
		$this->set_config( $full_sync_config );
149
		$this->set_enqueue_status( $enqueue_status );
150
151
		$range = $this->get_content_range( $full_sync_config );
152
		/**
153
		 * Fires when a full sync begins. This action is serialized
154
		 * and sent to the server so that it knows a full sync is coming.
155
		 *
156
		 * @since 4.2.0
157
		 * @since 7.3.0 Added $range arg.
158
		 * @since 7.4.0 Added $empty arg.
159
		 *
160
		 * @param array $full_sync_config Sync configuration for all sync modules.
161
		 * @param array $range            Range of the sync items, containing min and max IDs for some item types.
162
		 * @param array $empty            The modules with no items to sync during a full sync.
163
		 */
164
		do_action( 'jetpack_full_sync_start', $full_sync_config, $range, $empty );
165
166
		$this->continue_enqueuing( $full_sync_config );
167
168
		return true;
169
	}
170
171
	/**
172
	 * Enqueue the next items to sync.
173
	 *
174
	 * @access public
175
	 *
176
	 * @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...
177
	 */
178
	public function continue_enqueuing( $configs = null ) {
179
		if ( ! $this->is_started() || ! ( new Lock() )->attempt( self::ENQUEUE_LOCK_NAME ) || $this->get_status_option( 'queue_finished' ) ) {
180
			return;
181
		}
182
183
		$this->enqueue( $configs );
184
185
		( new Lock() )->remove( self::ENQUEUE_LOCK_NAME );
186
	}
187
188
	/**
189
	 * Get Modules that are configured to Full Sync and haven't finished enqueuing
190
	 *
191
	 * @param array $configs Full sync configuration for all sync modules.
192
	 *
193
	 * @return array
194
	 */
195
	public function get_remaining_modules_to_enqueue( $configs ) {
196
		$enqueue_status = $this->get_enqueue_status();
197
		return array_filter(
198
			Modules::get_modules(),
199
			/**
200
			 * Select configured and not finished modules.
201
			 *
202
			 * @var $module Module
203
			 * @return bool
204
			 */
205
			function ( $module ) use ( $configs, $enqueue_status ) {
206
				// Skip module if not configured for this sync or module is done.
207
				if ( ! isset( $configs[ $module->name() ] ) ) {
208
					return false;
209
				}
210
				if ( ! $configs[ $module->name() ] ) {
211
					return false;
212
				}
213
				if ( isset( $enqueue_status[ $module->name() ][2] ) ) {
214
					if ( true === $enqueue_status[ $module->name() ][2] ) {
215
						return false;
216
					}
217
				}
218
219
				return true;
220
			}
221
		);
222
	}
223
224
	/**
225
	 * Enqueue the next items to sync.
226
	 *
227
	 * @access public
228
	 *
229
	 * @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...
230
	 */
231
	public function enqueue( $configs = null ) {
232
		if ( ! $configs ) {
233
			$configs = $this->get_config();
234
		}
235
236
		$enqueue_status        = $this->get_enqueue_status();
237
		$full_sync_queue       = new Queue( 'full_sync' );
238
		$available_queue_slots = Settings::get_setting( 'max_queue_size_full_sync' ) - $full_sync_queue->size();
239
240
		if ( $available_queue_slots <= 0 ) {
241
			return;
242
		}
243
244
		$remaining_items_to_enqueue = min( Settings::get_setting( 'max_enqueue_full_sync' ), $available_queue_slots );
245
246
		/**
247
		 * If a module exits early (e.g. because it ran out of full sync queue slots, or we ran out of request time)
248
		 * then it should exit early
249
		 */
250
		foreach ( $this->get_remaining_modules_to_enqueue( $configs ) as $module ) {
251
			list( $items_enqueued, $next_enqueue_state ) = $module->enqueue_full_sync_actions( $configs[ $module->name() ], $remaining_items_to_enqueue, $enqueue_status[ $module->name() ][2] );
252
253
			$enqueue_status[ $module->name() ][2] = $next_enqueue_state;
254
255
			// If items were processed, subtract them from the limit.
256
			if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) {
257
				$enqueue_status[ $module->name() ][1] += $items_enqueued;
258
				$remaining_items_to_enqueue           -= $items_enqueued;
259
			}
260
261
			if ( 0 >= $remaining_items_to_enqueue || true !== $next_enqueue_state ) {
262
				$this->set_enqueue_status( $enqueue_status );
263
				return;
264
			}
265
		}
266
267
		$this->queue_full_sync_end( $configs );
268
		$this->set_enqueue_status( $enqueue_status );
269
	}
270
271
	/**
272
	 * Enqueue 'jetpack_full_sync_end' and update 'queue_finished' status.
273
	 *
274
	 * @access public
275
	 *
276
	 * @param array $configs Full sync configuration for all sync modules.
277
	 */
278
	public function queue_full_sync_end( $configs ) {
279
		$range = $this->get_content_range( $configs );
280
281
		/**
282
		 * Fires when a full sync ends. This action is serialized
283
		 * and sent to the server.
284
		 *
285
		 * @since 4.2.0
286
		 * @since 7.3.0 Added $range arg.
287
		 *
288
		 * @param string $checksum Deprecated since 7.3.0 - @see https://github.com/Automattic/jetpack/pull/11945/
289
		 * @param array  $range    Range of the sync items, containing min and max IDs for some item types.
290
		 */
291
		do_action( 'jetpack_full_sync_end', '', $range );
292
293
		// Setting autoload to true means that it's faster to check whether we should continue enqueuing.
294
		$this->update_status_option( 'queue_finished', time(), true );
295
	}
296
297
	/**
298
	 * Get the range (min ID, max ID and total items) of items to sync.
299
	 *
300
	 * @access public
301
	 *
302
	 * @param string $type Type of sync item to get the range for.
303
	 * @return array Array of min ID, max ID and total items in the range.
304
	 */
305
	public function get_range( $type ) {
306
		global $wpdb;
307
		if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
308
			return array();
309
		}
310
311
		switch ( $type ) {
312
			case 'posts':
313
				$table     = $wpdb->posts;
314
				$id        = 'ID';
315
				$where_sql = Settings::get_blacklisted_post_types_sql();
316
317
				break;
318
			case 'comments':
319
				$table     = $wpdb->comments;
320
				$id        = 'comment_ID';
321
				$where_sql = Settings::get_comments_filter_sql();
322
				break;
323
		}
324
325
		// TODO: Call $wpdb->prepare on the following query.
326
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
327
		$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...
328
		if ( isset( $results[0] ) ) {
329
			return $results[0];
330
		}
331
332
		return array();
333
	}
334
335
	/**
336
	 * Get the range for content (posts and comments) to sync.
337
	 *
338
	 * @access private
339
	 *
340
	 * @param array $config Full sync configuration for this all sync modules.
341
	 * @return array Array of range (min ID, max ID, total items) for all content types.
342
	 */
343
	private function get_content_range( $config ) {
344
		$range = array();
345
		// Only when we are sending the whole range do we want to send also the range.
346
		if ( true === isset( $config['posts'] ) && $config['posts'] ) {
347
			$range['posts'] = $this->get_range( 'posts' );
348
		}
349
350
		if ( true === isset( $config['comments'] ) && $config['comments'] ) {
351
			$range['comments'] = $this->get_range( 'comments' );
352
		}
353
		return $range;
354
	}
355
356
	/**
357
	 * Update the progress after sync modules actions have been processed on the server.
358
	 *
359
	 * @access public
360
	 *
361
	 * @param array $actions Actions that have been processed on the server.
362
	 */
363
	public function update_sent_progress_action( $actions ) {
364
		// Quick way to map to first items with an array of arrays.
365
		$actions_with_counts = array_count_values( array_filter( array_map( array( $this, 'get_action_name' ), $actions ) ) );
366
367
		// Total item counts for each action.
368
		$actions_with_total_counts = $this->get_actions_totals( $actions );
369
370
		if ( ! $this->is_started() || $this->is_finished() ) {
371
			return;
372
		}
373
374
		if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
375
			$this->update_status_option( 'send_started', time() );
376
		}
377
378
		foreach ( Modules::get_modules() as $module ) {
0 ignored issues
show
Bug introduced by
The expression \Automattic\Jetpack\Sync\Modules::get_modules() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
379
			$module_actions     = $module->get_full_sync_actions();
380
			$status_option_name = "{$module->name()}_sent";
381
			$total_option_name  = "{$status_option_name}_total";
382
			$items_sent         = $this->get_status_option( $status_option_name, 0 );
383
			$items_sent_total   = $this->get_status_option( $total_option_name, 0 );
384
385
			foreach ( $module_actions as $module_action ) {
386
				if ( isset( $actions_with_counts[ $module_action ] ) ) {
387
					$items_sent += $actions_with_counts[ $module_action ];
388
				}
389
390
				if ( ! empty( $actions_with_total_counts[ $module_action ] ) ) {
391
					$items_sent_total += $actions_with_total_counts[ $module_action ];
392
				}
393
			}
394
395
			if ( $items_sent > 0 ) {
396
				$this->update_status_option( $status_option_name, $items_sent );
397
			}
398
399
			if ( 0 !== $items_sent_total ) {
400
				$this->update_status_option( $total_option_name, $items_sent_total );
401
			}
402
		}
403
404
		if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
405
			$this->update_status_option( 'finished', time() );
406
		}
407
	}
408
409
	/**
410
	 * Get the name of the action for an item in the sync queue.
411
	 *
412
	 * @access public
413
	 *
414
	 * @param array $queue_item Item of the sync queue.
415
	 * @return string|boolean Name of the action, false if queue item is invalid.
416
	 */
417
	public function get_action_name( $queue_item ) {
418
		if ( is_array( $queue_item ) && isset( $queue_item[0] ) ) {
419
			return $queue_item[0];
420
		}
421
		return false;
422
	}
423
424
	/**
425
	 * Retrieve the total number of items we're syncing in a particular queue item (action).
426
	 * `$queue_item[1]` is expected to contain chunks of items, and `$queue_item[1][0]`
427
	 * represents the first (and only) chunk of items to sync in that action.
428
	 *
429
	 * @access public
430
	 *
431
	 * @param array $queue_item Item of the sync queue that corresponds to a particular action.
432
	 * @return int Total number of items in the action.
433
	 */
434
	public function get_action_totals( $queue_item ) {
435
		if ( is_array( $queue_item ) && isset( $queue_item[1][0] ) ) {
436
			if ( is_array( $queue_item[1][0] ) ) {
437
				// Let's count the items we sync in this action.
438
				return count( $queue_item[1][0] );
439
			}
440
			// -1 indicates that this action syncs all items by design.
441
			return -1;
442
		}
443
		return 0;
444
	}
445
446
	/**
447
	 * Retrieve the total number of items for a set of actions, grouped by action name.
448
	 *
449
	 * @access public
450
	 *
451
	 * @param array $actions An array of actions.
452
	 * @return array An array, representing the total number of items, grouped per action.
453
	 */
454
	public function get_actions_totals( $actions ) {
455
		$totals = array();
456
457
		foreach ( $actions as $action ) {
458
			$name          = $this->get_action_name( $action );
459
			$action_totals = $this->get_action_totals( $action );
460
			if ( ! isset( $totals[ $name ] ) ) {
461
				$totals[ $name ] = 0;
462
			}
463
			$totals[ $name ] += $action_totals;
464
		}
465
466
		return $totals;
467
	}
468
469
	/**
470
	 * Whether full sync has started.
471
	 *
472
	 * @access public
473
	 *
474
	 * @return boolean
475
	 */
476
	public function is_started() {
477
		return ! ! $this->get_status_option( 'started' );
478
	}
479
480
	/**
481
	 * Whether full sync has finished.
482
	 *
483
	 * @access public
484
	 *
485
	 * @return boolean
486
	 */
487
	public function is_finished() {
488
		return ! ! $this->get_status_option( 'finished' );
489
	}
490
491
	/**
492
	 * Retrieve the status of the current full sync.
493
	 *
494
	 * @access public
495
	 *
496
	 * @return array Full sync status.
497
	 */
498
	public function get_status() {
499
		$status = array(
500
			'started'        => $this->get_status_option( 'started' ),
501
			'queue_finished' => $this->get_status_option( 'queue_finished' ),
502
			'send_started'   => $this->get_status_option( 'send_started' ),
503
			'finished'       => $this->get_status_option( 'finished' ),
504
			'sent'           => array(),
505
			'sent_total'     => array(),
506
			'queue'          => array(),
507
			'config'         => $this->get_status_option( 'params' ),
508
			'total'          => array(),
509
		);
510
511
		$enqueue_status = $this->get_enqueue_status();
512
513
		foreach ( Modules::get_modules() as $module ) {
0 ignored issues
show
Bug introduced by
The expression \Automattic\Jetpack\Sync\Modules::get_modules() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
514
			$name = $module->name();
515
516
			if ( ! isset( $enqueue_status[ $name ] ) ) {
517
				continue;
518
			}
519
520
			list( $total, $queued ) = $enqueue_status[ $name ];
521
522
			if ( $total ) {
523
				$status['total'][ $name ] = $total;
524
			}
525
526
			if ( $queued ) {
527
				$status['queue'][ $name ] = $queued;
528
			}
529
530
			$sent = $this->get_status_option( "{$name}_sent" );
531
			if ( $sent ) {
532
				$status['sent'][ $name ] = $sent;
533
			}
534
535
			$sent_total = $this->get_status_option( "{$name}_sent_total" );
536
			if ( $sent_total ) {
537
				$status['sent_total'][ $name ] = $sent_total;
538
			}
539
		}
540
541
		return $status;
542
	}
543
544
	/**
545
	 * Clear all the full sync status options.
546
	 *
547
	 * @access public
548
	 */
549
	public function clear_status() {
550
		$prefix = self::STATUS_OPTION_PREFIX;
551
		\Jetpack_Options::delete_raw_option( "{$prefix}_started" );
552
		\Jetpack_Options::delete_raw_option( "{$prefix}_params" );
553
		\Jetpack_Options::delete_raw_option( "{$prefix}_queue_finished" );
554
		\Jetpack_Options::delete_raw_option( "{$prefix}_send_started" );
555
		\Jetpack_Options::delete_raw_option( "{$prefix}_finished" );
556
557
		$this->delete_enqueue_status();
558
559
		foreach ( Modules::get_modules() as $module ) {
0 ignored issues
show
Bug introduced by
The expression \Automattic\Jetpack\Sync\Modules::get_modules() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
560
			\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent" );
561
			\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent_total" );
562
		}
563
	}
564
565
	/**
566
	 * Clear all the full sync data.
567
	 *
568
	 * @access public
569
	 */
570
	public function reset_data() {
571
		$this->clear_status();
572
		$this->delete_config();
573
		( new Lock() )->remove( self::ENQUEUE_LOCK_NAME );
574
575
		$listener = Listener::get_instance();
576
		$listener->get_full_sync_queue()->reset();
577
	}
578
579
	/**
580
	 * Get the value of a full sync status option.
581
	 *
582
	 * @access private
583
	 *
584
	 * @param string $name    Name of the option.
585
	 * @param mixed  $default Default value of the option.
586
	 * @return mixed Option value.
587
	 */
588
	private function get_status_option( $name, $default = null ) {
589
		$value = \Jetpack_Options::get_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $default );
590
591
		return is_numeric( $value ) ? intval( $value ) : $value;
592
	}
593
594
	/**
595
	 * Update the value of a full sync status option.
596
	 *
597
	 * @access private
598
	 *
599
	 * @param string  $name     Name of the option.
600
	 * @param mixed   $value    Value of the option.
601
	 * @param boolean $autoload Whether the option should be autoloaded at the beginning of the request.
602
	 */
603
	private function update_status_option( $name, $value, $autoload = false ) {
604
		\Jetpack_Options::update_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $value, $autoload );
605
	}
606
607
	/**
608
	 * Set the full sync enqueue status.
609
	 *
610
	 * @access private
611
	 *
612
	 * @param array $new_status The new full sync enqueue status.
613
	 */
614
	private function set_enqueue_status( $new_status ) {
615
		\Jetpack_Options::update_raw_option( 'jetpack_sync_full_enqueue_status', $new_status );
616
	}
617
618
	/**
619
	 * Delete full sync enqueue status.
620
	 *
621
	 * @access private
622
	 *
623
	 * @return boolean Whether the status was deleted.
624
	 */
625
	private function delete_enqueue_status() {
626
		return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_enqueue_status' );
627
	}
628
629
	/**
630
	 * Retrieve the current full sync enqueue status.
631
	 *
632
	 * @access private
633
	 *
634
	 * @return array Full sync enqueue status.
635
	 */
636
	public function get_enqueue_status() {
637
		return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_enqueue_status' );
638
	}
639
640
	/**
641
	 * Set the full sync enqueue configuration.
642
	 *
643
	 * @access private
644
	 *
645
	 * @param array $config The new full sync enqueue configuration.
646
	 */
647
	private function set_config( $config ) {
648
		\Jetpack_Options::update_raw_option( 'jetpack_sync_full_config', $config );
649
	}
650
651
	/**
652
	 * Delete full sync configuration.
653
	 *
654
	 * @access private
655
	 *
656
	 * @return boolean Whether the configuration was deleted.
657
	 */
658
	private function delete_config() {
659
		return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_config' );
660
	}
661
662
	/**
663
	 * Retrieve the current full sync enqueue config.
664
	 *
665
	 * @access private
666
	 *
667
	 * @return array Full sync enqueue config.
668
	 */
669
	private function get_config() {
670
		return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
671
	}
672
673
}
674