Completed
Push — add/oauth-connection ( 084714...305e42 )
by
unknown
14:09 queued 06:55
created

Queue::lock()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 8
nop 1
dl 0
loc 25
rs 8.5866
c 0
b 0
f 0
1
<?php
2
/**
3
 * The class that describes the Queue for the sync package.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync;
9
10
/**
11
 * A persistent queue that can be flushed in increments of N items,
12
 * and which blocks reads until checked-out buffers are checked in or
13
 * closed. This uses raw SQL for two reasons: speed, and not triggering
14
 * tons of added_option callbacks.
15
 */
16
class Queue {
17
	/**
18
	 * The queue id.
19
	 *
20
	 * @var string
21
	 */
22
	public $id;
23
	/**
24
	 * Keeps track of the rows.
25
	 *
26
	 * @var int
27
	 */
28
	private $row_iterator;
29
30
	/**
31
	 * Queue constructor.
32
	 *
33
	 * @param string $id Name of the queue.
34
	 */
35
	public function __construct( $id ) {
36
		$this->id           = str_replace( '-', '_', $id ); // Necessary to ensure we don't have ID collisions in the SQL.
37
		$this->row_iterator = 0;
38
		$this->random_int   = wp_rand( 1, 1000000 );
0 ignored issues
show
Bug introduced by
The property random_int does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
39
	}
40
41
	/**
42
	 * Add a single item to the queue.
43
	 *
44
	 * @param object $item Event object to add to queue.
45
	 */
46
	public function add( $item ) {
47
		global $wpdb;
48
		$added = false;
49
		// This basically tries to add the option until enough time has elapsed that
50
		// it has a unique (microtime-based) option key.
51
		while ( ! $added ) {
52
			$rows_added = $wpdb->query(
53
				$wpdb->prepare(
54
					"INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES (%s, %s,%s)",
55
					$this->get_next_data_row_option_name(),
56
					serialize( $item ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
57
					'no'
58
				)
59
			);
60
			$added      = ( 0 !== $rows_added );
61
		}
62
	}
63
64
	/**
65
	 * Insert all the items in a single SQL query. May be subject to query size limits!
66
	 *
67
	 * @param array $items Array of events to add to the queue.
68
	 *
69
	 * @return bool|\WP_Error
70
	 */
71
	public function add_all( $items ) {
72
		global $wpdb;
73
		$base_option_name = $this->get_next_data_row_option_name();
74
75
		$query = "INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES ";
76
77
		$rows        = array();
78
		$count_items = count( $items );
79
		for ( $i = 0; $i < $count_items; ++$i ) {
80
			$option_name  = esc_sql( $base_option_name . '-' . $i );
81
			$option_value = esc_sql( serialize( $items[ $i ] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
82
			$rows[]       = "('$option_name', '$option_value', 'no')";
83
		}
84
85
		$rows_added = $wpdb->query( $query . join( ',', $rows ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
86
87
		if ( count( $items ) === $rows_added ) {
88
			return new \WP_Error( 'row_count_mismatch', "The number of rows inserted didn't match the size of the input array" );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'row_count_mismatch'.

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...
89
		}
90
		return true;
91
	}
92
93
	/**
94
	 * Get the front-most item on the queue without checking it out.
95
	 *
96
	 * @param int $count Number of items to return when looking at the items.
97
	 *
98
	 * @return array
99
	 */
100
	public function peek( $count = 1 ) {
101
		$items = $this->fetch_items( $count );
102
		if ( $items ) {
103
			return Utils::get_item_values( $items );
0 ignored issues
show
Bug introduced by
It seems like $items defined by $this->fetch_items($count) on line 101 can also be of type object; however, Automattic\Jetpack\Sync\Utils::get_item_values() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
104
		}
105
106
		return array();
107
	}
108
109
	/**
110
	 * Gets items with particular IDs.
111
	 *
112
	 * @param array $item_ids Array of item IDs to retrieve.
113
	 *
114
	 * @return array
115
	 */
116
	public function peek_by_id( $item_ids ) {
117
		$items = $this->fetch_items_by_id( $item_ids );
118
		if ( $items ) {
119
			return Utils::get_item_values( $items );
0 ignored issues
show
Bug introduced by
It seems like $items defined by $this->fetch_items_by_id($item_ids) on line 117 can also be of type object; however, Automattic\Jetpack\Sync\Utils::get_item_values() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
120
		}
121
122
		return array();
123
	}
124
125
	/**
126
	 * Gets the queue lag.
127
	 * Lag is the difference in time between the age of the oldest item
128
	 * (aka first or frontmost item) and the current time.
129
	 *
130
	 * @param microtime $now The current time in microtime.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $now not be microtime|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...
131
	 *
132
	 * @return float|int|mixed|null
133
	 */
134
	public function lag( $now = null ) {
135
		global $wpdb;
136
137
		$first_item_name = $wpdb->get_var(
138
			$wpdb->prepare(
139
				"SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 1",
140
				"jpsq_{$this->id}-%"
141
			)
142
		);
143
144
		if ( ! $first_item_name ) {
145
			return 0;
146
		}
147
148
		if ( null === $now ) {
149
			$now = microtime( true );
150
		}
151
152
		// Break apart the item name to get the timestamp.
153
		$matches = null;
154
		if ( preg_match( '/^jpsq_' . $this->id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) {
155
			return $now - floatval( $matches[1] );
156
		} else {
157
			return 0;
158
		}
159
	}
160
161
	/**
162
	 * Resets the queue.
163
	 */
164
	public function reset() {
165
		global $wpdb;
166
		$this->delete_checkout_id();
167
		$wpdb->query(
168
			$wpdb->prepare(
169
				"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
170
				"jpsq_{$this->id}-%"
171
			)
172
		);
173
	}
174
175
	/**
176
	 * Return the size of the queue.
177
	 *
178
	 * @return int
179
	 */
180
	public function size() {
181
		global $wpdb;
182
183
		return (int) $wpdb->get_var(
184
			$wpdb->prepare(
185
				"SELECT count(*) FROM $wpdb->options WHERE option_name LIKE %s",
186
				"jpsq_{$this->id}-%"
187
			)
188
		);
189
	}
190
191
	/**
192
	 * Lets you know if there is any items in the queue.
193
	 *
194
	 * We use this peculiar implementation because it's much faster than count(*).
195
	 *
196
	 * @return bool
197
	 */
198
	public function has_any_items() {
199
		global $wpdb;
200
		$value = $wpdb->get_var(
201
			$wpdb->prepare(
202
				"SELECT exists( SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s )",
203
				"jpsq_{$this->id}-%"
204
			)
205
		);
206
207
		return ( '1' === $value );
208
	}
209
210
	/**
211
	 * Used to checkout the queue.
212
	 *
213
	 * @param int $buffer_size Size of the buffer to checkout.
214
	 *
215
	 * @return Automattic\Jetpack\Sync\Queue_Buffer|bool|int|\WP_Error
216
	 */
217
	public function checkout( $buffer_size ) {
218
		if ( $this->get_checkout_id() ) {
219
			return new \WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unclosed_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...
220
		}
221
222
		$buffer_id = uniqid();
223
224
		$result = $this->set_checkout_id( $buffer_id );
225
226
		if ( ! $result || is_wp_error( $result ) ) {
227
			return $result;
228
		}
229
230
		$items = $this->fetch_items( $buffer_size );
231
232
		if ( count( $items ) === 0 ) {
233
			return false;
234
		}
235
236
		$buffer = new Queue_Buffer( $buffer_id, array_slice( $items, 0, $buffer_size ) );
237
238
		return $buffer;
239
	}
240
241
	/**
242
	 * Given a list of items return the items ids.
243
	 *
244
	 * @param array $items List of item objects.
245
	 *
246
	 * @return array Ids of the items.
247
	 */
248
	public function get_ids( $items ) {
249
		return array_map(
250
			function( $item ) {
251
				return $item->id;
252
			},
253
			$items
254
		);
255
	}
256
257
	/**
258
	 * Pop elements from the queue.
259
	 *
260
	 * @param int $limit Number of items to pop from the queue.
261
	 *
262
	 * @return array|object|null
263
	 */
264
	public function pop( $limit ) {
265
		$items = $this->fetch_items( $limit );
266
267
		$ids = $this->get_ids( $items );
0 ignored issues
show
Bug introduced by
It seems like $items defined by $this->fetch_items($limit) on line 265 can also be of type null or object; however, Automattic\Jetpack\Sync\Queue::get_ids() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
268
269
		$this->delete( $ids );
270
271
		return $items;
272
	}
273
274
	/**
275
	 * Get the items from the queue with a memory limit.
276
	 *
277
	 * This checks out rows until it either empties the queue or hits a certain memory limit
278
	 * it loads the sizes from the DB first so that it doesn't accidentally
279
	 * load more data into memory than it needs to.
280
	 * The only way it will load more items than $max_size is if a single queue item
281
	 * exceeds the memory limit, but in that case it will send that item by itself.
282
	 *
283
	 * @param int $max_memory (bytes) Maximum memory threshold.
284
	 * @param int $max_buffer_size Maximum buffer size (number of items).
285
	 *
286
	 * @return Automattic\Jetpack\Sync\Queue_Buffer|bool|int|\WP_Error
287
	 */
288
	public function checkout_with_memory_limit( $max_memory, $max_buffer_size = 500 ) {
289
		if ( $this->get_checkout_id() ) {
290
			return new \WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unclosed_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...
291
		}
292
293
		$buffer_id = uniqid();
294
295
		$result = $this->set_checkout_id( $buffer_id );
296
297
		if ( ! $result || is_wp_error( $result ) ) {
298
			return $result;
299
		}
300
301
		// Get the map of buffer_id -> memory_size.
302
		global $wpdb;
303
304
		$items_with_size = $wpdb->get_results(
305
			$wpdb->prepare(
306
				"SELECT option_name AS id, LENGTH(option_value) AS value_size FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
307
				"jpsq_{$this->id}-%",
308
				$max_buffer_size
309
			),
310
			OBJECT
311
		);
312
313
		if ( count( $items_with_size ) === 0 ) {
314
			return false;
315
		}
316
317
		$total_memory = 0;
318
		$max_item_id  = $items_with_size[0]->id;
319
		$min_item_id  = $max_item_id;
320
321
		foreach ( $items_with_size as $id => $item_with_size ) {
322
			$total_memory += $item_with_size->value_size;
323
324
			// If this is the first item and it exceeds memory, allow loop to continue
325
			// we will exit on the next iteration instead.
326
			if ( $total_memory > $max_memory && $id > 0 ) {
327
				break;
328
			}
329
330
			$max_item_id = $item_with_size->id;
331
		}
332
333
		$query = $wpdb->prepare(
334
			"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name >= %s and option_name <= %s ORDER BY option_name ASC",
335
			$min_item_id,
336
			$max_item_id
337
		);
338
339
		$items = $wpdb->get_results( $query, OBJECT ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
340
		foreach ( $items as $item ) {
341
			$item->value = maybe_unserialize( $item->value );
342
		}
343
344
		if ( count( $items ) === 0 ) {
345
			$this->delete_checkout_id();
346
347
			return false;
348
		}
349
350
		$buffer = new Queue_Buffer( $buffer_id, $items );
351
352
		return $buffer;
353
	}
354
355
	/**
356
	 * Check in the queue.
357
	 *
358
	 * @param Automattic\Jetpack\Sync\Queue_Buffer $buffer Queue_Buffer object.
359
	 *
360
	 * @return bool|\WP_Error
361
	 */
362
	public function checkin( $buffer ) {
363
		$is_valid = $this->validate_checkout( $buffer );
364
365
		if ( is_wp_error( $is_valid ) ) {
366
			return $is_valid;
367
		}
368
369
		$this->delete_checkout_id();
370
371
		return true;
372
	}
373
374
	/**
375
	 * Close the buffer.
376
	 *
377
	 * @param Automattic\Jetpack\Sync\Queue_Buffer $buffer Queue_Buffer object.
378
	 * @param null|array                           $ids_to_remove Ids to remove from the queue.
379
	 *
380
	 * @return bool|\WP_Error
381
	 */
382
	public function close( $buffer, $ids_to_remove = null ) {
383
		$is_valid = $this->validate_checkout( $buffer );
384
385
		if ( is_wp_error( $is_valid ) ) {
386
			return $is_valid;
387
		}
388
389
		$this->delete_checkout_id();
390
391
		// By default clear all items in the buffer.
392
		if ( is_null( $ids_to_remove ) ) {
393
			$ids_to_remove = $buffer->get_item_ids();
394
		}
395
396
		$this->delete( $ids_to_remove );
397
398
		return true;
399
	}
400
401
	/**
402
	 * Delete elements from the queue.
403
	 *
404
	 * @param array $ids Ids to delete.
405
	 *
406
	 * @return bool|int
407
	 */
408
	private function delete( $ids ) {
409
		if ( 0 === count( $ids ) ) {
410
			return 0;
411
		}
412
		global $wpdb;
413
		$sql   = "DELETE FROM $wpdb->options WHERE option_name IN (" . implode( ', ', array_fill( 0, count( $ids ), '%s' ) ) . ')';
414
		$query = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $ids ) );
415
416
		return $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
417
	}
418
419
	/**
420
	 * Flushes all items from the queue.
421
	 *
422
	 * @return array
423
	 */
424
	public function flush_all() {
425
		$items = Utils::get_item_values( $this->fetch_items() );
0 ignored issues
show
Bug introduced by
It seems like $this->fetch_items() targeting Automattic\Jetpack\Sync\Queue::fetch_items() can also be of type null or object; however, Automattic\Jetpack\Sync\Utils::get_item_values() does only seem to accept array, maybe add an additional type check?

This check looks at variables that 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...
426
		$this->reset();
427
428
		return $items;
429
	}
430
431
	/**
432
	 * Get all the items from the queue.
433
	 *
434
	 * @return array|object|null
435
	 */
436
	public function get_all() {
437
		return $this->fetch_items();
438
	}
439
440
	/**
441
	 * Forces Checkin of the queue.
442
	 * Use with caution, this could allow multiple processes to delete
443
	 * and send from the queue at the same time
444
	 */
445
	public function force_checkin() {
446
		$this->delete_checkout_id();
447
	}
448
449
	/**
450
	 * Locks checkouts from the queue
451
	 * tries to wait up to $timeout seconds for the queue to be empty.
452
	 *
453
	 * @param int $timeout The wait time in seconds for the queue to be empty.
454
	 *
455
	 * @return bool|int|\WP_Error
456
	 */
457
	public function lock( $timeout = 30 ) {
458
		$tries = 0;
459
460
		while ( $this->has_any_items() && $tries < $timeout ) {
461
			sleep( 1 );
462
			++$tries;
463
		}
464
465
		if ( 30 === $tries ) {
466
			return new \WP_Error( 'lock_timeout', 'Timeout waiting for sync queue to empty' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'lock_timeout'.

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...
467
		}
468
469
		if ( $this->get_checkout_id() ) {
470
			return new \WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unclosed_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...
471
		}
472
473
		// Hopefully this means we can acquire a checkout?
474
		$result = $this->set_checkout_id( 'lock' );
475
476
		if ( ! $result || is_wp_error( $result ) ) {
477
			return $result;
478
		}
479
480
		return true;
481
	}
482
483
	/**
484
	 * Unlocks the queue.
485
	 *
486
	 * @return bool|int
487
	 */
488
	public function unlock() {
489
		return $this->delete_checkout_id();
490
	}
491
492
	/**
493
	 * This option is specifically chosen to, as much as possible, preserve time order
494
	 * and minimise the possibility of collisions between multiple processes working
495
	 * at the same time.
496
	 *
497
	 * @return string
498
	 */
499
	protected function generate_option_name_timestamp() {
500
		return sprintf( '%.6f', microtime( true ) );
501
	}
502
503
	/**
504
	 * Gets the checkout ID.
505
	 *
506
	 * @return bool|string
507
	 */
508
	private function get_checkout_id() {
509
		global $wpdb;
510
		$checkout_value = $wpdb->get_var(
511
			$wpdb->prepare(
512
				"SELECT option_value FROM $wpdb->options WHERE option_name = %s",
513
				$this->get_lock_option_name()
514
			)
515
		);
516
517
		if ( $checkout_value ) {
518
			list( $checkout_id, $timestamp ) = explode( ':', $checkout_value );
519
			if ( intval( $timestamp ) > time() ) {
520
				return $checkout_id;
521
			}
522
		}
523
524
		return false;
525
	}
526
527
	/**
528
	 * Sets the checkout id.
529
	 *
530
	 * @param string $checkout_id The ID of the checkout.
531
	 *
532
	 * @return bool|int
533
	 */
534
	private function set_checkout_id( $checkout_id ) {
535
		global $wpdb;
536
537
		$expires     = time() + Defaults::$default_sync_queue_lock_timeout;
538
		$updated_num = $wpdb->query(
539
			$wpdb->prepare(
540
				"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
541
				"$checkout_id:$expires",
542
				$this->get_lock_option_name()
543
			)
544
		);
545
546
		if ( ! $updated_num ) {
547
			$updated_num = $wpdb->query(
548
				$wpdb->prepare(
549
					"INSERT INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, 'no' )",
550
					$this->get_lock_option_name(),
551
					"$checkout_id:$expires"
552
				)
553
			);
554
		}
555
556
		return $updated_num;
557
	}
558
559
	/**
560
	 * Deletes the checkout ID.
561
	 *
562
	 * @return bool|int
563
	 */
564
	private function delete_checkout_id() {
565
		global $wpdb;
566
		// Rather than delete, which causes fragmentation, we update in place.
567
		return $wpdb->query(
568
			$wpdb->prepare(
569
				"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
570
				'0:0',
571
				$this->get_lock_option_name()
572
			)
573
		);
574
575
	}
576
577
	/**
578
	 * Return the lock option name.
579
	 *
580
	 * @return string
581
	 */
582
	private function get_lock_option_name() {
583
		return "jpsq_{$this->id}_checkout";
584
	}
585
586
	/**
587
	 * Return the next data row option name.
588
	 *
589
	 * @return string
590
	 */
591
	private function get_next_data_row_option_name() {
592
		$timestamp = $this->generate_option_name_timestamp();
593
594
		// Row iterator is used to avoid collisions where we're writing data waaay fast in a single process.
595
		if ( PHP_INT_MAX === $this->row_iterator ) {
596
			$this->row_iterator = 0;
597
		} else {
598
			$this->row_iterator += 1;
599
		}
600
601
		return 'jpsq_' . $this->id . '-' . $timestamp . '-' . $this->random_int . '-' . $this->row_iterator;
602
	}
603
604
	/**
605
	 * Return the items in the queue.
606
	 *
607
	 * @param null|int $limit Limit to the number of items we fetch at once.
608
	 *
609
	 * @return array|object|null
610
	 */
611
	private function fetch_items( $limit = null ) {
612
		global $wpdb;
613
614
		if ( $limit ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
615
			$items = $wpdb->get_results(
616
				$wpdb->prepare(
617
					"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
618
					"jpsq_{$this->id}-%",
619
					$limit
620
				),
621
				OBJECT
622
			);
623
		} else {
624
			$items = $wpdb->get_results(
625
				$wpdb->prepare(
626
					"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC",
627
					"jpsq_{$this->id}-%"
628
				),
629
				OBJECT
630
			);
631
		}
632
633
		return $this->unserialize_values( $items );
634
635
	}
636
637
	/**
638
	 * Return items with specific ids.
639
	 *
640
	 * @param array $items_ids Array of event ids.
641
	 *
642
	 * @return array|object|null
643
	 */
644
	private function fetch_items_by_id( $items_ids ) {
645
		global $wpdb;
646
647
		$ids_placeholders        = implode( ', ', array_fill( 0, count( $items_ids ), '%s' ) );
648
		$query_with_placeholders = "SELECT option_name AS id, option_value AS value
649
				FROM $wpdb->options
650
				WHERE option_name IN ( $ids_placeholders )";
651
		$items                   = $wpdb->get_results(
652
			$wpdb->prepare(
653
				$query_with_placeholders, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
654
				$items_ids
655
			),
656
			OBJECT
657
		);
658
659
		return $this->unserialize_values( $items );
660
	}
661
662
	/**
663
	 * Unserialize item values.
664
	 *
665
	 * @param array $items Events from the Queue to be serialized.
666
	 *
667
	 * @return mixed
668
	 */
669
	private function unserialize_values( $items ) {
670
		array_walk(
671
			$items,
672
			function( $item ) {
673
				$item->value = maybe_unserialize( $item->value );
674
			}
675
		);
676
677
		return $items;
678
679
	}
680
681
	/**
682
	 * Return true if the buffer is still valid or an Error other wise.
683
	 *
684
	 * @param Automattic\Jetpack\Sync\Queue_Buffer $buffer The Queue_Buffer.
685
	 *
686
	 * @return bool|\WP_Error
687
	 */
688
	private function validate_checkout( $buffer ) {
689
		if ( ! $buffer instanceof Queue_Buffer ) {
690
			return new \WP_Error( 'not_a_buffer', 'You must checkin an instance of Automattic\\Jetpack\\Sync\\Queue_Buffer' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'not_a_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...
691
		}
692
693
		$checkout_id = $this->get_checkout_id();
694
695
		if ( ! $checkout_id ) {
696
			return new \WP_Error( 'buffer_not_checked_out', 'There are no checked out buffers' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'buffer_not_checked_out'.

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...
697
		}
698
699
		// TODO: change to strict comparison.
700
		if ( $checkout_id != $buffer->id ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
701
			return new \WP_Error( 'buffer_mismatch', 'The buffer you checked in was not checked out' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'buffer_mismatch'.

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...
702
		}
703
704
		return true;
705
	}
706
}
707