Completed
Push — try/alternative-fullsync-enque... ( 2f2a9e...1dc00c )
by
unknown
146:49 queued 136:46
created

Jetpack_Sync_Sender::do_sync_for_queue()   F

Complexity

Conditions 21
Paths 245

Size

Total Lines 172
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 2 Features 2
Metric Value
cc 21
eloc 72
c 8
b 2
f 2
nc 245
nop 1
dl 0
loc 172
rs 3.6963

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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-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 SYNC_THROTTLE_OPTION_NAME = 'jetpack_sync_min_wait';
15
	const NEXT_SYNC_TIME_OPTION_NAME = 'jetpack_next_sync_time';
16
	const WPCOM_ERROR_SYNC_DELAY = 60;
17
18
	private $dequeue_max_bytes;
19
	private $upload_max_bytes;
20
	private $upload_max_rows;
21
	private $sync_wait_time;
22
	private $sync_wait_threshold;
23
	private $sync_queue;
24
	private $full_sync_queue;
25
	private $codec;
26
27
	// singleton functions
28
	private static $instance;
29
30
	public static function get_instance() {
31
		if ( null === self::$instance ) {
32
			self::$instance = new self();
33
		}
34
35
		return self::$instance;
36
	}
37
38
	// this is necessary because you can't use "new" when you declare instance properties >:(
39
	protected function __construct() {
40
		$this->set_defaults();
41
		$this->init();
42
	}
43
44
	private function init() {
45
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
46
			$module->init_before_send();
47
		}
48
	}
49
50
	public function get_next_sync_time() {
51
		return (double) get_option( self::NEXT_SYNC_TIME_OPTION_NAME, 0 );
52
	}
53
54
	public function set_next_sync_time( $time ) {
55
		return update_option( self::NEXT_SYNC_TIME_OPTION_NAME, $time, true );
56
	}
57
58
	public function do_sync() {
59
		// don't sync if importing
60
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
61
			return false;
62
		}
63
64
		// don't sync if we are throttled
65
		if ( $this->get_next_sync_time() > microtime( true ) ) {
66
			return false;
67
		}
68
69
		$start_time = microtime( true );
70
		
71
		$full_sync_result = $this->do_sync_for_queue( $this->full_sync_queue );
72
		$sync_result      = $this->do_sync_for_queue( $this->sync_queue );
73
74
		$exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (double) $this->get_sync_wait_threshold();
75
76
		if ( is_wp_error( $full_sync_result ) || is_wp_error( $sync_result ) ) {
77
			$this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY );
78
			$full_sync_result = false;
79
			$sync_result      = false;
80
		} elseif ( $exceeded_sync_wait_threshold ) {
81
			// if we actually sent data and it took a while, wait before sending again
82
			$this->set_next_sync_time( time() + $this->get_sync_wait_time() );
83
		}
84
85
		// we use OR here because if either one returns true then the caller should
86
		// be allowed to call do_sync again, as there may be more items
87
		return $full_sync_result || $sync_result;
88
	}
89
90
	public function do_sync_for_queue( $queue ) {
91
92
		do_action( 'jetpack_sync_before_send_queue_' . $queue->id );
93
94
		if ( $queue->size() === 0 ) {
95
			return false;
96
		}
97
98
		// now that we're sure we are about to sync, try to
99
		// ignore user abort so we can avoid getting into a
100
		// bad state
101
		if ( function_exists( 'ignore_user_abort' ) ) {
102
			ignore_user_abort( true );
103
		}
104
105
		$buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows );
106
107
		if ( ! $buffer ) {
108
			// buffer has no items
109
			return false;
110
		}
111
112
		if ( is_wp_error( $buffer ) ) {
113
			// another buffer is currently sending
114
			return false;
115
		}
116
117
		$upload_size   = 0;
118
		$items_to_send = array();
119
		$items         = $buffer->get_items();
120
121
		// set up current screen to avoid errors rendering content
122
		require_once(ABSPATH . 'wp-admin/includes/class-wp-screen.php');
123
		require_once(ABSPATH . 'wp-admin/includes/screen.php');
124
		set_current_screen( 'sync' );
125
126
		$skipped_items_ids = array();
127
128
		// we estimate the total encoded size as we go by encoding each item individually
129
		// this is expensive, but the only way to really know :/
130
		foreach ( $items as $key => $item ) {
131
			// Suspending cache addition help prevent overloading in memory cache of large sites.
132
			wp_suspend_cache_addition( true );
133
			/**
134
			 * Modify the data within an action before it is serialized and sent to the server
135
			 * For example, during full sync this expands Post ID's into full Post objects,
136
			 * so that we don't have to serialize the whole object into the queue.
137
			 *
138
			 * @since 4.2.0
139
			 *
140
			 * @param array The action parameters
141
			 * @param int The ID of the user who triggered the action
142
			 */
143
			$item_value = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] );
144
			wp_suspend_cache_addition( false );
145
			if ( $item_value === false ) {
146
				$skipped_items_ids[] = $key;
147
				continue;
148
			}
149
			if ( $item_value instanceof Traversable ) {
150
				foreach ( $item_value as $value ) {
151
					$new_item[0] = $item[0];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$new_item was never initialized. Although not strictly required by PHP, it is generally a good practice to add $new_item = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
152
153
					$new_item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], array( $value ), $item[2] );
0 ignored issues
show
Bug introduced by
The variable $new_item 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...
154
					$new_item[2] = $item[2];
155
					$new_item[3] = $item[3];
156
					$new_item[4] = $item[4];
157
158
					$encoded_item = $this->codec->encode( $new_item );
159
160
					$upload_size += strlen( $encoded_item );
161
162
					if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
163
						break 2;
164
					}
165
166
					$last_item = end( $value );
167
					$items_to_send[ $key . '-from-' . $last_item ] = $encoded_item;
168
				}
169
			} else {
170
171
				$item[1] = $item_value;
172
173
				$encoded_item = $this->codec->encode( $item );
174
175
				$upload_size += strlen( $encoded_item );
176
177
				if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
178
					break;
179
				}
180
181
				$items_to_send[ $key ] = $encoded_item;
182
			}
183
		}
184
185
		/**
186
		 * Fires when data is ready to send to the server.
187
		 * Return false or WP_Error to abort the sync (e.g. if there's an error)
188
		 * The items will be automatically re-sent later
189
		 *
190
		 * @since 4.2.0
191
		 *
192
		 * @param array $data The action buffer
193
		 * @param string $codec The codec name used to encode the data
194
		 * @param double $time The current time
195
		 * @param string $queue The queue used to send ('sync' or 'full_sync')
196
		 */
197
		$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id );
198
199
		if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) {
200
			$checked_in_item_ids = $queue->checkin( $buffer );
201
202
			if ( is_wp_error( $checked_in_item_ids ) ) {
203
				error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() );
204
				$queue->force_checkin();
205
			}
206
207
			if ( is_wp_error( $processed_item_ids ) ) {
208
				return $processed_item_ids;
209
			}
210
211
			// returning a WP_Error is a sign to the caller that we should wait a while
212
			// before syncing again
213
			return new WP_Error( 'server_error' );
214
			
215
		} else {
216
217
			// detect if the last item ID was an error
218
			$had_wp_error = is_wp_error( end( $processed_item_ids ) );
219
220
			if ( $had_wp_error ) {
221
				$wp_error = array_pop( $processed_item_ids );
222
			}
223
224
			$items_to_update = array();
225
			$last_processed_item_id = end( $processed_item_ids );
226
227
			if ( preg_match( '/^jpsq_full_sync-(\d+\.\d+)-(\d+)-(\d+)-(\d+)$/', $last_processed_item_id, $matches ) ) {
228
				// detect if it was an item that we expanded during sending
229
				$parent_item_key = chop( $last_processed_item_id, '-' . $matches[4] );
230
				$items_to_update[ $parent_item_key ] = $items[ $parent_item_key ];
231
				$items_to_update[ $parent_item_key ][1] = $matches[4];
232
			}
233
234
			// also checkin any items that were skipped
235
			if ( count( $skipped_items_ids ) > 0 ) {
236
				$processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
237
			}
238
239
			$processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );
240
241
			/**
242
			 * Allows us to keep track of all the actions that have been sent.
243
			 * Allows us to calculate the progress of specific actions.
244
			 *
245
			 * @since 4.2.0
246
			 *
247
			 * @param array $processed_actions The actions that we send successfully.
248
			 */
249
			do_action( 'jetpack_sync_processed_actions', $processed_items );
250
251
			$queue->close( $buffer, $processed_item_ids, $items_to_update );
252
253
			// returning a WP_Error is a sign to the caller that we should wait a while
254
			// before syncing again
255
			if ( $had_wp_error ) {
256
				return $wp_error;
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...
257
			} 
258
		}
259
		
260
		return true;
261
	}
262
263
	function get_sync_queue() {
264
		return $this->sync_queue;
265
	}
266
267
	function get_full_sync_queue() {
268
		return $this->full_sync_queue;
269
	}
270
271
	function get_codec() {
272
		return $this->codec;
273
	}
274
275
	function send_checksum() {
276
		require_once 'class.jetpack-sync-wp-replicastore.php';
277
		$store = new Jetpack_Sync_WP_Replicastore();
278
		do_action( 'jetpack_sync_checksum', $store->checksum_all() );
279
	}
280
281
	function reset_sync_queue() {
282
		$this->sync_queue->reset();
283
	}
284
285
	function set_dequeue_max_bytes( $size ) {
286
		$this->dequeue_max_bytes = $size;
287
	}
288
289
	// in bytes
290
	function set_upload_max_bytes( $max_bytes ) {
291
		$this->upload_max_bytes = $max_bytes;
292
	}
293
294
	// in rows
295
	function set_upload_max_rows( $max_rows ) {
296
		$this->upload_max_rows = $max_rows;
297
	}
298
299
	// in seconds
300
	function set_sync_wait_time( $seconds ) {
301
		$this->sync_wait_time = $seconds;
302
	}
303
304
	function get_sync_wait_time() {
305
		return $this->sync_wait_time;
306
	}
307
308
	// in seconds
309
	function set_sync_wait_threshold( $seconds ) {
310
		$this->sync_wait_threshold = $seconds;
311
	}
312
313
	function get_sync_wait_threshold() {
314
		return $this->sync_wait_threshold;
315
	}
316
317
	function set_defaults() {
318
		$this->sync_queue = new Jetpack_Sync_Queue( 'sync' );
319
		$this->full_sync_queue = new Jetpack_Sync_Queue( 'full_sync' );
320
		$this->codec      = new Jetpack_Sync_JSON_Deflate_Codec();
321
322
		// saved settings
323
		Jetpack_Sync_Settings::set_importing( null );
324
		$settings = Jetpack_Sync_Settings::get_settings();
325
		$this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] );
326
		$this->set_upload_max_bytes( $settings['upload_max_bytes'] );
327
		$this->set_upload_max_rows( $settings['upload_max_rows'] );
328
		$this->set_sync_wait_time( $settings['sync_wait_time'] );
329
		$this->set_sync_wait_threshold( $settings['sync_wait_threshold'] );
330
	}
331
332
	function reset_data() {
333
		$this->reset_sync_queue();
334
335
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
336
			$module->reset_data();
337
		}
338
339
		delete_option( self::SYNC_THROTTLE_OPTION_NAME );
340
		delete_option( self::NEXT_SYNC_TIME_OPTION_NAME );
341
342
		Jetpack_Sync_Settings::reset_data();
343
	}
344
345
	function uninstall() {
346
		// Lets delete all the other fun stuff like transient and option and the sync queue
347
		$this->reset_data();
348
349
		// delete the full sync status
350
		delete_option( 'jetpack_full_sync_status' );
351
352
		// clear the sync cron.
353
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
354
	}
355
}
356