Completed
Push — renovate/copy-webpack-plugin-5... ( 1645c2...f892fa )
by
unknown
233:24 queued 224:35
created

Sender   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 403
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 10

Importance

Changes 0
Metric Value
wmc 77
lcom 3
cbo 10
dl 0
loc 403
rs 2.24
c 0
b 0
f 0

34 Methods

Rating   Name   Duplication   Size   Complexity  
A get_instance() 0 7 2
A __construct() 0 4 1
A init() 0 7 2
A maybe_set_user_from_token() 0 12 5
A maybe_clear_user_from_token() 0 5 2
A get_next_sync_time() 0 3 1
A set_next_sync_time() 0 3 1
A do_full_sync() 0 7 2
A continue_full_sync_enqueue() 0 13 4
A do_sync() 0 3 1
B do_sync_and_set_delays() 0 35 8
B get_items_to_send() 0 45 7
A fastcgi_finish_request() 0 5 3
D do_sync_for_queue() 0 95 15
A get_sync_queue() 0 3 1
A get_full_sync_queue() 0 3 1
A get_codec() 0 3 1
A set_codec() 0 7 2
A send_checksum() 0 4 1
A reset_sync_queue() 0 3 1
A reset_full_sync_queue() 0 3 1
A set_dequeue_max_bytes() 0 3 1
A set_upload_max_bytes() 0 3 1
A set_upload_max_rows() 0 3 1
A set_sync_wait_time() 0 3 1
A get_sync_wait_time() 0 3 1
A set_enqueue_wait_time() 0 3 1
A get_enqueue_wait_time() 0 3 1
A set_sync_wait_threshold() 0 3 1
A get_sync_wait_threshold() 0 3 1
A set_max_dequeue_time() 0 3 1
A set_defaults() 0 16 1
A reset_data() 0 14 3
A uninstall() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like Sender often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Sender, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Automattic\Jetpack\Sync;
4
5
use Automattic\Jetpack\Constants;
6
7
/**
8
 * This class grabs pending actions from the queue and sends them
9
 */
10
class Sender {
11
12
	const NEXT_SYNC_TIME_OPTION_NAME = 'jetpack_next_sync_time';
13
	const WPCOM_ERROR_SYNC_DELAY     = 60;
14
	const QUEUE_LOCKED_SYNC_DELAY    = 10;
15
16
	private $dequeue_max_bytes;
17
	private $upload_max_bytes;
18
	private $upload_max_rows;
19
	private $max_dequeue_time;
20
	private $sync_wait_time;
21
	private $sync_wait_threshold;
22
	private $enqueue_wait_time;
23
	private $sync_queue;
24
	private $full_sync_queue;
25
	private $codec;
26
	private $old_user;
27
28
	// singleton functions
29
	private static $instance;
30
31
	public static function get_instance() {
32
		if ( null === self::$instance ) {
33
			self::$instance = new self();
34
		}
35
36
		return self::$instance;
37
	}
38
39
	// this is necessary because you can't use "new" when you declare instance properties >:(
40
	protected function __construct() {
41
		$this->set_defaults();
42
		$this->init();
43
	}
44
45
	private function init() {
46
		add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_set_user_from_token' ), 1 );
47
		add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_clear_user_from_token' ), 20 );
48
		foreach ( Modules::get_modules() as $module ) {
49
			$module->init_before_send();
50
		}
51
	}
52
53
	public function maybe_set_user_from_token() {
54
		$jetpack       = \Jetpack::init();
55
		$verified_user = $jetpack->verify_xml_rpc_signature();
56
		if ( Constants::is_true( 'XMLRPC_REQUEST' ) &&
57
			! is_wp_error( $verified_user )
58
			&& $verified_user
59
		) {
60
			$old_user       = wp_get_current_user();
61
			$this->old_user = isset( $old_user->ID ) ? $old_user->ID : 0;
62
			wp_set_current_user( $verified_user['user_id'] );
63
		}
64
	}
65
66
	public function maybe_clear_user_from_token() {
67
		if ( isset( $this->old_user ) ) {
68
			wp_set_current_user( $this->old_user );
69
		}
70
	}
71
72
	public function get_next_sync_time( $queue_name ) {
73
		return (float) get_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, 0 );
74
	}
75
76
	public function set_next_sync_time( $time, $queue_name ) {
77
		return update_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, $time, true );
78
	}
79
80
	public function do_full_sync() {
81
		if ( ! Modules::get_module( 'full-sync' ) ) {
82
			return;
83
		}
84
		$this->continue_full_sync_enqueue();
85
		return $this->do_sync_and_set_delays( $this->full_sync_queue );
86
	}
87
88
	private function continue_full_sync_enqueue() {
89
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
90
			return false;
91
		}
92
93
		if ( $this->get_next_sync_time( 'full-sync-enqueue' ) > microtime( true ) ) {
94
			return false;
95
		}
96
97
		Modules::get_module( 'full-sync' )->continue_enqueuing();
98
99
		$this->set_next_sync_time( time() + $this->get_enqueue_wait_time(), 'full-sync-enqueue' );
100
	}
101
102
	public function do_sync() {
103
		return $this->do_sync_and_set_delays( $this->sync_queue );
104
	}
105
106
	public function do_sync_and_set_delays( $queue ) {
107
		// don't sync if importing
108
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
109
			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...
110
		}
111
112
		// don't sync if we are throttled
113
		if ( $this->get_next_sync_time( $queue->id ) > microtime( true ) ) {
114
			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...
115
		}
116
117
		$start_time = microtime( true );
118
119
		Settings::set_is_syncing( true );
120
121
		$sync_result = $this->do_sync_for_queue( $queue );
122
123
		Settings::set_is_syncing( false );
124
125
		$exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (float) $this->get_sync_wait_threshold();
126
127
		if ( is_wp_error( $sync_result ) ) {
128
			if ( 'unclosed_buffer' === $sync_result->get_error_code() ) {
129
				$this->set_next_sync_time( time() + self::QUEUE_LOCKED_SYNC_DELAY, $queue->id );
130
			}
131
			if ( 'wpcom_error' === $sync_result->get_error_code() ) {
132
				$this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY, $queue->id );
133
			}
134
		} elseif ( $exceeded_sync_wait_threshold ) {
135
			// if we actually sent data and it took a while, wait before sending again
136
			$this->set_next_sync_time( time() + $this->get_sync_wait_time(), $queue->id );
137
		}
138
139
		return $sync_result;
140
	}
141
142
	public function get_items_to_send( $buffer, $encode = true ) {
143
		// track how long we've been processing so we can avoid request timeouts
144
		$start_time    = microtime( true );
145
		$upload_size   = 0;
146
		$items_to_send = array();
147
		$items         = $buffer->get_items();
148
		// set up current screen to avoid errors rendering content
149
		require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
150
		require_once ABSPATH . 'wp-admin/includes/screen.php';
151
		set_current_screen( 'sync' );
152
		$skipped_items_ids = array();
153
		// we estimate the total encoded size as we go by encoding each item individually
154
		// this is expensive, but the only way to really know :/
155
		foreach ( $items as $key => $item ) {
156
			// Suspending cache addition help prevent overloading in memory cache of large sites.
157
			wp_suspend_cache_addition( true );
158
			/**
159
			 * Modify the data within an action before it is serialized and sent to the server
160
			 * For example, during full sync this expands Post ID's into full Post objects,
161
			 * so that we don't have to serialize the whole object into the queue.
162
			 *
163
			 * @since 4.2.0
164
			 *
165
			 * @param array The action parameters
166
			 * @param int The ID of the user who triggered the action
167
			 */
168
			$item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] );
169
			wp_suspend_cache_addition( false );
170
			if ( $item[1] === false ) {
171
				$skipped_items_ids[] = $key;
172
				continue;
173
			}
174
			$encoded_item = $encode ? $this->codec->encode( $item ) : $item;
175
			$upload_size += strlen( $encoded_item );
176
			if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
177
				break;
178
			}
179
			$items_to_send[ $key ] = $encoded_item;
180
			if ( microtime( true ) - $start_time > $this->max_dequeue_time ) {
181
				break;
182
			}
183
		}
184
185
		return array( $items_to_send, $skipped_items_ids, $items, microtime( true ) - $start_time );
186
	}
187
188
	private function fastcgi_finish_request() {
189
		if ( function_exists( 'fastcgi_finish_request' ) && version_compare( phpversion(), '7.0.16', '>=' ) ) {
190
			fastcgi_finish_request();
191
		}
192
	}
193
194
	public function do_sync_for_queue( $queue ) {
195
		do_action( 'jetpack_sync_before_send_queue_' . $queue->id );
196
		if ( $queue->size() === 0 ) {
197
			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...
198
		}
199
		// now that we're sure we are about to sync, try to
200
		// ignore user abort so we can avoid getting into a
201
		// bad state
202
		if ( function_exists( 'ignore_user_abort' ) ) {
203
			ignore_user_abort( true );
204
		}
205
206
		/* Don't make the request block till we finish, if possible. */
207
		if ( Constants::is_true( 'REST_REQUEST' ) || Constants::is_true( 'XMLRPC_REQUEST' ) ) {
208
			$this->fastcgi_finish_request();
209
		}
210
211
		$checkout_start_time = microtime( true );
212
213
		$buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows );
214
215
		if ( ! $buffer ) {
216
			// buffer has no items
217
			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...
218
		}
219
220
		if ( is_wp_error( $buffer ) ) {
221
			return $buffer;
222
		}
223
224
		$checkout_duration = microtime( true ) - $checkout_start_time;
225
226
		list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $buffer, true );
227
		if ( ! empty( $items_to_send ) ) {
228
			/**
229
			 * Fires when data is ready to send to the server.
230
			 * Return false or WP_Error to abort the sync (e.g. if there's an error)
231
			 * The items will be automatically re-sent later
232
			 *
233
			 * @since 4.2.0
234
			 *
235
			 * @param array $data The action buffer
236
			 * @param string $codec The codec name used to encode the data
237
			 * @param double $time The current time
238
			 * @param string $queue The queue used to send ('sync' or 'full_sync')
239
			 */
240
			Settings::set_is_sending( true );
241
			$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id, $checkout_duration, $preprocess_duration );
242
			Settings::set_is_sending( false );
243
		} else {
244
			$processed_item_ids = $skipped_items_ids;
245
			$skipped_items_ids  = array();
246
		}
247
248
		if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) {
249
			$checked_in_item_ids = $queue->checkin( $buffer );
250
			if ( is_wp_error( $checked_in_item_ids ) ) {
251
				error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() );
252
				$queue->force_checkin();
253
			}
254
			if ( is_wp_error( $processed_item_ids ) ) {
255
				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...
256
			}
257
			// returning a WP_Error('wpcom_error') is a sign to the caller that we should wait a while
258
			// before syncing again
259
			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...
260
		} else {
261
			// detect if the last item ID was an error
262
			$had_wp_error = is_wp_error( end( $processed_item_ids ) );
263
			if ( $had_wp_error ) {
264
				$wp_error = array_pop( $processed_item_ids );
265
			}
266
			// also checkin any items that were skipped
267
			if ( count( $skipped_items_ids ) > 0 ) {
268
				$processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
269
			}
270
			$processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );
271
			/**
272
			 * Allows us to keep track of all the actions that have been sent.
273
			 * Allows us to calculate the progress of specific actions.
274
			 *
275
			 * @since 4.2.0
276
			 *
277
			 * @param array $processed_actions The actions that we send successfully.
278
			 */
279
			do_action( 'jetpack_sync_processed_actions', $processed_items );
280
			$queue->close( $buffer, $processed_item_ids );
281
			// returning a WP_Error is a sign to the caller that we should wait a while
282
			// before syncing again
283
			if ( $had_wp_error ) {
284
				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...
285
			}
286
		}
287
		return true;
288
	}
289
290
	function get_sync_queue() {
291
		return $this->sync_queue;
292
	}
293
294
	function get_full_sync_queue() {
295
		return $this->full_sync_queue;
296
	}
297
298
	function get_codec() {
299
		return $this->codec;
300
	}
301
	function set_codec() {
302
		if ( function_exists( 'gzinflate' ) ) {
303
			$this->codec = new JSON_Deflate_Array_Codec();
304
		} else {
305
			$this->codec = new Simple_Codec();
306
		}
307
	}
308
309
	function send_checksum() {
310
		$store = new Replicastore();
311
		do_action( 'jetpack_sync_checksum', $store->checksum_all() );
312
	}
313
314
	function reset_sync_queue() {
315
		$this->sync_queue->reset();
316
	}
317
318
	function reset_full_sync_queue() {
319
		$this->full_sync_queue->reset();
320
	}
321
322
	function set_dequeue_max_bytes( $size ) {
323
		$this->dequeue_max_bytes = $size;
324
	}
325
326
	// in bytes
327
	function set_upload_max_bytes( $max_bytes ) {
328
		$this->upload_max_bytes = $max_bytes;
329
	}
330
331
	// in rows
332
	function set_upload_max_rows( $max_rows ) {
333
		$this->upload_max_rows = $max_rows;
334
	}
335
336
	// in seconds
337
	function set_sync_wait_time( $seconds ) {
338
		$this->sync_wait_time = $seconds;
339
	}
340
341
	function get_sync_wait_time() {
342
		return $this->sync_wait_time;
343
	}
344
345
	function set_enqueue_wait_time( $seconds ) {
346
		$this->enqueue_wait_time = $seconds;
347
	}
348
349
	function get_enqueue_wait_time() {
350
		return $this->enqueue_wait_time;
351
	}
352
353
	// in seconds
354
	function set_sync_wait_threshold( $seconds ) {
355
		$this->sync_wait_threshold = $seconds;
356
	}
357
358
	function get_sync_wait_threshold() {
359
		return $this->sync_wait_threshold;
360
	}
361
362
	// in seconds
363
	function set_max_dequeue_time( $seconds ) {
364
		$this->max_dequeue_time = $seconds;
365
	}
366
367
368
369
	function set_defaults() {
370
		$this->sync_queue      = new Queue( 'sync' );
371
		$this->full_sync_queue = new Queue( 'full_sync' );
372
		$this->set_codec();
373
374
		// saved settings
375
		Settings::set_importing( null );
376
		$settings = Settings::get_settings();
377
		$this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] );
378
		$this->set_upload_max_bytes( $settings['upload_max_bytes'] );
379
		$this->set_upload_max_rows( $settings['upload_max_rows'] );
380
		$this->set_sync_wait_time( $settings['sync_wait_time'] );
381
		$this->set_enqueue_wait_time( $settings['enqueue_wait_time'] );
382
		$this->set_sync_wait_threshold( $settings['sync_wait_threshold'] );
383
		$this->set_max_dequeue_time( Defaults::get_max_sync_execution_time() );
384
	}
385
386
	function reset_data() {
387
		$this->reset_sync_queue();
388
		$this->reset_full_sync_queue();
389
390
		foreach ( Modules::get_modules() as $module ) {
391
			$module->reset_data();
392
		}
393
394
		foreach ( array( 'sync', 'full_sync', 'full-sync-enqueue' ) as $queue_name ) {
395
			delete_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name );
396
		}
397
398
		Settings::reset_data();
399
	}
400
401
	function uninstall() {
402
		// Lets delete all the other fun stuff like transient and option and the sync queue
403
		$this->reset_data();
404
405
		// delete the full sync status
406
		delete_option( 'jetpack_full_sync_status' );
407
408
		// clear the sync cron.
409
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
410
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
411
	}
412
}
413