Completed
Push — fix/concurrency-race ( 35614e...7b1800 )
by
unknown
90:55 queued 81:30
created

Sender::get_sync_wait_threshold()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Sync sender.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync;
9
10
use Automattic\Jetpack\Connection\Manager;
11
use Automattic\Jetpack\Constants;
12
13
/**
14
 * This class grabs pending actions from the queue and sends them
15
 */
16
class Sender {
17
	/**
18
	 * Name of the option that stores the time of the next sync.
19
	 *
20
	 * @access public
21
	 *
22
	 * @var string
23
	 */
24
	const NEXT_SYNC_TIME_OPTION_NAME = 'jetpack_next_sync_time';
25
26
	/**
27
	 * Sync timeout after a WPCOM error.
28
	 *
29
	 * @access public
30
	 *
31
	 * @var int
32
	 */
33
	const WPCOM_ERROR_SYNC_DELAY = 60;
34
35
	/**
36
	 * Sync timeout after a queue has been locked.
37
	 *
38
	 * @access public
39
	 *
40
	 * @var int
41
	 */
42
	const QUEUE_LOCKED_SYNC_DELAY = 10;
43
44
	/**
45
	 * Maximum bytes to checkout without exceeding the memory limit.
46
	 *
47
	 * @access private
48
	 *
49
	 * @var int
50
	 */
51
	private $dequeue_max_bytes;
52
53
	/**
54
	 * Maximum bytes in a single encoded item.
55
	 *
56
	 * @access private
57
	 *
58
	 * @var int
59
	 */
60
	private $upload_max_bytes;
61
62
	/**
63
	 * Maximum number of sync items in a single action.
64
	 *
65
	 * @access private
66
	 *
67
	 * @var int
68
	 */
69
	private $upload_max_rows;
70
71
	/**
72
	 * Maximum time for perfirming a checkout of items from the queue (in seconds).
73
	 *
74
	 * @access private
75
	 *
76
	 * @var int
77
	 */
78
	private $max_dequeue_time;
79
80
	/**
81
	 * How many seconds to wait after sending sync items after exceeding the sync wait threshold (in seconds).
82
	 *
83
	 * @access private
84
	 *
85
	 * @var int
86
	 */
87
	private $sync_wait_time;
88
89
	/**
90
	 * How much maximum time to wait for the checkout to finish (in seconds).
91
	 *
92
	 * @access private
93
	 *
94
	 * @var int
95
	 */
96
	private $sync_wait_threshold;
97
98
	/**
99
	 * How much maximum time to wait for the sync items to be queued for sending (in seconds).
100
	 *
101
	 * @access private
102
	 *
103
	 * @var int
104
	 */
105
	private $enqueue_wait_time;
106
107
	/**
108
	 * Incremental sync queue object.
109
	 *
110
	 * @access private
111
	 *
112
	 * @var Automattic\Jetpack\Sync\Queue
113
	 */
114
	private $sync_queue;
115
116
	/**
117
	 * Full sync queue object.
118
	 *
119
	 * @access private
120
	 *
121
	 * @var Automattic\Jetpack\Sync\Queue
122
	 */
123
	private $full_sync_queue;
124
125
	/**
126
	 * Codec object for encoding and decoding sync items.
127
	 *
128
	 * @access private
129
	 *
130
	 * @var Automattic\Jetpack\Sync\Codec_Interface
131
	 */
132
	private $codec;
133
134
	/**
135
	 * The current user before we change or clear it.
136
	 *
137
	 * @access private
138
	 *
139
	 * @var \WP_User
140
	 */
141
	private $old_user;
142
143
	/**
144
	 * Container for the singleton instance of this class.
145
	 *
146
	 * @access private
147
	 * @static
148
	 *
149
	 * @var Automattic\Jetpack\Sync\Sender
150
	 */
151
	private static $instance;
152
153
	/**
154
	 * Retrieve the singleton instance of this class.
155
	 *
156
	 * @access public
157
	 * @static
158
	 *
159
	 * @return Sender
160
	 */
161
	public static function get_instance() {
162
		if ( null === self::$instance ) {
163
			self::$instance = new self();
0 ignored issues
show
Documentation Bug introduced by
It seems like new self() of type object<Automattic\Jetpack\Sync\Sender> is incompatible with the declared type object<Automattic\Jetpac...ic\Jetpack\Sync\Sender> of property $instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
164
		}
165
166
		return self::$instance;
167
	}
168
169
	/**
170
	 * Constructor.
171
	 * This is necessary because you can't use "new" when you declare instance properties >:(
172
	 *
173
	 * @access protected
174
	 * @static
175
	 */
176
	protected function __construct() {
177
		$this->set_defaults();
178
		$this->init();
179
	}
180
181
	/**
182
	 * Initialize the sender.
183
	 * Prepares the current user and initializes all sync modules.
184
	 *
185
	 * @access private
186
	 */
187
	private function init() {
188
		add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_set_user_from_token' ), 1 );
189
		add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_clear_user_from_token' ), 20 );
190
		add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( $this, 'register_jetpack_xmlrpc_methods' ) );
191
		foreach ( Modules::get_modules() as $module ) {
192
			$module->init_before_send();
193
		}
194
	}
195
196
	/**
197
	 * Detect if this is a XMLRPC request with a valid signature.
198
	 * If so, changes the user to the new one.
199
	 *
200
	 * @access public
201
	 */
202
	public function maybe_set_user_from_token() {
203
		$connection    = new Manager();
204
		$verified_user = $connection->verify_xml_rpc_signature();
205
		if ( Constants::is_true( 'XMLRPC_REQUEST' ) &&
206
			! is_wp_error( $verified_user )
207
			&& $verified_user
208
		) {
209
			$old_user       = wp_get_current_user();
210
			$this->old_user = isset( $old_user->ID ) ? $old_user->ID : 0;
211
			wp_set_current_user( $verified_user['user_id'] );
212
		}
213
	}
214
215
	/**
216
	 * If we used to have a previous current user, revert back to it.
217
	 *
218
	 * @access public
219
	 */
220
	public function maybe_clear_user_from_token() {
221
		if ( isset( $this->old_user ) ) {
222
			wp_set_current_user( $this->old_user );
223
		}
224
	}
225
226
	/**
227
	 * Retrieve the next sync time.
228
	 *
229
	 * @access public
230
	 *
231
	 * @param string $queue_name Name of the queue.
232
	 * @return float Timestamp of the next sync.
233
	 */
234
	public function get_next_sync_time( $queue_name ) {
235
		return (float) get_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, 0 );
236
	}
237
238
	/**
239
	 * Set the next sync time.
240
	 *
241
	 * @access public
242
	 *
243
	 * @param int    $time       Timestamp of the next sync.
244
	 * @param string $queue_name Name of the queue.
245
	 * @return boolean True if update was successful, false otherwise.
246
	 */
247
	public function set_next_sync_time( $time, $queue_name ) {
248
		return update_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, $time, true );
249
	}
250
251
	/**
252
	 * Trigger a full sync.
253
	 *
254
	 * @access public
255
	 *
256
	 * @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
257
	 */
258
	public function do_full_sync() {
259
		$sync_module = Modules::get_module( 'full-sync' );
260
		if ( ! $sync_module ) {
261
			return;
262
		}
263
		if ( ! Settings::get_setting( 'full_sync_sender_enabled' ) ) {
264
			return;
265
		}
266
267
		// Don't sync if request is marked as read only.
268
		if ( Constants::is_true( 'JETPACK_SYNC_READ_ONLY' ) ) {
269
			return new \WP_Error( 'jetpack_sync_read_only' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'jetpack_sync_read_only'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
270
		}
271
272
		$this->continue_full_sync_enqueue();
273
		// immediate full sync sends data in continue_full_sync_enqueue.
274
		if ( false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
275
			return $this->do_sync_and_set_delays( $this->full_sync_queue );
276
		} else {
277
			$status = $sync_module->get_status();
278
			// Sync not started or Sync finished.
279
			if ( false === $status['started'] || ( ! empty( $status['started'] ) && ! empty( $status['finished'] ) ) ) {
280
				return false;
281
			} else {
282
				return true;
283
			}
284
		}
285
	}
286
287
	/**
288
	 * Enqueue the next sync items for sending.
289
	 * Will not be done if the current request is a WP import one.
290
	 * Will be delayed until the next sync time comes.
291
	 *
292
	 * @access private
293
	 */
294
	private function continue_full_sync_enqueue() {
295
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
296
			return false;
297
		}
298
299
		if ( $this->get_next_sync_time( 'full-sync-enqueue' ) > microtime( true ) ) {
300
			return false;
301
		}
302
303
		Modules::get_module( 'full-sync' )->continue_enqueuing();
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 continue_enqueuing() does only exist in the following sub-classes of Automattic\Jetpack\Sync\Modules\Module: Automattic\Jetpack\Sync\Modules\Full_Sync, Automattic\Jetpack\Sync\...s\Full_Sync_Immediately. 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...
304
305
		$this->set_next_sync_time( time() + $this->get_enqueue_wait_time(), 'full-sync-enqueue' );
306
	}
307
308
	/**
309
	 * Trigger incremental sync.
310
	 *
311
	 * @access public
312
	 *
313
	 * @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
314
	 */
315
	public function do_sync() {
316
		return $this->do_sync_and_set_delays( $this->sync_queue );
317
	}
318
319
	/**
320
	 * Trigger sync for a certain sync queue.
321
	 * Responsible for setting next sync time.
322
	 * Will not be delayed if the current request is a WP import one.
323
	 * Will be delayed until the next sync time comes.
324
	 *
325
	 * @access public
326
	 *
327
	 * @param Automattic\Jetpack\Sync\Queue $queue Queue object.
328
	 * @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
329
	 */
330
	public function do_sync_and_set_delays( $queue ) {
331
		// Don't sync if importing.
332
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
333
			return new \WP_Error( 'is_importing' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'is_importing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
334
		}
335
336
		// Don't sync if request is marked as read only.
337
		if ( Constants::is_true( 'JETPACK_SYNC_READ_ONLY' ) ) {
338
			return new \WP_Error( 'jetpack_sync_read_only' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'jetpack_sync_read_only'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
339
		}
340
341
		if ( ! Settings::is_sender_enabled( $queue->id ) ) {
342
			return new \WP_Error( 'sender_disabled_for_queue_' . $queue->id );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'sender_disabled_for_queue_' . $queue->id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
343
		}
344
345
		// Return early if we've gotten a retry-after header response.
346
		$retry_time = get_option( Actions::RETRY_AFTER_PREFIX . $queue->id );
347
		if ( $retry_time ) {
348
			// If expired delete but don't send. Send will occurr in new request to avoid race conditions.
349
			if ( microtime( true ) > $retry_time ) {
350
				delete_option( Actions::RETRY_AFTER_PREFIX . $queue->id );
351
			}
352
			return new \WP_Error( 'retry_after' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'retry_after'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
353
		}
354
355
		// Don't sync if we are throttled.
356
		if ( $this->get_next_sync_time( $queue->id ) > microtime( true ) ) {
357
			return new \WP_Error( 'sync_throttled' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'sync_throttled'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
358
		}
359
360
		$start_time = microtime( true );
361
362
		Settings::set_is_syncing( true );
363
364
		$sync_result = $this->do_sync_for_queue( $queue );
365
366
		Settings::set_is_syncing( false );
367
368
		$exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (float) $this->get_sync_wait_threshold();
369
370
		if ( is_wp_error( $sync_result ) ) {
371
			if ( 'unclosed_buffer' === $sync_result->get_error_code() ) {
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
372
				$this->set_next_sync_time( time() + self::QUEUE_LOCKED_SYNC_DELAY, $queue->id );
373
			}
374
			if ( 'wpcom_error' === $sync_result->get_error_code() ) {
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
375
				$this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY, $queue->id );
376
			}
377
		} elseif ( $exceeded_sync_wait_threshold ) {
378
			// If we actually sent data and it took a while, wait before sending again.
379
			$this->set_next_sync_time( time() + $this->get_sync_wait_time(), $queue->id );
380
		}
381
382
		return $sync_result;
383
	}
384
385
	/**
386
	 * Retrieve the next sync items to send.
387
	 *
388
	 * @access public
389
	 *
390
	 * @param (array|Automattic\Jetpack\Sync\Queue_Buffer) $buffer_or_items Queue buffer or array of objects.
391
	 * @param boolean                                      $encode Whether to encode the items.
392
	 * @return array Sync items to send.
393
	 */
394
	public function get_items_to_send( $buffer_or_items, $encode = true ) {
395
		// Track how long we've been processing so we can avoid request timeouts.
396
		$start_time    = microtime( true );
397
		$upload_size   = 0;
398
		$items_to_send = array();
399
		$items         = is_array( $buffer_or_items ) ? $buffer_or_items : $buffer_or_items->get_items();
400
		// Set up current screen to avoid errors rendering content.
401
		require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
402
		require_once ABSPATH . 'wp-admin/includes/screen.php';
403
		set_current_screen( 'sync' );
404
		$skipped_items_ids = array();
405
		/**
406
		 * We estimate the total encoded size as we go by encoding each item individually.
407
		 * This is expensive, but the only way to really know :/
408
		 */
409
		foreach ( $items as $key => $item ) {
410
			// Suspending cache addition help prevent overloading in memory cache of large sites.
411
			wp_suspend_cache_addition( true );
412
			/**
413
			 * Modify the data within an action before it is serialized and sent to the server
414
			 * For example, during full sync this expands Post ID's into full Post objects,
415
			 * so that we don't have to serialize the whole object into the queue.
416
			 *
417
			 * @since 4.2.0
418
			 *
419
			 * @param array The action parameters
420
			 * @param int The ID of the user who triggered the action
421
			 */
422
			$item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $item[2].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
423
			wp_suspend_cache_addition( false );
424
			if ( false === $item[1] ) {
425
				$skipped_items_ids[] = $key;
426
				continue;
427
			}
428
			$encoded_item = $encode ? $this->codec->encode( $item ) : $item;
429
			$upload_size += strlen( $encoded_item );
430
			if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
431
				break;
432
			}
433
			$items_to_send[ $key ] = $encoded_item;
434
			if ( microtime( true ) - $start_time > $this->max_dequeue_time ) {
435
				break;
436
			}
437
		}
438
439
		return array( $items_to_send, $skipped_items_ids, $items, microtime( true ) - $start_time );
440
	}
441
442
	/**
443
	 * If supported, flush all response data to the client and finish the request.
444
	 * This allows for time consuming tasks to be performed without leaving the connection open.
445
	 *
446
	 * @access private
447
	 */
448
	private function fastcgi_finish_request() {
449
		if ( function_exists( 'fastcgi_finish_request' ) && version_compare( phpversion(), '7.0.16', '>=' ) ) {
450
			fastcgi_finish_request();
451
		}
452
	}
453
454
	/**
455
	 * Perform sync for a certain sync queue.
456
	 *
457
	 * @access public
458
	 *
459
	 * @param Automattic\Jetpack\Sync\Queue $queue Queue object.
460
	 * @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
461
	 */
462
	public function do_sync_for_queue( $queue ) {
463
		do_action( 'jetpack_sync_before_send_queue_' . $queue->id );
464
		if ( $queue->size() === 0 ) {
465
			return new \WP_Error( 'empty_queue_' . $queue->id );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'empty_queue_' . $queue->id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
466
		}
467
468
		/**
469
		 * Now that we're sure we are about to sync, try to ignore user abort
470
		 * so we can avoid getting into a bad state.
471
		 */
472
		if ( function_exists( 'ignore_user_abort' ) ) {
473
			ignore_user_abort( true );
474
		}
475
476
		/* Don't make the request block till we finish, if possible. */
477
		if ( Constants::is_true( 'REST_REQUEST' ) || Constants::is_true( 'XMLRPC_REQUEST' ) ) {
478
			$this->fastcgi_finish_request();
479
		}
480
481
		$checkout_start_time = microtime( true );
482
483
		$buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows );
484
485
		if ( ! $buffer ) {
486
			// Buffer has no items.
487
			return new \WP_Error( 'empty_buffer' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'empty_buffer'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
488
		}
489
490
		if ( is_wp_error( $buffer ) ) {
491
			return $buffer;
492
		}
493
494
		$checkout_duration = microtime( true ) - $checkout_start_time;
495
496
		list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $buffer, true );
497
		if ( ! empty( $items_to_send ) ) {
498
			/**
499
			 * Fires when data is ready to send to the server.
500
			 * Return false or WP_Error to abort the sync (e.g. if there's an error)
501
			 * The items will be automatically re-sent later
502
			 *
503
			 * @since 4.2.0
504
			 *
505
			 * @param array  $data The action buffer
506
			 * @param string $codec The codec name used to encode the data
507
			 * @param double $time The current time
508
			 * @param string $queue The queue used to send ('sync' or 'full_sync')
509
			 * @param float  $checkout_duration The duration of the checkout operation.
510
			 * @param float  $preprocess_duration The duration of the pre-process operation.
511
			 * @param int    $queue_size The size of the sync queue at the time of processing.
512
			 */
513
			Settings::set_is_sending( true );
514
			$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id, $checkout_duration, $preprocess_duration, $queue->size(), $buffer->id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $this->codec->name().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
515
			Settings::set_is_sending( false );
516
		} else {
517
			$processed_item_ids = $skipped_items_ids;
518
			$skipped_items_ids  = array();
519
		}
520
521
		if ( 'non-blocking' !== $processed_item_ids ) {
522
			if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) {
523
				$checked_in_item_ids = $queue->checkin( $buffer );
524
				if ( is_wp_error( $checked_in_item_ids ) ) {
525
					// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
526
					error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() );
527
					$queue->force_checkin();
528
				}
529
				if ( is_wp_error( $processed_item_ids ) ) {
530
					return new \WP_Error( 'wpcom_error', $processed_item_ids->get_error_code() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wpcom_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
531
				}
532
533
				// Returning a wpcom_error is a sign to the caller that we should wait a while before syncing again.
534
				return new \WP_Error( 'wpcom_error', 'jetpack_sync_send_data_false' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wpcom_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
535
			} else {
536
				// Detect if the last item ID was an error.
537
				$had_wp_error = is_wp_error( end( $processed_item_ids ) );
538
				if ( $had_wp_error ) {
539
					$wp_error = array_pop( $processed_item_ids );
540
				}
541
				// Also checkin any items that were skipped.
542
				if ( count( $skipped_items_ids ) > 0 ) {
543
					$processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
544
				}
545
				$processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );
546
				/**
547
				 * Allows us to keep track of all the actions that have been sent.
548
				 * Allows us to calculate the progress of specific actions.
549
				 *
550
				 * @since 4.2.0
551
				 *
552
				 * @param array $processed_actions The actions that we send successfully.
553
				 */
554
				do_action( 'jetpack_sync_processed_actions', $processed_items );
555
				$queue->close( $buffer, $processed_item_ids );
556
				// Returning a WP_Error is a sign to the caller that we should wait a while before syncing again.
557
				if ( $had_wp_error ) {
558
					return new \WP_Error( 'wpcom_error', $wp_error->get_error_code() );
0 ignored issues
show
Bug introduced by
The variable $wp_error 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...
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wpcom_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
559
				}
560
			}
561
		}
562
563
		return true;
564
	}
565
566
	/**
567
	 * Immediately sends a single item without firing or enqueuing it
568
	 *
569
	 * @param string $action_name The action.
570
	 * @param array  $data The data associated with the action.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $data 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...
571
	 *
572
	 * @return Items processed. TODO: this doesn't make much sense anymore, it should probably be just a bool.
573
	 */
574
	public function send_action( $action_name, $data = null ) {
575
		if ( ! Settings::is_sender_enabled( 'full_sync' ) ) {
576
			return array();
577
		}
578
579
		// Compose the data to be sent.
580
		$action_to_send = $this->create_action_to_send( $action_name, $data );
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 574 can also be of type null; however, Automattic\Jetpack\Sync\...create_action_to_send() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
581
582
		list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $action_to_send, true ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
0 ignored issues
show
Unused Code introduced by
The assignment to $skipped_items_ids is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $items is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
583
		Settings::set_is_sending( true );
584
		$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->get_codec()->name(), microtime( true ), 'immediate-send', 0, $preprocess_duration );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $this->get_codec()->name().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
585
		Settings::set_is_sending( false );
586
587
		/**
588
		 * Allows us to keep track of all the actions that have been sent.
589
		 * Allows us to calculate the progress of specific actions.
590
		 *
591
		 * @param array $processed_actions The actions that we send successfully.
592
		 *
593
		 * @since 4.2.0
594
		 */
595
		do_action( 'jetpack_sync_processed_actions', $action_to_send );
596
597
		return $processed_item_ids;
598
	}
599
600
	/**
601
	 * Create an synthetic action for direct sending to WPCOM during full sync (for example)
602
	 *
603
	 * @access private
604
	 *
605
	 * @param string $action_name The action.
606
	 * @param array  $data The data associated with the action.
607
	 * @return array An array of synthetic sync actions keyed by current microtime(true)
608
	 */
609
	private function create_action_to_send( $action_name, $data ) {
610
		return array(
611
			(string) microtime( true ) => array(
612
				$action_name,
613
				$data,
614
				get_current_user_id(),
615
				microtime( true ),
616
				Settings::is_importing(),
617
			),
618
		);
619
	}
620
621
	/**
622
	 * Returns any object that is able to be synced.
623
	 *
624
	 * @access public
625
	 *
626
	 * @param array $args the synchronized object parameters.
627
	 * @return string Encoded sync object.
628
	 */
629
	public function sync_object( $args ) {
630
		// For example: posts, post, 5.
631
		list( $module_name, $object_type, $id ) = $args;
632
633
		$sync_module = Modules::get_module( $module_name );
634
		$codec       = $this->get_codec();
635
636
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
637
	}
638
639
	/**
640
	 * Register additional sync XML-RPC methods available to Jetpack for authenticated users.
641
	 *
642
	 * @access public
643
	 * @since 7.8
644
	 *
645
	 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
646
	 * @return array Filtered XML-RPC methods.
647
	 */
648
	public function register_jetpack_xmlrpc_methods( $jetpack_methods ) {
649
		$jetpack_methods['jetpack.syncObject'] = array( $this, 'sync_object' );
650
		return $jetpack_methods;
651
	}
652
653
	/**
654
	 * Get the incremental sync queue object.
655
	 *
656
	 * @access public
657
	 *
658
	 * @return Automattic\Jetpack\Sync\Queue Queue object.
659
	 */
660
	public function get_sync_queue() {
661
		return $this->sync_queue;
662
	}
663
664
	/**
665
	 * Get the full sync queue object.
666
	 *
667
	 * @access public
668
	 *
669
	 * @return Automattic\Jetpack\Sync\Queue Queue object.
670
	 */
671
	public function get_full_sync_queue() {
672
		return $this->full_sync_queue;
673
	}
674
675
	/**
676
	 * Get the codec object.
677
	 *
678
	 * @access public
679
	 *
680
	 * @return Automattic\Jetpack\Sync\Codec_Interface Codec object.
681
	 */
682
	public function get_codec() {
683
		return $this->codec;
684
	}
685
686
	/**
687
	 * Determine the codec object.
688
	 * Use gzip deflate if supported.
689
	 *
690
	 * @access public
691
	 */
692
	public function set_codec() {
693
		if ( function_exists( 'gzinflate' ) ) {
694
			$this->codec = new JSON_Deflate_Array_Codec();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Automattic\Jetpack\...N_Deflate_Array_Codec() of type object<Automattic\Jetpac...ON_Deflate_Array_Codec> is incompatible with the declared type object<Automattic\Jetpac...k\Sync\Codec_Interface> of property $codec.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
695
		} else {
696
			$this->codec = new Simple_Codec();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Automattic\Jetpack\Sync\Simple_Codec() of type object<Automattic\Jetpack\Sync\Simple_Codec> is incompatible with the declared type object<Automattic\Jetpac...k\Sync\Codec_Interface> of property $codec.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
697
		}
698
	}
699
700
	/**
701
	 * Compute and send all the checksums.
702
	 *
703
	 * @access public
704
	 */
705
	public function send_checksum() {
706
		$store = new Replicastore();
707
		do_action( 'jetpack_sync_checksum', $store->checksum_all() );
708
	}
709
710
	/**
711
	 * Reset the incremental sync queue.
712
	 *
713
	 * @access public
714
	 */
715
	public function reset_sync_queue() {
716
		$this->sync_queue->reset();
717
	}
718
719
	/**
720
	 * Reset the full sync queue.
721
	 *
722
	 * @access public
723
	 */
724
	public function reset_full_sync_queue() {
725
		$this->full_sync_queue->reset();
726
	}
727
728
	/**
729
	 * Set the maximum bytes to checkout without exceeding the memory limit.
730
	 *
731
	 * @access public
732
	 *
733
	 * @param int $size Maximum bytes to checkout.
734
	 */
735
	public function set_dequeue_max_bytes( $size ) {
736
		$this->dequeue_max_bytes = $size;
737
	}
738
739
	/**
740
	 * Set the maximum bytes in a single encoded item.
741
	 *
742
	 * @access public
743
	 *
744
	 * @param int $max_bytes Maximum bytes in a single encoded item.
745
	 */
746
	public function set_upload_max_bytes( $max_bytes ) {
747
		$this->upload_max_bytes = $max_bytes;
748
	}
749
750
	/**
751
	 * Set the maximum number of sync items in a single action.
752
	 *
753
	 * @access public
754
	 *
755
	 * @param int $max_rows Maximum number of sync items.
756
	 */
757
	public function set_upload_max_rows( $max_rows ) {
758
		$this->upload_max_rows = $max_rows;
759
	}
760
761
	/**
762
	 * Set the sync wait time (in seconds).
763
	 *
764
	 * @access public
765
	 *
766
	 * @param int $seconds Sync wait time.
767
	 */
768
	public function set_sync_wait_time( $seconds ) {
769
		$this->sync_wait_time = $seconds;
770
	}
771
772
	/**
773
	 * Get current sync wait time (in seconds).
774
	 *
775
	 * @access public
776
	 *
777
	 * @return int Sync wait time.
778
	 */
779
	public function get_sync_wait_time() {
780
		return $this->sync_wait_time;
781
	}
782
783
	/**
784
	 * Set the enqueue wait time (in seconds).
785
	 *
786
	 * @access public
787
	 *
788
	 * @param int $seconds Enqueue wait time.
789
	 */
790
	public function set_enqueue_wait_time( $seconds ) {
791
		$this->enqueue_wait_time = $seconds;
792
	}
793
794
	/**
795
	 * Get current enqueue wait time (in seconds).
796
	 *
797
	 * @access public
798
	 *
799
	 * @return int Enqueue wait time.
800
	 */
801
	public function get_enqueue_wait_time() {
802
		return $this->enqueue_wait_time;
803
	}
804
805
	/**
806
	 * Set the sync wait threshold (in seconds).
807
	 *
808
	 * @access public
809
	 *
810
	 * @param int $seconds Sync wait threshold.
811
	 */
812
	public function set_sync_wait_threshold( $seconds ) {
813
		$this->sync_wait_threshold = $seconds;
814
	}
815
816
	/**
817
	 * Get current sync wait threshold (in seconds).
818
	 *
819
	 * @access public
820
	 *
821
	 * @return int Sync wait threshold.
822
	 */
823
	public function get_sync_wait_threshold() {
824
		return $this->sync_wait_threshold;
825
	}
826
827
	/**
828
	 * Set the maximum time for perfirming a checkout of items from the queue (in seconds).
829
	 *
830
	 * @access public
831
	 *
832
	 * @param int $seconds Maximum dequeue time.
833
	 */
834
	public function set_max_dequeue_time( $seconds ) {
835
		$this->max_dequeue_time = $seconds;
836
	}
837
838
	/**
839
	 * Initialize the sync queues, codec and set the default settings.
840
	 *
841
	 * @access public
842
	 */
843
	public function set_defaults() {
844
		$this->sync_queue      = new Queue( 'sync' );
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Automattic\Jetpack\Sync\Queue('sync') of type object<Automattic\Jetpack\Sync\Queue> is incompatible with the declared type object<Automattic\Jetpac...tic\Jetpack\Sync\Queue> of property $sync_queue.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
845
		$this->full_sync_queue = new Queue( 'full_sync' );
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Automattic\Jetpack\Sync\Queue('full_sync') of type object<Automattic\Jetpack\Sync\Queue> is incompatible with the declared type object<Automattic\Jetpac...tic\Jetpack\Sync\Queue> of property $full_sync_queue.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
846
		$this->set_codec();
847
848
		// Saved settings.
849
		Settings::set_importing( null );
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
850
		$settings = Settings::get_settings();
851
		$this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] );
852
		$this->set_upload_max_bytes( $settings['upload_max_bytes'] );
853
		$this->set_upload_max_rows( $settings['upload_max_rows'] );
854
		$this->set_sync_wait_time( $settings['sync_wait_time'] );
855
		$this->set_enqueue_wait_time( $settings['enqueue_wait_time'] );
856
		$this->set_sync_wait_threshold( $settings['sync_wait_threshold'] );
857
		$this->set_max_dequeue_time( Defaults::get_max_sync_execution_time() );
858
	}
859
860
	/**
861
	 * Reset sync queues, modules and settings.
862
	 *
863
	 * @access public
864
	 */
865
	public function reset_data() {
866
		$this->reset_sync_queue();
867
		$this->reset_full_sync_queue();
868
869
		foreach ( Modules::get_modules() as $module ) {
870
			$module->reset_data();
871
		}
872
873
		foreach ( array( 'sync', 'full_sync', 'full-sync-enqueue' ) as $queue_name ) {
874
			delete_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name );
875
		}
876
877
		Settings::reset_data();
878
	}
879
880
	/**
881
	 * Perform cleanup at the event of plugin uninstallation.
882
	 *
883
	 * @access public
884
	 */
885
	public function uninstall() {
886
		// Lets delete all the other fun stuff like transient and option and the sync queue.
887
		$this->reset_data();
888
889
		// Delete the full sync status.
890
		delete_option( 'jetpack_full_sync_status' );
891
892
		// Clear the sync cron.
893
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
894
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
895
	}
896
}
897