Completed
Push — renovate/jest-monorepo ( bd2eaf...d289c3 )
by
unknown
44:07 queued 37:28
created

Replicastore::checksum_histogram()   F

Complexity

Conditions 18
Paths 282

Size

Total Lines 104

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 282
nop 7
dl 0
loc 104
rs 2.4066
c 0
b 0
f 0

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
 * Sync replicastore.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync;
9
10
/**
11
 * An implementation of Replicastore Interface which returns data stored in a WordPress.org DB.
12
 * This is useful to compare values in the local WP DB to values in the synced replica store
13
 */
14
class Replicastore implements Replicastore_Interface {
15
	/**
16
	 * Empty and reset the replicastore.
17
	 *
18
	 * @access public
19
	 */
20
	public function reset() {
21
		global $wpdb;
22
23
		$wpdb->query( "DELETE FROM $wpdb->posts" );
24
		$wpdb->query( "DELETE FROM $wpdb->comments" );
25
26
		// Also need to delete terms from cache.
27
		$term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" );
28
		foreach ( $term_ids as $term_id ) {
29
			wp_cache_delete( $term_id, 'terms' );
30
		}
31
32
		$wpdb->query( "DELETE FROM $wpdb->terms" );
33
34
		$wpdb->query( "DELETE FROM $wpdb->term_taxonomy" );
35
		$wpdb->query( "DELETE FROM $wpdb->term_relationships" );
36
37
		// Callables and constants.
38
		$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" );
39
		$wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" );
40
	}
41
42
	/**
43
	 * Ran when full sync has just started.
44
	 *
45
	 * @access public
46
	 *
47
	 * @param array $config Full sync configuration for this sync module.
48
	 */
49
	public function full_sync_start( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
50
		$this->reset();
51
	}
52
53
	/**
54
	 * Ran when full sync has just finished.
55
	 *
56
	 * @access public
57
	 *
58
	 * @param string $checksum Deprecated since 7.3.0.
59
	 */
60
	public function full_sync_end( $checksum ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
61
		// Noop right now.
62
	}
63
64
	/**
65
	 * Retrieve the number of terms.
66
	 *
67
	 * @access public
68
	 *
69
	 * @return int Number of terms.
70
	 */
71
	public function term_count() {
72
		global $wpdb;
73
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->terms" );
74
	}
75
76
	/**
77
	 * Retrieve the number of rows in the `term_taxonomy` table.
78
	 *
79
	 * @access public
80
	 *
81
	 * @return int Number of terms.
82
	 */
83
	public function term_taxonomy_count() {
84
		global $wpdb;
85
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_taxonomy" );
86
	}
87
88
	/**
89
	 * Retrieve the number of term relationships.
90
	 *
91
	 * @access public
92
	 *
93
	 * @return int Number of rows in the term relationships table.
94
	 */
95
	public function term_relationship_count() {
96
		global $wpdb;
97
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_relationships" );
98
	}
99
100
	/**
101
	 * Retrieve the number of posts with a particular post status within a certain range.
102
	 *
103
	 * @access public
104
	 *
105
	 * @todo Prepare the SQL query before executing it.
106
	 *
107
	 * @param string $status Post status.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $status not be string|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...
108
	 * @param int    $min_id Minimum post ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
109
	 * @param int    $max_id Maximum post ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
110
	 * @return int Number of posts.
111
	 */
112 View Code Duplication
	public function post_count( $status = null, $min_id = null, $max_id = null ) {
113
		global $wpdb;
114
115
		$where = '';
0 ignored issues
show
Unused Code introduced by
$where is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
116
117
		if ( $status ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $status of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
118
			$where = "post_status = '" . esc_sql( $status ) . "'";
119
		} else {
120
			$where = '1=1';
121
		}
122
123
		if ( ! empty( $min_id ) ) {
124
			$where .= ' AND ID >= ' . intval( $min_id );
125
		}
126
127
		if ( ! empty( $max_id ) ) {
128
			$where .= ' AND ID <= ' . intval( $max_id );
129
		}
130
131
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
132
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
133
	}
134
135
	/**
136
	 * Retrieve the posts with a particular post status.
137
	 *
138
	 * @access public
139
	 *
140
	 * @todo Implement range and actually use max_id/min_id arguments.
141
	 *
142
	 * @param string $status Post status.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $status not be string|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...
143
	 * @param int    $min_id Minimum post ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
144
	 * @param int    $max_id Maximum post ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
145
	 * @return array Array of posts.
146
	 */
147
	public function get_posts( $status = null, $min_id = null, $max_id = null ) {
148
		$args = array(
149
			'orderby'        => 'ID',
150
			'posts_per_page' => -1,
151
		);
152
153
		if ( $status ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $status of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
154
			$args['post_status'] = $status;
155
		} else {
156
			$args['post_status'] = 'any';
157
		}
158
159
		return get_posts( $args );
160
	}
161
162
	/**
163
	 * Retrieve a post object by the post ID.
164
	 *
165
	 * @access public
166
	 *
167
	 * @param int $id Post ID.
168
	 * @return \WP_Post Post object.
169
	 */
170
	public function get_post( $id ) {
171
		return get_post( $id );
172
	}
173
174
	/**
175
	 * Update or insert a post.
176
	 *
177
	 * @access public
178
	 *
179
	 * @param \WP_Post $post   Post object.
180
	 * @param bool     $silent Whether to perform a silent action. Not used in this implementation.
181
	 */
182
	public function upsert_post( $post, $silent = false ) {
183
		global $wpdb;
184
185
		// Reject the post if it's not a \WP_Post.
186
		if ( ! $post instanceof \WP_Post ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
187
			return;
188
		}
189
190
		$post = $post->to_array();
191
192
		// Reject posts without an ID.
193
		if ( ! isset( $post['ID'] ) ) {
194
			return;
195
		}
196
197
		$now     = current_time( 'mysql' );
198
		$now_gmt = get_gmt_from_date( $now );
199
200
		$defaults = array(
201
			'ID'                    => 0,
202
			'post_author'           => '0',
203
			'post_content'          => '',
204
			'post_content_filtered' => '',
205
			'post_title'            => '',
206
			'post_name'             => '',
207
			'post_excerpt'          => '',
208
			'post_status'           => 'draft',
209
			'post_type'             => 'post',
210
			'comment_status'        => 'closed',
211
			'comment_count'         => '0',
212
			'ping_status'           => '',
213
			'post_password'         => '',
214
			'to_ping'               => '',
215
			'pinged'                => '',
216
			'post_parent'           => 0,
217
			'menu_order'            => 0,
218
			'guid'                  => '',
219
			'post_date'             => $now,
220
			'post_date_gmt'         => $now_gmt,
221
			'post_modified'         => $now,
222
			'post_modified_gmt'     => $now_gmt,
223
		);
224
225
		$post = array_intersect_key( $post, $defaults );
226
227
		$post = sanitize_post( $post, 'db' );
228
229
		unset( $post['filter'] );
230
231
		$exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
232
233
		if ( $exists ) {
234
			$wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
235
		} else {
236
			$wpdb->insert( $wpdb->posts, $post );
237
		}
238
239
		clean_post_cache( $post['ID'] );
240
	}
241
242
	/**
243
	 * Delete a post by the post ID.
244
	 *
245
	 * @access public
246
	 *
247
	 * @param int $post_id Post ID.
248
	 */
249
	public function delete_post( $post_id ) {
250
		wp_delete_post( $post_id, true );
251
	}
252
253
	/**
254
	 * Retrieve the checksum for posts within a range.
255
	 *
256
	 * @access public
257
	 *
258
	 * @param int $min_id Minimum post ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
259
	 * @param int $max_id Maximum post ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
260
	 * @return int The checksum.
261
	 */
262
	public function posts_checksum( $min_id = null, $max_id = null ) {
263
		global $wpdb;
264
		return $this->table_checksum( $wpdb->posts, Defaults::$default_post_checksum_columns, 'ID', Settings::get_blacklisted_post_types_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_post_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
265
	}
266
267
	/**
268
	 * Retrieve the checksum for post meta within a range.
269
	 *
270
	 * @access public
271
	 *
272
	 * @param int $min_id Minimum post meta ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
273
	 * @param int $max_id Maximum post meta ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
274
	 * @return int The checksum.
275
	 */
276
	public function post_meta_checksum( $min_id = null, $max_id = null ) {
277
		global $wpdb;
278
		return $this->table_checksum( $wpdb->postmeta, Defaults::$default_post_meta_checksum_columns, 'meta_id', Settings::get_whitelisted_post_meta_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_post_meta_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
279
	}
280
281
	/**
282
	 * Retrieve the number of comments with a particular comment status within a certain range.
283
	 *
284
	 * @access public
285
	 *
286
	 * @todo Prepare the SQL query before executing it.
287
	 *
288
	 * @param string $status Comment status.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $status not be string|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...
289
	 * @param int    $min_id Minimum comment ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
290
	 * @param int    $max_id Maximum comment ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
291
	 * @return int Number of comments.
292
	 */
293 View Code Duplication
	public function comment_count( $status = null, $min_id = null, $max_id = null ) {
294
		global $wpdb;
295
296
		$comment_approved = $this->comment_status_to_approval_value( $status );
297
298
		if ( false !== $comment_approved ) {
299
			$where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
300
		} else {
301
			$where = '1=1';
302
		}
303
304
		if ( ! empty( $min_id ) ) {
305
			$where .= ' AND comment_ID >= ' . intval( $min_id );
306
		}
307
308
		if ( ! empty( $max_id ) ) {
309
			$where .= ' AND comment_ID <= ' . intval( $max_id );
310
		}
311
312
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
313
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
314
	}
315
316
	/**
317
	 * Translate a comment status to a value of the comment_approved field.
318
	 *
319
	 * @access private
320
	 *
321
	 * @param string $status Comment status.
322
	 * @return string|bool New comment_approved value, false if the status doesn't affect it.
323
	 */
324
	private function comment_status_to_approval_value( $status ) {
325
		switch ( $status ) {
326
			case 'approve':
327
				return '1';
328
			case 'hold':
329
				return '0';
330
			case 'spam':
331
				return 'spam';
332
			case 'trash':
333
				return 'trash';
334
			case 'any':
335
				return false;
336
			case 'all':
337
				return false;
338
			default:
339
				return false;
340
		}
341
	}
342
343
	/**
344
	 * Retrieve the comments with a particular comment status.
345
	 *
346
	 * @access public
347
	 *
348
	 * @todo Implement range and actually use max_id/min_id arguments.
349
	 *
350
	 * @param string $status Comment status.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $status not be string|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...
351
	 * @param int    $min_id Minimum comment ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
352
	 * @param int    $max_id Maximum comment ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
353
	 * @return array Array of comments.
354
	 */
355
	public function get_comments( $status = null, $min_id = null, $max_id = null ) {
356
		$args = array(
357
			'orderby' => 'ID',
358
			'status'  => 'all',
359
		);
360
361
		if ( $status ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $status of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
362
			$args['status'] = $status;
363
		}
364
365
		return get_comments( $args );
366
	}
367
368
	/**
369
	 * Retrieve a comment object by the comment ID.
370
	 *
371
	 * @access public
372
	 *
373
	 * @param int $id Comment ID.
374
	 * @return \WP_Comment Comment object.
375
	 */
376
	public function get_comment( $id ) {
377
		return \WP_Comment::get_instance( $id );
378
	}
379
380
	/**
381
	 * Update or insert a comment.
382
	 *
383
	 * @access public
384
	 *
385
	 * @param \WP_Comment $comment Comment object.
386
	 */
387
	public function upsert_comment( $comment ) {
388
		global $wpdb;
389
390
		$comment = $comment->to_array();
391
392
		// Filter by fields on comment table.
393
		$comment_fields_whitelist = array(
394
			'comment_ID',
395
			'comment_post_ID',
396
			'comment_author',
397
			'comment_author_email',
398
			'comment_author_url',
399
			'comment_author_IP',
400
			'comment_date',
401
			'comment_date_gmt',
402
			'comment_content',
403
			'comment_karma',
404
			'comment_approved',
405
			'comment_agent',
406
			'comment_type',
407
			'comment_parent',
408
			'user_id',
409
		);
410
411
		foreach ( $comment as $key => $value ) {
412
			if ( ! in_array( $key, $comment_fields_whitelist, true ) ) {
413
				unset( $comment[ $key ] );
414
			}
415
		}
416
417
		$exists = $wpdb->get_var(
418
			$wpdb->prepare(
419
				"SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
420
				$comment['comment_ID']
421
			)
422
		);
423
424
		if ( $exists ) {
425
			$wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
426
		} else {
427
			$wpdb->insert( $wpdb->comments, $comment );
428
		}
429
430
		wp_update_comment_count( $comment['comment_post_ID'] );
431
	}
432
433
	/**
434
	 * Trash a comment by the comment ID.
435
	 *
436
	 * @access public
437
	 *
438
	 * @param int $comment_id Comment ID.
439
	 */
440
	public function trash_comment( $comment_id ) {
441
		wp_delete_comment( $comment_id );
442
	}
443
444
	/**
445
	 * Delete a comment by the comment ID.
446
	 *
447
	 * @access public
448
	 *
449
	 * @param int $comment_id Comment ID.
450
	 */
451
	public function delete_comment( $comment_id ) {
452
		wp_delete_comment( $comment_id, true );
453
	}
454
455
	/**
456
	 * Mark a comment by the comment ID as spam.
457
	 *
458
	 * @access public
459
	 *
460
	 * @param int $comment_id Comment ID.
461
	 */
462
	public function spam_comment( $comment_id ) {
463
		wp_spam_comment( $comment_id );
464
	}
465
466
	/**
467
	 * Trash the comments of a post.
468
	 *
469
	 * @access public
470
	 *
471
	 * @param int   $post_id  Post ID.
472
	 * @param array $statuses Post statuses. Not used in this implementation.
473
	 */
474
	public function trashed_post_comments( $post_id, $statuses ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
475
		wp_trash_post_comments( $post_id );
476
	}
477
478
	/**
479
	 * Untrash the comments of a post.
480
	 *
481
	 * @access public
482
	 *
483
	 * @param int $post_id Post ID.
484
	 */
485
	public function untrashed_post_comments( $post_id ) {
486
		wp_untrash_post_comments( $post_id );
487
	}
488
489
	/**
490
	 * Retrieve the checksum for comments within a range.
491
	 *
492
	 * @access public
493
	 *
494
	 * @param int $min_id Minimum comment ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
495
	 * @param int $max_id Maximum comment ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
496
	 * @return int The checksum.
497
	 */
498
	public function comments_checksum( $min_id = null, $max_id = null ) {
499
		global $wpdb;
500
		return $this->table_checksum( $wpdb->comments, Defaults::$default_comment_checksum_columns, 'comment_ID', Settings::get_comments_filter_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_comment_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
501
	}
502
503
	/**
504
	 * Retrieve the checksum for comment meta within a range.
505
	 *
506
	 * @access public
507
	 *
508
	 * @param int $min_id Minimum comment meta ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
509
	 * @param int $max_id Maximum comment meta ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
510
	 * @return int The checksum.
511
	 */
512
	public function comment_meta_checksum( $min_id = null, $max_id = null ) {
513
		global $wpdb;
514
		return $this->table_checksum( $wpdb->commentmeta, Defaults::$default_comment_meta_checksum_columns, 'meta_id', Settings::get_whitelisted_comment_meta_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_comment_meta_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
515
	}
516
517
	/**
518
	 * Retrieve the checksum for all options.
519
	 *
520
	 * @access public
521
	 *
522
	 * @return int The checksum.
523
	 */
524
	public function options_checksum() {
525
		global $wpdb;
526
		$options_whitelist = "'" . implode( "', '", Defaults::$default_options_whitelist ) . "'";
0 ignored issues
show
Bug introduced by
The property default_options_whitelist cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
527
		$where_sql         = "option_name IN ( $options_whitelist )";
528
529
		return $this->table_checksum( $wpdb->options, Defaults::$default_option_checksum_columns, null, $where_sql, null, null );
0 ignored issues
show
Bug introduced by
The property default_option_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
530
	}
531
532
	/**
533
	 * Update the value of an option.
534
	 *
535
	 * @access public
536
	 *
537
	 * @param string $option Option name.
538
	 * @param mixed  $value  Option value.
539
	 * @return bool False if value was not updated and true if value was updated.
540
	 */
541
	public function update_option( $option, $value ) {
542
		return update_option( $option, $value );
543
	}
544
545
	/**
546
	 * Retrieve an option value based on an option name.
547
	 *
548
	 * @access public
549
	 *
550
	 * @param string $option  Name of option to retrieve.
551
	 * @param mixed  $default Optional. Default value to return if the option does not exist.
552
	 * @return mixed Value set for the option.
553
	 */
554
	public function get_option( $option, $default = false ) {
555
		return get_option( $option, $default );
556
	}
557
558
	/**
559
	 * Remove an option by name.
560
	 *
561
	 * @access public
562
	 *
563
	 * @param string $option Name of option to remove.
564
	 * @return bool True, if option is successfully deleted. False on failure.
565
	 */
566
	public function delete_option( $option ) {
567
		return delete_option( $option );
568
	}
569
570
	/**
571
	 * Change the features that the current theme supports.
572
	 * Intentionally not implemented in this replicastore.
573
	 *
574
	 * @access public
575
	 *
576
	 * @param array $theme_support Features that the theme supports.
577
	 */
578
	public function set_theme_support( $theme_support ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
579
		// Noop.
580
	}
581
582
	/**
583
	 * Whether the current theme supports a certain feature.
584
	 *
585
	 * @access public
586
	 *
587
	 * @param string $feature Name of the feature.
588
	 */
589
	public function current_theme_supports( $feature ) {
590
		return current_theme_supports( $feature );
591
	}
592
593
	/**
594
	 * Retrieve metadata for the specified object.
595
	 *
596
	 * @access public
597
	 *
598
	 * @param string $type       Meta type.
599
	 * @param int    $object_id  ID of the object.
600
	 * @param string $meta_key   Meta key.
601
	 * @param bool   $single     If true, return only the first value of the specified meta_key.
602
	 *
603
	 * @return mixed Single metadata value, or array of values.
604
	 */
605
	public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
606
		return get_metadata( $type, $object_id, $meta_key, $single );
607
	}
608
609
	/**
610
	 * Stores remote meta key/values alongside an ID mapping key.
611
	 *
612
	 * @access public
613
	 *
614
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
615
	 *
616
	 * @param string $type       Meta type.
617
	 * @param int    $object_id  ID of the object.
618
	 * @param string $meta_key   Meta key.
619
	 * @param mixed  $meta_value Meta value.
620
	 * @param int    $meta_id    ID of the meta.
621
	 *
622
	 * @return bool False if meta table does not exist, true otherwise.
623
	 */
624
	public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
625
		$table = _get_meta_table( $type );
626
		if ( ! $table ) {
627
			return false;
628
		}
629
630
		global $wpdb;
631
632
		$exists = $wpdb->get_var(
633
			$wpdb->prepare(
634
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
635
				"SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
636
				$meta_id
637
			)
638
		);
639
640
		if ( $exists ) {
641
			$wpdb->update(
642
				$table,
643
				array(
644
					'meta_key'   => $meta_key,
645
					'meta_value' => maybe_serialize( $meta_value ),
646
				),
647
				array( 'meta_id' => $meta_id )
648
			);
649
		} else {
650
			$object_id_field = $type . '_id';
651
			$wpdb->insert(
652
				$table,
653
				array(
654
					'meta_id'        => $meta_id,
655
					$object_id_field => $object_id,
656
					'meta_key'       => $meta_key,
657
					'meta_value'     => maybe_serialize( $meta_value ),
658
				)
659
			);
660
		}
661
662
		wp_cache_delete( $object_id, $type . '_meta' );
663
664
		return true;
665
	}
666
667
	/**
668
	 * Delete metadata for the specified object.
669
	 *
670
	 * @access public
671
	 *
672
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
673
	 *
674
	 * @param string $type      Meta type.
675
	 * @param int    $object_id ID of the object.
676
	 * @param array  $meta_ids  IDs of the meta objects to delete.
677
	 */
678
	public function delete_metadata( $type, $object_id, $meta_ids ) {
679
		global $wpdb;
680
681
		$table = _get_meta_table( $type );
682
		if ( ! $table ) {
683
			return false;
684
		}
685
686
		foreach ( $meta_ids as $meta_id ) {
687
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
688
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
689
		}
690
691
		// If we don't have an object ID what do we do - invalidate ALL meta?
692
		if ( $object_id ) {
693
			wp_cache_delete( $object_id, $type . '_meta' );
694
		}
695
	}
696
697
	/**
698
	 * Delete metadata with a certain key for the specified objects.
699
	 *
700
	 * @access public
701
	 *
702
	 * @todo Test this out to make sure it works as expected.
703
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
704
	 *
705
	 * @param string $type       Meta type.
706
	 * @param array  $object_ids IDs of the objects.
707
	 * @param string $meta_key   Meta key.
708
	 */
709
	public function delete_batch_metadata( $type, $object_ids, $meta_key ) {
710
		global $wpdb;
711
712
		$table = _get_meta_table( $type );
713
		if ( ! $table ) {
714
			return false;
715
		}
716
		$column = sanitize_key( $type . '_id' );
717
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
718
		$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE $column IN (%s) && meta_key = %s", implode( ',', $object_ids ), $meta_key ) );
719
720
		// If we don't have an object ID what do we do - invalidate ALL meta?
721
		foreach ( $object_ids as $object_id ) {
722
			wp_cache_delete( $object_id, $type . '_meta' );
723
		}
724
	}
725
726
	/**
727
	 * Retrieve value of a constant based on the constant name.
728
	 *
729
	 * @access public
730
	 *
731
	 * @param string $constant Name of constant to retrieve.
732
	 * @return mixed Value set for the constant.
733
	 */
734
	public function get_constant( $constant ) {
735
		$value = get_option( 'jetpack_constant_' . $constant );
736
737
		if ( $value ) {
738
			return $value;
739
		}
740
741
		return null;
742
	}
743
744
	/**
745
	 * Set the value of a constant.
746
	 *
747
	 * @access public
748
	 *
749
	 * @param string $constant Name of constant to retrieve.
750
	 * @param mixed  $value    Value set for the constant.
751
	 */
752
	public function set_constant( $constant, $value ) {
753
		update_option( 'jetpack_constant_' . $constant, $value );
754
	}
755
756
	/**
757
	 * Retrieve the number of the available updates of a certain type.
758
	 * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
759
	 *
760
	 * @access public
761
	 *
762
	 * @param string $type Type of updates to retrieve.
763
	 * @return int|null Number of updates available, `null` if type is invalid or missing.
764
	 */
765
	public function get_updates( $type ) {
766
		$all_updates = get_option( 'jetpack_updates', array() );
767
768
		if ( isset( $all_updates[ $type ] ) ) {
769
			return $all_updates[ $type ];
770
		} else {
771
			return null;
772
		}
773
	}
774
775
	/**
776
	 * Set the available updates of a certain type.
777
	 * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
778
	 *
779
	 * @access public
780
	 *
781
	 * @param string $type    Type of updates to set.
782
	 * @param int    $updates Total number of updates.
783
	 */
784
	public function set_updates( $type, $updates ) {
785
		$all_updates          = get_option( 'jetpack_updates', array() );
786
		$all_updates[ $type ] = $updates;
787
		update_option( 'jetpack_updates', $all_updates );
788
	}
789
790
	/**
791
	 * Retrieve a callable value based on its name.
792
	 *
793
	 * @access public
794
	 *
795
	 * @param string $name Name of the callable to retrieve.
796
	 * @return mixed Value of the callable.
797
	 */
798
	public function get_callable( $name ) {
799
		$value = get_option( 'jetpack_' . $name );
800
801
		if ( $value ) {
802
			return $value;
803
		}
804
805
		return null;
806
	}
807
808
	/**
809
	 * Update the value of a callable.
810
	 *
811
	 * @access public
812
	 *
813
	 * @param string $name  Callable name.
814
	 * @param mixed  $value Callable value.
815
	 */
816
	public function set_callable( $name, $value ) {
817
		update_option( 'jetpack_' . $name, $value );
818
	}
819
820
	/**
821
	 * Retrieve a network option value based on a network option name.
822
	 *
823
	 * @access public
824
	 *
825
	 * @param string $option Name of network option to retrieve.
826
	 * @return mixed Value set for the network option.
827
	 */
828
	public function get_site_option( $option ) {
829
		return get_option( 'jetpack_network_' . $option );
830
	}
831
832
	/**
833
	 * Update the value of a network option.
834
	 *
835
	 * @access public
836
	 *
837
	 * @param string $option Network option name.
838
	 * @param mixed  $value  Network option value.
839
	 * @return bool False if value was not updated and true if value was updated.
840
	 */
841
	public function update_site_option( $option, $value ) {
842
		return update_option( 'jetpack_network_' . $option, $value );
843
	}
844
845
	/**
846
	 * Remove a network option by name.
847
	 *
848
	 * @access public
849
	 *
850
	 * @param string $option Name of option to remove.
851
	 * @return bool True, if option is successfully deleted. False on failure.
852
	 */
853
	public function delete_site_option( $option ) {
854
		return delete_option( 'jetpack_network_' . $option );
855
	}
856
857
	/**
858
	 * Retrieve the terms from a particular taxonomy.
859
	 *
860
	 * @access public
861
	 *
862
	 * @param string $taxonomy Taxonomy slug.
863
	 * @return array Array of terms.
864
	 */
865
	public function get_terms( $taxonomy ) {
866
		return get_terms( $taxonomy );
867
	}
868
869
	/**
870
	 * Retrieve a particular term.
871
	 *
872
	 * @access public
873
	 *
874
	 * @param string $taxonomy   Taxonomy slug.
875
	 * @param int    $term_id    ID of the term.
876
	 * @param bool   $is_term_id Whether this is a `term_id` or a `term_taxonomy_id`.
877
	 * @return \WP_Term|\WP_Error Term object on success, \WP_Error object on failure.
878
	 */
879
	public function get_term( $taxonomy, $term_id, $is_term_id = true ) {
880
		$t = $this->ensure_taxonomy( $taxonomy );
881
		if ( ! $t || is_wp_error( $t ) ) {
882
			return $t;
883
		}
884
885
		return get_term( $term_id, $taxonomy );
886
	}
887
888
	/**
889
	 * Verify a taxonomy is legitimate and register it if necessary.
890
	 *
891
	 * @access private
892
	 *
893
	 * @param string $taxonomy Taxonomy slug.
894
	 * @return bool|void|\WP_Error True if already exists; void if it was registered; \WP_Error on error.
895
	 */
896
	private function ensure_taxonomy( $taxonomy ) {
897
		if ( ! taxonomy_exists( $taxonomy ) ) {
898
			// Try re-registering synced taxonomies.
899
			$taxonomies = $this->get_callable( 'taxonomies' );
900
			if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
901
				// Doesn't exist, or somehow hasn't been synced.
902
				return new \WP_Error( 'invalid_taxonomy', "The taxonomy '$taxonomy' doesn't exist" );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_taxonomy'.

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...
903
			}
904
			$t = $taxonomies[ $taxonomy ];
905
906
			return register_taxonomy(
907
				$taxonomy,
908
				$t->object_type,
909
				(array) $t
910
			);
911
		}
912
913
		return true;
914
	}
915
916
	/**
917
	 * Retrieve all terms from a taxonomy that are related to an object with a particular ID.
918
	 *
919
	 * @access public
920
	 *
921
	 * @param int    $object_id Object ID.
922
	 * @param string $taxonomy  Taxonomy slug.
923
	 * @return array|bool|\WP_Error Array of terms on success, `false` if no terms or post doesn't exist, \WP_Error on failure.
924
	 */
925
	public function get_the_terms( $object_id, $taxonomy ) {
926
		return get_the_terms( $object_id, $taxonomy );
927
	}
928
929
	/**
930
	 * Insert or update a term.
931
	 *
932
	 * @access public
933
	 *
934
	 * @param \WP_Term $term_object Term object.
935
	 * @return array|bool|\WP_Error Array of term_id and term_taxonomy_id if updated, true if inserted, \WP_Error on failure.
936
	 */
937
	public function update_term( $term_object ) {
938
		$taxonomy = $term_object->taxonomy;
939
		global $wpdb;
940
		$exists = $wpdb->get_var(
941
			$wpdb->prepare(
942
				"SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
943
				$term_object->term_id
944
			)
945
		);
946
		if ( ! $exists ) {
947
			$term_object   = sanitize_term( clone( $term_object ), $taxonomy, 'db' );
948
			$term          = array(
949
				'term_id'    => $term_object->term_id,
950
				'name'       => $term_object->name,
951
				'slug'       => $term_object->slug,
952
				'term_group' => $term_object->term_group,
953
			);
954
			$term_taxonomy = array(
955
				'term_taxonomy_id' => $term_object->term_taxonomy_id,
956
				'term_id'          => $term_object->term_id,
957
				'taxonomy'         => $term_object->taxonomy,
958
				'description'      => $term_object->description,
959
				'parent'           => (int) $term_object->parent,
960
				'count'            => (int) $term_object->count,
961
			);
962
			$wpdb->insert( $wpdb->terms, $term );
963
			$wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
964
965
			return true;
966
		}
967
968
		return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
969
	}
970
971
	/**
972
	 * Delete a term by the term ID and its corresponding taxonomy.
973
	 *
974
	 * @access public
975
	 *
976
	 * @param int    $term_id  Term ID.
977
	 * @param string $taxonomy Taxonomy slug.
978
	 * @return bool|int|\WP_Error True on success, false if term doesn't exist. Zero if trying with default category. \WP_Error on invalid taxonomy.
979
	 */
980
	public function delete_term( $term_id, $taxonomy ) {
981
		return wp_delete_term( $term_id, $taxonomy );
982
	}
983
984
	/**
985
	 * Add/update terms of a particular taxonomy of an object with the specified ID.
986
	 *
987
	 * @access public
988
	 *
989
	 * @param int              $object_id The object to relate to.
990
	 * @param string           $taxonomy  The context in which to relate the term to the object.
991
	 * @param string|int|array $terms     A single term slug, single term id, or array of either term slugs or ids.
992
	 * @param bool             $append    Optional. If false will delete difference of terms. Default false.
993
	 */
994
	public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
995
		wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
996
	}
997
998
	/**
999
	 * Remove certain term relationships from the specified object.
1000
	 *
1001
	 * @access public
1002
	 *
1003
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
1004
	 *
1005
	 * @param int   $object_id ID of the object.
1006
	 * @param array $tt_ids    Term taxonomy IDs.
1007
	 * @return bool True on success, false on failure.
1008
	 */
1009
	public function delete_object_terms( $object_id, $tt_ids ) {
1010
		global $wpdb;
1011
1012
		if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
1013
			// Escape.
1014
			$tt_ids_sanitized = array_map( 'intval', $tt_ids );
1015
1016
			$taxonomies = array();
1017
			foreach ( $tt_ids_sanitized as $tt_id ) {
1018
				$term                            = get_term_by( 'term_taxonomy_id', $tt_id );
1019
				$taxonomies[ $term->taxonomy ][] = $tt_id;
1020
			}
1021
			$in_tt_ids = implode( ', ', $tt_ids_sanitized );
1022
1023
			/**
1024
			 * Fires immediately before an object-term relationship is deleted.
1025
			 *
1026
			 * @since 2.9.0
1027
			 *
1028
			 * @param int   $object_id Object ID.
1029
			 * @param array $tt_ids    An array of term taxonomy IDs.
1030
			 */
1031
			do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized );
1032
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1033
			$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
1034
			foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
1035
				$this->ensure_taxonomy( $taxonomy );
1036
				wp_cache_delete( $object_id, $taxonomy . '_relationships' );
1037
				/**
1038
				 * Fires immediately after an object-term relationship is deleted.
1039
				 *
1040
				 * @since 2.9.0
1041
				 *
1042
				 * @param int   $object_id Object ID.
1043
				 * @param array $tt_ids    An array of term taxonomy IDs.
1044
				 */
1045
				do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
1046
				wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
1047
			}
1048
1049
			return (bool) $deleted;
1050
		}
1051
1052
		return false;
1053
	}
1054
1055
	/**
1056
	 * Retrieve the number of users.
1057
	 * Not supported in this replicastore.
1058
	 *
1059
	 * @access public
1060
	 */
1061
	public function user_count() {
1062
		// Noop.
1063
	}
1064
1065
	/**
1066
	 * Retrieve a user object by the user ID.
1067
	 *
1068
	 * @access public
1069
	 *
1070
	 * @param int $user_id User ID.
1071
	 * @return \WP_User User object.
1072
	 */
1073
	public function get_user( $user_id ) {
1074
		return \WP_User::get_instance( $user_id );
1075
	}
1076
1077
	/**
1078
	 * Insert or update a user.
1079
	 * Not supported in this replicastore.
1080
	 *
1081
	 * @access public
1082
	 * @throws \Exception If this method is invoked.
1083
	 *
1084
	 * @param \WP_User $user User object.
1085
	 */
1086
	public function upsert_user( $user ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1087
		$this->invalid_call();
1088
	}
1089
1090
	/**
1091
	 * Delete a user.
1092
	 * Not supported in this replicastore.
1093
	 *
1094
	 * @access public
1095
	 * @throws \Exception If this method is invoked.
1096
	 *
1097
	 * @param int $user_id User ID.
1098
	 */
1099
	public function delete_user( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1100
		$this->invalid_call();
1101
	}
1102
1103
	/**
1104
	 * Update/insert user locale.
1105
	 * Not supported in this replicastore.
1106
	 *
1107
	 * @access public
1108
	 * @throws \Exception If this method is invoked.
1109
	 *
1110
	 * @param int    $user_id User ID.
1111
	 * @param string $local   The user locale.
1112
	 */
1113
	public function upsert_user_locale( $user_id, $local ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1114
		$this->invalid_call();
1115
	}
1116
1117
	/**
1118
	 * Delete user locale.
1119
	 * Not supported in this replicastore.
1120
	 *
1121
	 * @access public
1122
	 * @throws \Exception If this method is invoked.
1123
	 *
1124
	 * @param int $user_id User ID.
1125
	 */
1126
	public function delete_user_locale( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1127
		$this->invalid_call();
1128
	}
1129
1130
	/**
1131
	 * Retrieve the user locale.
1132
	 *
1133
	 * @access public
1134
	 *
1135
	 * @param int $user_id User ID.
1136
	 * @return string The user locale.
1137
	 */
1138
	public function get_user_locale( $user_id ) {
1139
		return get_user_locale( $user_id );
1140
	}
1141
1142
	/**
1143
	 * Retrieve the allowed mime types for the user.
1144
	 * Not supported in this replicastore.
1145
	 *
1146
	 * @access public
1147
	 *
1148
	 * @param int $user_id User ID.
1149
	 */
1150
	public function get_allowed_mime_types( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1151
		// Noop.
1152
	}
1153
1154
	/**
1155
	 * Retrieve all the checksums we are interested in.
1156
	 * Currently that is posts, comments, post meta and comment meta.
1157
	 *
1158
	 * @access public
1159
	 *
1160
	 * @return array Checksums.
1161
	 */
1162
	public function checksum_all() {
1163
		$post_meta_checksum    = $this->checksum_histogram( 'post_meta', 1 );
1164
		$comment_meta_checksum = $this->checksum_histogram( 'comment_meta', 1 );
1165
1166
		return array(
1167
			'posts'        => $this->posts_checksum(),
1168
			'comments'     => $this->comments_checksum(),
1169
			'post_meta'    => reset( $post_meta_checksum ),
1170
			'comment_meta' => reset( $comment_meta_checksum ),
1171
		);
1172
	}
1173
1174
	/**
1175
	 * Retrieve the columns that are needed to calculate a checksum for an object type.
1176
	 *
1177
	 * @access public
1178
	 *
1179
	 * @todo Refactor to not use interpolated values and prepare the SQL query.
1180
	 *
1181
	 * @param string $object_type Object type.
1182
	 * @return array|bool Columns, or false if invalid object type is specified.
1183
	 */
1184
	public function get_checksum_columns_for_object_type( $object_type ) {
1185
		switch ( $object_type ) {
1186
			case 'posts':
1187
				return Defaults::$default_post_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_post_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1188
			case 'post_meta':
1189
				return Defaults::$default_post_meta_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_post_meta_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1190
			case 'comments':
1191
				return Defaults::$default_comment_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_comment_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1192
			case 'comment_meta':
1193
				return Defaults::$default_post_meta_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_post_meta_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1194
			case 'terms':
1195
				return Defaults::$default_term_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_term_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1196
			case 'term_taxonomy':
1197
				return Defaults::$default_term_taxonomy_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_term_taxonomy_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1198
			case 'term_relationships':
1199
				return Defaults::$default_term_relationships_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_term_relationships_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1200
			default:
1201
				return false;
1202
		}
1203
	}
1204
1205
	/**
1206
	 * Grabs the minimum and maximum object ids for the given parameters.
1207
	 *
1208
	 * @access public
1209
	 *
1210
	 * @param string $id_field     The id column in the table to query.
1211
	 * @param string $object_table The table to query.
1212
	 * @param string $where        A sql where clause without 'WHERE'.
1213
	 * @param int    $bucket_size  The maximum amount of objects to include in the query.
1214
	 *                             For `term_relationships` table, the bucket size will refer to the amount
1215
	 *                             of distinct object ids. This will likely include more database rows than
1216
	 *                             the bucket size implies.
1217
	 *
1218
	 * @return object An object with min_id and max_id properties.
1219
	 */
1220
	public function get_min_max_object_id( $id_field, $object_table, $where, $bucket_size ) {
1221
		global $wpdb;
1222
1223
		// The term relationship table's unique key is a combination of 2 columns. `DISTINCT` helps us get a more acurate query.
1224
		$distinct_sql = ( $wpdb->term_relationships === $object_table ) ? 'DISTINCT' : '';
1225
		$where_sql    = $where ? "WHERE $where" : '';
1226
1227
		// Since MIN() and MAX() do not work with LIMIT, we'll need to adjust the dataset we query if a limit is present.
1228
		// With a limit present, we'll look at a dataset consisting of object_ids that meet the constructs of the $where clause.
1229
		// Without a limit, we can use the actual table as a dataset.
1230
		$from = $bucket_size ?
1231
			"( SELECT $distinct_sql $id_field FROM $object_table $where_sql ORDER BY $id_field ASC LIMIT $bucket_size ) as ids" :
1232
			"$object_table $where_sql ORDER BY $id_field ASC";
1233
1234
		return $wpdb->get_row(
1235
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1236
			"SELECT MIN($id_field) as min, MAX($id_field) as max FROM $from"
1237
		);
1238
	}
1239
1240
	/**
1241
	 * Retrieve the checksum histogram for a specific object type.
1242
	 *
1243
	 * @access public
1244
	 *
1245
	 * @todo Refactor to not use interpolated values and properly prepare the SQL query.
1246
	 *
1247
	 * @param string $object_type     Object type.
1248
	 * @param int    $buckets         Number of buckets to split the objects to.
1249
	 * @param int    $start_id        Minimum object ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $start_id not be integer|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...
1250
	 * @param int    $end_id          Maximum object ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $end_id not be integer|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...
1251
	 * @param array  $columns         Table columns to calculate the checksum from.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $columns not be array|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...
1252
	 * @param bool   $strip_non_ascii Whether to strip non-ASCII characters.
1253
	 * @param string $salt            Salt, used for $wpdb->prepare()'s args.
1254
	 * @return array The checksum histogram.
1255
	 */
1256
	public function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '' ) {
1257
		global $wpdb;
1258
1259
		$wpdb->queries = array();
1260
1261
		if ( empty( $columns ) ) {
1262
			$columns = $this->get_checksum_columns_for_object_type( $object_type );
1263
		}
1264
1265
		switch ( $object_type ) {
1266
			case 'posts':
1267
				$object_count = $this->post_count( null, $start_id, $end_id );
1268
				$object_table = $wpdb->posts;
1269
				$id_field     = 'ID';
1270
				$where_sql    = Settings::get_blacklisted_post_types_sql();
1271
				break;
1272
			case 'post_meta':
1273
				$object_table = $wpdb->postmeta;
1274
				$where_sql    = Settings::get_whitelisted_post_meta_sql();
1275
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
1276
				$id_field     = 'meta_id';
1277
				break;
1278
			case 'comments':
1279
				$object_count = $this->comment_count( null, $start_id, $end_id );
1280
				$object_table = $wpdb->comments;
1281
				$id_field     = 'comment_ID';
1282
				$where_sql    = Settings::get_comments_filter_sql();
1283
				break;
1284
			case 'comment_meta':
1285
				$object_table = $wpdb->commentmeta;
1286
				$where_sql    = Settings::get_whitelisted_comment_meta_sql();
1287
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
1288
				$id_field     = 'meta_id';
1289
				break;
1290
			case 'terms':
1291
				$object_table = $wpdb->terms;
1292
				$object_count = $this->term_count();
1293
				$id_field     = 'term_id';
1294
				$where_sql    = '1=1';
1295
				break;
1296
			case 'term_taxonomy':
1297
				$object_table = $wpdb->term_taxonomy;
1298
				$object_count = $this->term_taxonomy_count();
1299
				$id_field     = 'term_taxonomy_id';
1300
				$where_sql    = '1=1';
1301
				break;
1302
			case 'term_relationships':
1303
				$object_table = $wpdb->term_relationships;
1304
				$object_count = $this->term_relationship_count();
1305
				$id_field     = 'object_id';
1306
				$where_sql    = '1=1';
1307
				break;
1308
			default:
1309
				return false;
1310
		}
1311
1312
		$bucket_size     = intval( ceil( $object_count / $buckets ) );
1313
		$previous_max_id = 0;
1314
		$histogram       = array();
1315
1316
		// This is used for the min / max query, while $where_sql is used for the checksum query.
1317
		$where = $where_sql;
1318
1319
		if ( $start_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $start_id of type integer|null 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...
1320
			$where .= " AND $id_field >= " . intval( $start_id );
1321
		}
1322
1323
		if ( $end_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $end_id of type integer|null 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...
1324
			$where .= " AND $id_field <= " . intval( $end_id );
1325
		}
1326
1327
		do {
1328
			$result = $this->get_min_max_object_id(
1329
				$id_field,
1330
				$object_table,
1331
				$where . " AND $id_field > $previous_max_id",
1332
				$bucket_size
1333
			);
1334
1335
			if ( null === $result->min || null === $result->max ) {
1336
				// Nothing to checksum here...
1337
				break;
1338
			}
1339
1340
			// Get the checksum value.
1341
			$value = $this->table_checksum( $object_table, $columns, $id_field, $where_sql, $result->min, $result->max, $strip_non_ascii, $salt );
0 ignored issues
show
Security Bug introduced by
It seems like $columns can also be of type false; however, Automattic\Jetpack\Sync\...store::table_checksum() does only seem to accept array, did you maybe forget to handle an error condition?
Loading history...
1342
1343
			if ( is_wp_error( $value ) ) {
1344
				return $value;
1345
			}
1346
1347
			if ( null === $result->min || null === $result->max ) {
1348
				break;
1349
			} elseif ( $result->min === $result->max ) {
1350
				$histogram[ $result->min ] = $value;
1351
			} else {
1352
				$histogram[ "{$result->min}-{$result->max}" ] = $value;
1353
			}
1354
1355
			$previous_max_id = $result->max;
1356
		} while ( true );
1357
1358
		return $histogram;
1359
	}
1360
1361
	/**
1362
	 * Retrieve the checksum for a specific database table.
1363
	 *
1364
	 * @access private
1365
	 *
1366
	 * @todo Refactor to properly prepare the SQL query.
1367
	 *
1368
	 * @param string $table           Table name.
1369
	 * @param array  $columns         Table columns to calculate the checksum from.
1370
	 * @param int    $id_column       Name of the unique ID column.
1371
	 * @param string $where_sql       Additional WHERE clause SQL.
1372
	 * @param int    $min_id          Minimum object ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $min_id not be integer|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...
1373
	 * @param int    $max_id          Maximum object ID.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $max_id not be integer|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...
1374
	 * @param bool   $strip_non_ascii Whether to strip non-ASCII characters.
1375
	 * @param string $salt            Salt, used for $wpdb->prepare()'s args.
1376
	 * @return int|\WP_Error The table histogram, or \WP_Error on failure.
1377
	 */
1378
	private function table_checksum( $table, $columns, $id_column, $where_sql = '1=1', $min_id = null, $max_id = null, $strip_non_ascii = true, $salt = '' ) {
1379
		global $wpdb;
1380
1381
		// Sanitize to just valid MySQL column names.
1382
		$sanitized_columns = preg_grep( '/^[0-9,a-z,A-Z$_]+$/i', $columns );
1383
1384
		if ( $strip_non_ascii ) {
1385
			$columns_sql = implode( ',', array_map( array( $this, 'strip_non_ascii_sql' ), $sanitized_columns ) );
1386
		} else {
1387
			$columns_sql = implode( ',', $sanitized_columns );
1388
		}
1389
1390
		if ( null !== $min_id && null !== $max_id ) {
1391
			if ( $min_id === $max_id ) {
1392
				$min_id     = intval( $min_id );
1393
				$where_sql .= " AND $id_column = $min_id LIMIT 1";
1394
			} else {
1395
				$min_id     = intval( $min_id );
1396
				$max_id     = intval( $max_id );
1397
				$size       = $max_id - $min_id;
1398
				$where_sql .= " AND $id_column >= $min_id AND $id_column <= $max_id LIMIT $size";
1399
			}
1400
		} else {
1401
			if ( null !== $min_id ) {
1402
				$min_id     = intval( $min_id );
1403
				$where_sql .= " AND $id_column >= $min_id";
1404
			}
1405
1406
			if ( null !== $max_id ) {
1407
				$max_id     = intval( $max_id );
1408
				$where_sql .= " AND $id_column <= $max_id";
1409
			}
1410
		}
1411
1412
		$query = <<<ENDSQL
1413
			SELECT CAST( SUM( CRC32( CONCAT_WS( '#', '%s', {$columns_sql} ) ) ) AS UNSIGNED INT )
1414
			FROM $table
1415
			WHERE $where_sql;
1416
ENDSQL;
1417
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1418
		$result = $wpdb->get_var( $wpdb->prepare( $query, $salt ) );
1419
		if ( $wpdb->last_error ) {
1420
			return new \WP_Error( 'database_error', $wpdb->last_error );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'database_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...
1421
		}
1422
1423
		return $result;
1424
	}
1425
1426
	/**
1427
	 * Retrieve the type of the checksum.
1428
	 *
1429
	 * @access public
1430
	 *
1431
	 * @return string Type of the checksum.
1432
	 */
1433
	public function get_checksum_type() {
1434
		return 'sum';
1435
	}
1436
1437
	/**
1438
	 * Count the meta values in a table, within a specified range.
1439
	 *
1440
	 * @access private
1441
	 *
1442
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
1443
	 *
1444
	 * @param string $table     Table name.
1445
	 * @param string $where_sql Additional WHERE SQL.
1446
	 * @param int    $min_id    Minimum meta ID.
1447
	 * @param int    $max_id    Maximum meta ID.
1448
	 * @return int Number of meta values.
1449
	 */
1450
	private function meta_count( $table, $where_sql, $min_id, $max_id ) {
1451
		global $wpdb;
1452
1453
		if ( ! empty( $min_id ) ) {
1454
			$where_sql .= ' AND meta_id >= ' . intval( $min_id );
1455
		}
1456
1457
		if ( ! empty( $max_id ) ) {
1458
			$where_sql .= ' AND meta_id <= ' . intval( $max_id );
1459
		}
1460
1461
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1462
		return $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE $where_sql" );
1463
	}
1464
1465
	/**
1466
	 * Wraps a column name in SQL which strips non-ASCII chars.
1467
	 * This helps normalize data to avoid checksum differences caused by
1468
	 * badly encoded data in the DB.
1469
	 *
1470
	 * @param string $column_name Name of the column.
1471
	 * @return string Column name, without the non-ASCII chars.
1472
	 */
1473
	public function strip_non_ascii_sql( $column_name ) {
1474
		return "REPLACE( CONVERT( $column_name USING ascii ), '?', '' )";
1475
	}
1476
1477
	/**
1478
	 * Used in methods that are not implemented and shouldn't be invoked.
1479
	 *
1480
	 * @access private
1481
	 * @throws \Exception If this method is invoked.
1482
	 */
1483
	private function invalid_call() {
1484
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
1485
		$backtrace = debug_backtrace();
1486
		$caller    = $backtrace[1]['function'];
1487
		throw new \Exception( "This function $caller is not supported on the WP Replicastore" );
1488
	}
1489
}
1490