Completed
Push — add/jetpack-start-2 ( 57edf5...1413b3 )
by
unknown
67:00 queued 58:47
created

sync/class.jetpack-sync-sender.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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