Completed
Push — dna-stats-module ( 8da522...2880ce )
by
unknown
382:09 queued 374:09
created

Jetpack_Sync_Sender::set_defaults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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