Completed
Push — fix/sync-phpcs-replicastore ( 2e2abb )
by Marin
06:05
created

Replicastore::meta_count()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 4
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
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 posts with a particular post status within a certain range.
78
	 *
79
	 * @access public
80
	 *
81
	 * @todo Prepare the SQL query before executing it.
82
	 *
83
	 * @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...
84
	 * @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...
85
	 * @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...
86
	 * @return int Number of posts.
87
	 */
88 View Code Duplication
	public function post_count( $status = null, $min_id = null, $max_id = null ) {
89
		global $wpdb;
90
91
		$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...
92
93
		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...
94
			$where = "post_status = '" . esc_sql( $status ) . "'";
95
		} else {
96
			$where = '1=1';
97
		}
98
99
		if ( null !== $min_id ) {
100
			$where .= ' AND ID >= ' . intval( $min_id );
101
		}
102
103
		if ( null !== $max_id ) {
104
			$where .= ' AND ID <= ' . intval( $max_id );
105
		}
106
107
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
108
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
109
	}
110
111
	/**
112
	 * Retrieve the posts with a particular post status.
113
	 *
114
	 * @access public
115
	 *
116
	 * @todo Implement range and actually use max_id/min_id arguments.
117
	 *
118
	 * @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...
119
	 * @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...
120
	 * @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...
121
	 * @return array Array of posts.
122
	 */
123
	public function get_posts( $status = null, $min_id = null, $max_id = null ) {
124
		$args = array(
125
			'orderby'        => 'ID',
126
			'posts_per_page' => -1,
127
		);
128
129
		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...
130
			$args['post_status'] = $status;
131
		} else {
132
			$args['post_status'] = 'any';
133
		}
134
135
		return get_posts( $args );
136
	}
137
138
	/**
139
	 * Retrieve a post object by the post ID.
140
	 *
141
	 * @access public
142
	 *
143
	 * @param int $id Post ID.
144
	 * @return \WP_Post Post object.
145
	 */
146
	public function get_post( $id ) {
147
		return get_post( $id );
148
	}
149
150
	/**
151
	 * Update or insert a post.
152
	 *
153
	 * @access public
154
	 *
155
	 * @param \WP_Post $post   Post object.
156
	 * @param bool     $silent Whether to perform a silent action. Not used in this implementation.
157
	 */
158
	public function upsert_post( $post, $silent = false ) {
159
		global $wpdb;
160
161
		// Reject the post if it's not a \WP_Post.
162
		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...
163
			return;
164
		}
165
166
		$post = $post->to_array();
167
168
		// Reject posts without an ID.
169
		if ( ! isset( $post['ID'] ) ) {
170
			return;
171
		}
172
173
		$now     = current_time( 'mysql' );
174
		$now_gmt = get_gmt_from_date( $now );
175
176
		$defaults = array(
177
			'ID'                    => 0,
178
			'post_author'           => '0',
179
			'post_content'          => '',
180
			'post_content_filtered' => '',
181
			'post_title'            => '',
182
			'post_name'             => '',
183
			'post_excerpt'          => '',
184
			'post_status'           => 'draft',
185
			'post_type'             => 'post',
186
			'comment_status'        => 'closed',
187
			'comment_count'         => '0',
188
			'ping_status'           => '',
189
			'post_password'         => '',
190
			'to_ping'               => '',
191
			'pinged'                => '',
192
			'post_parent'           => 0,
193
			'menu_order'            => 0,
194
			'guid'                  => '',
195
			'post_date'             => $now,
196
			'post_date_gmt'         => $now_gmt,
197
			'post_modified'         => $now,
198
			'post_modified_gmt'     => $now_gmt,
199
		);
200
201
		$post = array_intersect_key( $post, $defaults );
202
203
		$post = sanitize_post( $post, 'db' );
204
205
		unset( $post['filter'] );
206
207
		$exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
208
209
		if ( $exists ) {
210
			$wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
211
		} else {
212
			$wpdb->insert( $wpdb->posts, $post );
213
		}
214
215
		clean_post_cache( $post['ID'] );
216
	}
217
218
	/**
219
	 * Delete a post by the post ID.
220
	 *
221
	 * @access public
222
	 *
223
	 * @param int $post_id Post ID.
224
	 */
225
	public function delete_post( $post_id ) {
226
		wp_delete_post( $post_id, true );
227
	}
228
229
	/**
230
	 * Retrieve the checksum for posts within a range.
231
	 *
232
	 * @access public
233
	 *
234
	 * @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...
235
	 * @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...
236
	 * @return int The checksum.
237
	 */
238
	public function posts_checksum( $min_id = null, $max_id = null ) {
239
		global $wpdb;
240
		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...
241
	}
242
243
	/**
244
	 * Retrieve the checksum for post meta within a range.
245
	 *
246
	 * @access public
247
	 *
248
	 * @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...
249
	 * @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...
250
	 * @return int The checksum.
251
	 */
252
	public function post_meta_checksum( $min_id = null, $max_id = null ) {
253
		global $wpdb;
254
		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...
255
	}
256
257
	/**
258
	 * Retrieve the number of comments with a particular comment status within a certain range.
259
	 *
260
	 * @access public
261
	 *
262
	 * @todo Prepare the SQL query before executing it.
263
	 *
264
	 * @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...
265
	 * @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...
266
	 * @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...
267
	 * @return int Number of comments.
268
	 */
269 View Code Duplication
	public function comment_count( $status = null, $min_id = null, $max_id = null ) {
270
		global $wpdb;
271
272
		$comment_approved = $this->comment_status_to_approval_value( $status );
273
274
		if ( false !== $comment_approved ) {
275
			$where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
276
		} else {
277
			$where = '1=1';
278
		}
279
280
		if ( null !== $min_id ) {
281
			$where .= ' AND comment_ID >= ' . intval( $min_id );
282
		}
283
284
		if ( null !== $max_id ) {
285
			$where .= ' AND comment_ID <= ' . intval( $max_id );
286
		}
287
288
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
289
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
290
	}
291
292
	/**
293
	 * Translate a comment status to a value of the comment_approved field.
294
	 *
295
	 * @access private
296
	 *
297
	 * @param string $status Comment status.
298
	 * @return string|bool New comment_approved value, false if the status doesn't affect it.
299
	 */
300
	private function comment_status_to_approval_value( $status ) {
301
		switch ( $status ) {
302
			case 'approve':
303
				return '1';
304
			case 'hold':
305
				return '0';
306
			case 'spam':
307
				return 'spam';
308
			case 'trash':
309
				return 'trash';
310
			case 'any':
311
				return false;
312
			case 'all':
313
				return false;
314
			default:
315
				return false;
316
		}
317
	}
318
319
	/**
320
	 * Retrieve the comments with a particular comment status.
321
	 *
322
	 * @access public
323
	 *
324
	 * @todo Implement range and actually use max_id/min_id arguments.
325
	 *
326
	 * @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...
327
	 * @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...
328
	 * @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...
329
	 * @return array Array of comments.
330
	 */
331
	public function get_comments( $status = null, $min_id = null, $max_id = null ) {
332
		$args = array(
333
			'orderby' => 'ID',
334
			'status'  => 'all',
335
		);
336
337
		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...
338
			$args['status'] = $status;
339
		}
340
341
		return get_comments( $args );
342
	}
343
344
	/**
345
	 * Retrieve a comment object by the comment ID.
346
	 *
347
	 * @access public
348
	 *
349
	 * @param int $id Comment ID.
350
	 * @return \WP_Comment Comment object.
351
	 */
352
	public function get_comment( $id ) {
353
		return \WP_Comment::get_instance( $id );
354
	}
355
356
	/**
357
	 * Update or insert a comment.
358
	 *
359
	 * @access public
360
	 *
361
	 * @param \WP_Comment $comment Comment object.
362
	 */
363
	public function upsert_comment( $comment ) {
364
		global $wpdb;
365
366
		$comment = $comment->to_array();
367
368
		// Filter by fields on comment table.
369
		$comment_fields_whitelist = array(
370
			'comment_ID',
371
			'comment_post_ID',
372
			'comment_author',
373
			'comment_author_email',
374
			'comment_author_url',
375
			'comment_author_IP',
376
			'comment_date',
377
			'comment_date_gmt',
378
			'comment_content',
379
			'comment_karma',
380
			'comment_approved',
381
			'comment_agent',
382
			'comment_type',
383
			'comment_parent',
384
			'user_id',
385
		);
386
387
		foreach ( $comment as $key => $value ) {
388
			if ( ! in_array( $key, $comment_fields_whitelist, true ) ) {
389
				unset( $comment[ $key ] );
390
			}
391
		}
392
393
		$exists = $wpdb->get_var(
394
			$wpdb->prepare(
395
				"SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
396
				$comment['comment_ID']
397
			)
398
		);
399
400
		if ( $exists ) {
401
			$wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
402
		} else {
403
			$wpdb->insert( $wpdb->comments, $comment );
404
		}
405
406
		wp_update_comment_count( $comment['comment_post_ID'] );
407
	}
408
409
	/**
410
	 * Trash a comment by the comment ID.
411
	 *
412
	 * @access public
413
	 *
414
	 * @param int $comment_id Comment ID.
415
	 */
416
	public function trash_comment( $comment_id ) {
417
		wp_delete_comment( $comment_id );
418
	}
419
420
	/**
421
	 * Delete a comment by the comment ID.
422
	 *
423
	 * @access public
424
	 *
425
	 * @param int $comment_id Comment ID.
426
	 */
427
	public function delete_comment( $comment_id ) {
428
		wp_delete_comment( $comment_id, true );
429
	}
430
431
	/**
432
	 * Mark a comment by the comment ID as spam.
433
	 *
434
	 * @access public
435
	 *
436
	 * @param int $comment_id Comment ID.
437
	 */
438
	public function spam_comment( $comment_id ) {
439
		wp_spam_comment( $comment_id );
440
	}
441
442
	/**
443
	 * Trash the comments of a post.
444
	 *
445
	 * @access public
446
	 *
447
	 * @param int   $post_id  Post ID.
448
	 * @param array $statuses Post statuses. Not used in this implementation.
449
	 */
450
	public function trashed_post_comments( $post_id, $statuses ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
451
		wp_trash_post_comments( $post_id );
452
	}
453
454
	/**
455
	 * Untrash the comments of a post.
456
	 *
457
	 * @access public
458
	 *
459
	 * @param int $post_id Post ID.
460
	 */
461
	public function untrashed_post_comments( $post_id ) {
462
		wp_untrash_post_comments( $post_id );
463
	}
464
465
	/**
466
	 * Retrieve the checksum for comments within a range.
467
	 *
468
	 * @access public
469
	 *
470
	 * @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...
471
	 * @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...
472
	 * @return int The checksum.
473
	 */
474
	public function comments_checksum( $min_id = null, $max_id = null ) {
475
		global $wpdb;
476
		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...
477
	}
478
479
	/**
480
	 * Retrieve the checksum for comment meta within a range.
481
	 *
482
	 * @access public
483
	 *
484
	 * @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...
485
	 * @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...
486
	 * @return int The checksum.
487
	 */
488
	public function comment_meta_checksum( $min_id = null, $max_id = null ) {
489
		global $wpdb;
490
		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...
491
	}
492
493
	/**
494
	 * Retrieve the checksum for all options.
495
	 *
496
	 * @access public
497
	 *
498
	 * @return int The checksum.
499
	 */
500
	public function options_checksum() {
501
		global $wpdb;
502
		$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...
503
		$where_sql         = "option_name IN ( $options_whitelist )";
504
505
		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...
506
	}
507
508
	/**
509
	 * Update the value of an option.
510
	 *
511
	 * @access public
512
	 *
513
	 * @param string $option Option name.
514
	 * @param mixed  $value  Option value.
515
	 * @return bool False if value was not updated and true if value was updated.
516
	 */
517
	public function update_option( $option, $value ) {
518
		return update_option( $option, $value );
519
	}
520
521
	/**
522
	 * Retrieve an option value based on an option name.
523
	 *
524
	 * @access public
525
	 *
526
	 * @param string $option  Name of option to retrieve.
527
	 * @param mixed  $default Optional. Default value to return if the option does not exist.
528
	 * @return mixed Value set for the option.
529
	 */
530
	public function get_option( $option, $default = false ) {
531
		return get_option( $option, $default );
532
	}
533
534
	/**
535
	 * Remove an option by name.
536
	 *
537
	 * @access public
538
	 *
539
	 * @param string $option Name of option to remove.
540
	 * @return bool True, if option is successfully deleted. False on failure.
541
	 */
542
	public function delete_option( $option ) {
543
		return delete_option( $option );
544
	}
545
546
	/**
547
	 * Change the features that the current theme supports.
548
	 * Intentionally not implemented in this replicastore.
549
	 *
550
	 * @access public
551
	 *
552
	 * @param array $theme_support Features that the theme supports.
553
	 */
554
	public function set_theme_support( $theme_support ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
555
		// Noop.
556
	}
557
558
	/**
559
	 * Whether the current theme supports a certain feature.
560
	 *
561
	 * @access public
562
	 *
563
	 * @param string $feature Name of the feature.
564
	 */
565
	public function current_theme_supports( $feature ) {
566
		return current_theme_supports( $feature );
567
	}
568
569
	/**
570
	 * Retrieve metadata for the specified object.
571
	 *
572
	 * @access public
573
	 *
574
	 * @param string $type       Meta type.
575
	 * @param int    $object_id  ID of the object.
576
	 * @param string $meta_key   Meta key.
577
	 * @param bool   $single     If true, return only the first value of the specified meta_key.
578
	 *
579
	 * @return mixed Single metadata value, or array of values.
580
	 */
581
	public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
582
		return get_metadata( $type, $object_id, $meta_key, $single );
583
	}
584
585
	/**
586
	 * Stores remote meta key/values alongside an ID mapping key.
587
	 *
588
	 * @access public
589
	 *
590
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
591
	 *
592
	 * @param string $type       Meta type.
593
	 * @param int    $object_id  ID of the object.
594
	 * @param string $meta_key   Meta key.
595
	 * @param mixed  $meta_value Meta value.
596
	 * @param int    $meta_id    ID of the meta.
597
	 *
598
	 * @return bool False if meta table does not exist, true otherwise.
599
	 */
600
	public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
601
		$table = _get_meta_table( $type );
602
		if ( ! $table ) {
603
			return false;
604
		}
605
606
		global $wpdb;
607
608
		$exists = $wpdb->get_var(
609
			$wpdb->prepare(
610
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
611
				"SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
612
				$meta_id
613
			)
614
		);
615
616
		if ( $exists ) {
617
			$wpdb->update(
618
				$table,
619
				array(
620
					'meta_key'   => $meta_key,
621
					'meta_value' => maybe_serialize( $meta_value ),
622
				),
623
				array( 'meta_id' => $meta_id )
624
			);
625
		} else {
626
			$object_id_field = $type . '_id';
627
			$wpdb->insert(
628
				$table,
629
				array(
630
					'meta_id'        => $meta_id,
631
					$object_id_field => $object_id,
632
					'meta_key'       => $meta_key,
633
					'meta_value'     => maybe_serialize( $meta_value ),
634
				)
635
			);
636
		}
637
638
		wp_cache_delete( $object_id, $type . '_meta' );
639
640
		return true;
641
	}
642
643
	/**
644
	 * Delete metadata for the specified object.
645
	 *
646
	 * @access public
647
	 *
648
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
649
	 *
650
	 * @param string $type      Meta type.
651
	 * @param int    $object_id ID of the object.
652
	 * @param array  $meta_ids  IDs of the meta objects to delete.
653
	 */
654
	public function delete_metadata( $type, $object_id, $meta_ids ) {
655
		global $wpdb;
656
657
		$table = _get_meta_table( $type );
658
		if ( ! $table ) {
659
			return false;
660
		}
661
662
		foreach ( $meta_ids as $meta_id ) {
663
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
664
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
665
		}
666
667
		// If we don't have an object ID what do we do - invalidate ALL meta?
668
		if ( $object_id ) {
669
			wp_cache_delete( $object_id, $type . '_meta' );
670
		}
671
	}
672
673
	/**
674
	 * Delete metadata with a certain key for the specified objects.
675
	 *
676
	 * @access public
677
	 *
678
	 * @todo Test this out to make sure it works as expected.
679
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
680
	 *
681
	 * @param string $type       Meta type.
682
	 * @param array  $object_ids IDs of the objects.
683
	 * @param string $meta_key   Meta key.
684
	 */
685
	public function delete_batch_metadata( $type, $object_ids, $meta_key ) {
686
		global $wpdb;
687
688
		$table = _get_meta_table( $type );
689
		if ( ! $table ) {
690
			return false;
691
		}
692
		$column = sanitize_key( $type . '_id' );
693
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
694
		$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE $column IN (%s) && meta_key = %s", implode( ',', $object_ids ), $meta_key ) );
695
696
		// If we don't have an object ID what do we do - invalidate ALL meta?
697
		foreach ( $object_ids as $object_id ) {
698
			wp_cache_delete( $object_id, $type . '_meta' );
699
		}
700
	}
701
702
	/**
703
	 * Retrieve value of a constant based on the constant name.
704
	 *
705
	 * @access public
706
	 *
707
	 * @param string $constant Name of constant to retrieve.
708
	 * @return mixed Value set for the constant.
709
	 */
710
	public function get_constant( $constant ) {
711
		$value = get_option( 'jetpack_constant_' . $constant );
712
713
		if ( $value ) {
714
			return $value;
715
		}
716
717
		return null;
718
	}
719
720
	/**
721
	 * Set the value of a constant.
722
	 *
723
	 * @access public
724
	 *
725
	 * @param string $constant Name of constant to retrieve.
726
	 * @param mixed  $value    Value set for the constant.
727
	 */
728
	public function set_constant( $constant, $value ) {
729
		update_option( 'jetpack_constant_' . $constant, $value );
730
	}
731
732
	/**
733
	 * Retrieve the number of the available updates of a certain type.
734
	 * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
735
	 *
736
	 * @access public
737
	 *
738
	 * @param string $type Type of updates to retrieve.
739
	 * @return int|null Number of updates available, `null` if type is invalid or missing.
740
	 */
741
	public function get_updates( $type ) {
742
		$all_updates = get_option( 'jetpack_updates', array() );
743
744
		if ( isset( $all_updates[ $type ] ) ) {
745
			return $all_updates[ $type ];
746
		} else {
747
			return null;
748
		}
749
	}
750
751
	/**
752
	 * Set the available updates of a certain type.
753
	 * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
754
	 *
755
	 * @access public
756
	 *
757
	 * @param string $type    Type of updates to set.
758
	 * @param int    $updates Total number of updates.
759
	 */
760
	public function set_updates( $type, $updates ) {
761
		$all_updates          = get_option( 'jetpack_updates', array() );
762
		$all_updates[ $type ] = $updates;
763
		update_option( 'jetpack_updates', $all_updates );
764
	}
765
766
	/**
767
	 * Retrieve a callable value based on its name.
768
	 *
769
	 * @access public
770
	 *
771
	 * @param string $name Name of the callable to retrieve.
772
	 * @return mixed Value of the callable.
773
	 */
774
	public function get_callable( $name ) {
775
		$value = get_option( 'jetpack_' . $name );
776
777
		if ( $value ) {
778
			return $value;
779
		}
780
781
		return null;
782
	}
783
784
	/**
785
	 * Update the value of a callable.
786
	 *
787
	 * @access public
788
	 *
789
	 * @param string $name  Callable name.
790
	 * @param mixed  $value Callable value.
791
	 */
792
	public function set_callable( $name, $value ) {
793
		update_option( 'jetpack_' . $name, $value );
794
	}
795
796
	/**
797
	 * Retrieve a network option value based on a network option name.
798
	 *
799
	 * @access public
800
	 *
801
	 * @param string $option Name of network option to retrieve.
802
	 * @return mixed Value set for the network option.
803
	 */
804
	public function get_site_option( $option ) {
805
		return get_option( 'jetpack_network_' . $option );
806
	}
807
808
	/**
809
	 * Update the value of a network option.
810
	 *
811
	 * @access public
812
	 *
813
	 * @param string $option Network option name.
814
	 * @param mixed  $value  Network option value.
815
	 * @return bool False if value was not updated and true if value was updated.
816
	 */
817
	public function update_site_option( $option, $value ) {
818
		return update_option( 'jetpack_network_' . $option, $value );
819
	}
820
821
	/**
822
	 * Remove a network option by name.
823
	 *
824
	 * @access public
825
	 *
826
	 * @param string $option Name of option to remove.
827
	 * @return bool True, if option is successfully deleted. False on failure.
828
	 */
829
	public function delete_site_option( $option ) {
830
		return delete_option( 'jetpack_network_' . $option );
831
	}
832
833
	/**
834
	 * Retrieve the terms from a particular taxonomy.
835
	 *
836
	 * @access public
837
	 *
838
	 * @param string $taxonomy Taxonomy slug.
839
	 * @return array Array of terms.
840
	 */
841
	public function get_terms( $taxonomy ) {
842
		return get_terms( $taxonomy );
843
	}
844
845
	/**
846
	 * Retrieve a particular term.
847
	 *
848
	 * @access public
849
	 *
850
	 * @param string $taxonomy   Taxonomy slug.
851
	 * @param int    $term_id    ID of the term.
852
	 * @param bool   $is_term_id Whether this is a `term_id` or a `term_taxonomy_id`.
853
	 * @return \WP_Term|\WP_Error Term object on success, \WP_Error object on failure.
854
	 */
855
	public function get_term( $taxonomy, $term_id, $is_term_id = true ) {
856
		$t = $this->ensure_taxonomy( $taxonomy );
857
		if ( ! $t || is_wp_error( $t ) ) {
858
			return $t;
859
		}
860
861
		return get_term( $term_id, $taxonomy );
862
	}
863
864
	/**
865
	 * Verify a taxonomy is legitimate and register it if necessary.
866
	 *
867
	 * @access private
868
	 *
869
	 * @param string $taxonomy Taxonomy slug.
870
	 * @return bool|void|\WP_Error True if already exists; void if it was registered; \WP_Error on error.
871
	 */
872
	private function ensure_taxonomy( $taxonomy ) {
873
		if ( ! taxonomy_exists( $taxonomy ) ) {
874
			// Try re-registering synced taxonomies.
875
			$taxonomies = $this->get_callable( 'taxonomies' );
876
			if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
877
				// Doesn't exist, or somehow hasn't been synced.
878
				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...
879
			}
880
			$t = $taxonomies[ $taxonomy ];
881
882
			return register_taxonomy(
883
				$taxonomy,
884
				$t->object_type,
885
				(array) $t
886
			);
887
		}
888
889
		return true;
890
	}
891
892
	/**
893
	 * Retrieve all terms from a taxonomy that are related to an object with a particular ID.
894
	 *
895
	 * @access public
896
	 *
897
	 * @param int    $object_id Object ID.
898
	 * @param string $taxonomy  Taxonomy slug.
899
	 * @return array|bool|\WP_Error Array of terms on success, `false` if no terms or post doesn't exist, \WP_Error on failure.
900
	 */
901
	public function get_the_terms( $object_id, $taxonomy ) {
902
		return get_the_terms( $object_id, $taxonomy );
903
	}
904
905
	/**
906
	 * Insert or update a term.
907
	 *
908
	 * @access public
909
	 *
910
	 * @param \WP_Term $term_object Term object.
911
	 * @return array|bool|\WP_Error Array of term_id and term_taxonomy_id if updated, true if inserted, \WP_Error on failure.
912
	 */
913
	public function update_term( $term_object ) {
914
		$taxonomy = $term_object->taxonomy;
915
		global $wpdb;
916
		$exists = $wpdb->get_var(
917
			$wpdb->prepare(
918
				"SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
919
				$term_object->term_id
920
			)
921
		);
922
		if ( ! $exists ) {
923
			$term_object   = sanitize_term( clone( $term_object ), $taxonomy, 'db' );
924
			$term          = array(
925
				'term_id'    => $term_object->term_id,
926
				'name'       => $term_object->name,
927
				'slug'       => $term_object->slug,
928
				'term_group' => $term_object->term_group,
929
			);
930
			$term_taxonomy = array(
931
				'term_taxonomy_id' => $term_object->term_taxonomy_id,
932
				'term_id'          => $term_object->term_id,
933
				'taxonomy'         => $term_object->taxonomy,
934
				'description'      => $term_object->description,
935
				'parent'           => (int) $term_object->parent,
936
				'count'            => (int) $term_object->count,
937
			);
938
			$wpdb->insert( $wpdb->terms, $term );
939
			$wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
940
941
			return true;
942
		}
943
944
		return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
945
	}
946
947
	/**
948
	 * Delete a term by the term ID and its corresponding taxonomy.
949
	 *
950
	 * @access public
951
	 *
952
	 * @param int    $term_id  Term ID.
953
	 * @param string $taxonomy Taxonomy slug.
954
	 * @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.
955
	 */
956
	public function delete_term( $term_id, $taxonomy ) {
957
		return wp_delete_term( $term_id, $taxonomy );
958
	}
959
960
	/**
961
	 * Add/update terms of a particular taxonomy of an object with the specified ID.
962
	 *
963
	 * @access public
964
	 *
965
	 * @param int              $object_id The object to relate to.
966
	 * @param string           $taxonomy  The context in which to relate the term to the object.
967
	 * @param string|int|array $terms     A single term slug, single term id, or array of either term slugs or ids.
968
	 * @param bool             $append    Optional. If false will delete difference of terms. Default false.
969
	 */
970
	public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
971
		wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
972
	}
973
974
	/**
975
	 * Remove certain term relationships from the specified object.
976
	 *
977
	 * @access public
978
	 *
979
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
980
	 *
981
	 * @param int   $object_id ID of the object.
982
	 * @param array $tt_ids    Term taxonomy IDs.
983
	 * @return bool True on success, false on failure.
984
	 */
985
	public function delete_object_terms( $object_id, $tt_ids ) {
986
		global $wpdb;
987
988
		if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
989
			// Escape.
990
			$tt_ids_sanitized = array_map( 'intval', $tt_ids );
991
992
			$taxonomies = array();
993
			foreach ( $tt_ids_sanitized as $tt_id ) {
994
				$term                            = get_term_by( 'term_taxonomy_id', $tt_id );
995
				$taxonomies[ $term->taxonomy ][] = $tt_id;
996
			}
997
			$in_tt_ids = implode( ', ', $tt_ids_sanitized );
998
999
			/**
1000
			 * Fires immediately before an object-term relationship is deleted.
1001
			 *
1002
			 * @since 2.9.0
1003
			 *
1004
			 * @param int   $object_id Object ID.
1005
			 * @param array $tt_ids    An array of term taxonomy IDs.
1006
			 */
1007
			do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized );
1008
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1009
			$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
1010
			foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
1011
				$this->ensure_taxonomy( $taxonomy );
1012
				wp_cache_delete( $object_id, $taxonomy . '_relationships' );
1013
				/**
1014
				 * Fires immediately after an object-term relationship is deleted.
1015
				 *
1016
				 * @since 2.9.0
1017
				 *
1018
				 * @param int   $object_id Object ID.
1019
				 * @param array $tt_ids    An array of term taxonomy IDs.
1020
				 */
1021
				do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
1022
				wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
1023
			}
1024
1025
			return (bool) $deleted;
1026
		}
1027
1028
		return false;
1029
	}
1030
1031
	/**
1032
	 * Retrieve the number of users.
1033
	 * Not supported in this replicastore.
1034
	 *
1035
	 * @access public
1036
	 */
1037
	public function user_count() {
1038
		// Noop.
1039
	}
1040
1041
	/**
1042
	 * Retrieve a user object by the user ID.
1043
	 *
1044
	 * @access public
1045
	 *
1046
	 * @param int $user_id User ID.
1047
	 * @return \WP_User User object.
1048
	 */
1049
	public function get_user( $user_id ) {
1050
		return \WP_User::get_instance( $user_id );
1051
	}
1052
1053
	/**
1054
	 * Insert or update a user.
1055
	 * Not supported in this replicastore.
1056
	 *
1057
	 * @access public
1058
	 * @throws \Exception If this method is invoked.
1059
	 *
1060
	 * @param \WP_User $user User object.
1061
	 */
1062
	public function upsert_user( $user ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1063
		$this->invalid_call();
1064
	}
1065
1066
	/**
1067
	 * Delete a user.
1068
	 * Not supported in this replicastore.
1069
	 *
1070
	 * @access public
1071
	 * @throws \Exception If this method is invoked.
1072
	 *
1073
	 * @param int $user_id User ID.
1074
	 */
1075
	public function delete_user( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1076
		$this->invalid_call();
1077
	}
1078
1079
	/**
1080
	 * Update/insert user locale.
1081
	 * Not supported in this replicastore.
1082
	 *
1083
	 * @access public
1084
	 * @throws \Exception If this method is invoked.
1085
	 *
1086
	 * @param int    $user_id User ID.
1087
	 * @param string $local   The user locale.
1088
	 */
1089
	public function upsert_user_locale( $user_id, $local ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1090
		$this->invalid_call();
1091
	}
1092
1093
	/**
1094
	 * Delete user locale.
1095
	 * Not supported in this replicastore.
1096
	 *
1097
	 * @access public
1098
	 * @throws \Exception If this method is invoked.
1099
	 *
1100
	 * @param int $user_id User ID.
1101
	 */
1102
	public function delete_user_locale( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1103
		$this->invalid_call();
1104
	}
1105
1106
	/**
1107
	 * Retrieve the user locale.
1108
	 *
1109
	 * @access public
1110
	 *
1111
	 * @param int $user_id User ID.
1112
	 * @return string The user locale.
1113
	 */
1114
	public function get_user_locale( $user_id ) {
1115
		return get_user_locale( $user_id );
1116
	}
1117
1118
	/**
1119
	 * Retrieve the allowed mime types for the user.
1120
	 * Not supported in this replicastore.
1121
	 *
1122
	 * @access public
1123
	 *
1124
	 * @param int $user_id User ID.
1125
	 */
1126
	public function get_allowed_mime_types( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1127
		// Noop.
1128
	}
1129
1130
	/**
1131
	 * Retrieve all the checksums we are interested in.
1132
	 * Currently that is posts, comments, post meta and comment meta.
1133
	 *
1134
	 * @access public
1135
	 *
1136
	 * @return array Checksums.
1137
	 */
1138
	public function checksum_all() {
1139
		$post_meta_checksum    = $this->checksum_histogram( 'post_meta', 1 );
1140
		$comment_meta_checksum = $this->checksum_histogram( 'comment_meta', 1 );
1141
1142
		return array(
1143
			'posts'        => $this->posts_checksum(),
1144
			'comments'     => $this->comments_checksum(),
1145
			'post_meta'    => reset( $post_meta_checksum ),
1146
			'comment_meta' => reset( $comment_meta_checksum ),
1147
		);
1148
	}
1149
1150
	/**
1151
	 * Retrieve the columns that are needed to calculate a checksum for an object type.
1152
	 *
1153
	 * @access public
1154
	 *
1155
	 * @todo Refactor to not use interpolated values and prepare the SQL query.
1156
	 *
1157
	 * @param string $object_type Object type.
1158
	 * @return array|bool Columns, or false if invalid object type is specified.
1159
	 */
1160
	public function get_checksum_columns_for_object_type( $object_type ) {
1161
		switch ( $object_type ) {
1162
			case 'posts':
1163
				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...
1164
			case 'post_meta':
1165
				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...
1166
			case 'comments':
1167
				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...
1168
			case 'comment_meta':
1169
				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...
1170
			case 'terms':
1171
				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...
1172
			default:
1173
				return false;
1174
		}
1175
	}
1176
1177
	/**
1178
	 * Retrieve the checksum histogram for a specific object type.
1179
	 *
1180
	 * @access public
1181
	 *
1182
	 * @todo Refactor to not use interpolated values and properly prepare the SQL query.
1183
	 *
1184
	 * @param string $object_type     Object type.
1185
	 * @param int    $buckets         Number of buckets to split the objects to.
1186
	 * @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...
1187
	 * @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...
1188
	 * @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...
1189
	 * @param bool   $strip_non_ascii Whether to strip non-ASCII characters.
1190
	 * @param string $salt            Salt, used for $wpdb->prepare()'s args.
1191
	 * @return array The checksum histogram.
1192
	 */
1193
	public function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '' ) {
1194
		global $wpdb;
1195
1196
		$wpdb->queries = array();
1197
1198
		if ( empty( $columns ) ) {
1199
			$columns = $this->get_checksum_columns_for_object_type( $object_type );
1200
		}
1201
1202
		switch ( $object_type ) {
1203
			case 'posts':
1204
				$object_count = $this->post_count( null, $start_id, $end_id );
1205
				$object_table = $wpdb->posts;
1206
				$id_field     = 'ID';
1207
				$where_sql    = Settings::get_blacklisted_post_types_sql();
1208
				break;
1209
			case 'post_meta':
1210
				$object_table = $wpdb->postmeta;
1211
				$where_sql    = Settings::get_whitelisted_post_meta_sql();
1212
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
1213
				$id_field     = 'meta_id';
1214
				break;
1215
			case 'comments':
1216
				$object_count = $this->comment_count( null, $start_id, $end_id );
1217
				$object_table = $wpdb->comments;
1218
				$id_field     = 'comment_ID';
1219
				$where_sql    = Settings::get_comments_filter_sql();
1220
				break;
1221
			case 'comment_meta':
1222
				$object_table = $wpdb->commentmeta;
1223
				$where_sql    = Settings::get_whitelisted_comment_meta_sql();
1224
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
1225
				$id_field     = 'meta_id';
1226
				break;
1227
			case 'terms':
1228
				$object_table = $wpdb->terms;
1229
				$object_count = $this->term_count();
1230
				$id_field     = 'term_id';
1231
				$where_sql    = '1=1';
1232
				break;
1233
			default:
1234
				return false;
1235
		}
1236
1237
		$bucket_size     = intval( ceil( $object_count / $buckets ) );
1238
		$previous_max_id = 0;
1239
		$histogram       = array();
1240
1241
		$where = '1=1';
1242
1243
		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...
1244
			$where .= " AND $id_field >= " . intval( $start_id );
1245
		}
1246
1247
		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...
1248
			$where .= " AND $id_field <= " . intval( $end_id );
1249
		}
1250
1251
		do {
1252
			list( $first_id, $last_id ) = $wpdb->get_row(
1253
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1254
				"SELECT MIN($id_field) as min_id, MAX($id_field) as max_id FROM ( SELECT $id_field FROM $object_table WHERE $where AND $id_field > $previous_max_id ORDER BY $id_field ASC LIMIT $bucket_size ) as ids",
1255
				ARRAY_N
1256
			);
1257
1258
			if ( null === $first_id || null === $last_id ) {
1259
				// Nothing to checksum here...
1260
				break;
1261
			}
1262
1263
			// Get the checksum value.
1264
			$value = $this->table_checksum( $object_table, $columns, $id_field, $where_sql, $first_id, $last_id, $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...
1265
1266
			if ( is_wp_error( $value ) ) {
1267
				return $value;
1268
			}
1269
1270
			if ( null === $first_id || null === $last_id ) {
1271
				break;
1272
			} elseif ( $first_id === $last_id ) {
1273
				$histogram[ $first_id ] = $value;
1274
			} else {
1275
				$histogram[ "{$first_id}-{$last_id}" ] = $value;
1276
			}
1277
1278
			$previous_max_id = $last_id;
1279
		} while ( true );
1280
1281
		return $histogram;
1282
	}
1283
1284
	/**
1285
	 * Retrieve the checksum for a specific database table.
1286
	 *
1287
	 * @access private
1288
	 *
1289
	 * @todo Refactor to properly prepare the SQL query.
1290
	 *
1291
	 * @param string $table           Table name.
1292
	 * @param array  $columns         Table columns to calculate the checksum from.
1293
	 * @param int    $id_column       Name of the unique ID column.
1294
	 * @param string $where_sql       Additional WHERE clause SQL.
1295
	 * @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...
1296
	 * @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...
1297
	 * @param bool   $strip_non_ascii Whether to strip non-ASCII characters.
1298
	 * @param string $salt            Salt, used for $wpdb->prepare()'s args.
1299
	 * @return int|\WP_Error The table histogram, or \WP_Error on failure.
1300
	 */
1301
	private function table_checksum( $table, $columns, $id_column, $where_sql = '1=1', $min_id = null, $max_id = null, $strip_non_ascii = true, $salt = '' ) {
1302
		global $wpdb;
1303
1304
		// Sanitize to just valid MySQL column names.
1305
		$sanitized_columns = preg_grep( '/^[0-9,a-z,A-Z$_]+$/i', $columns );
1306
1307
		if ( $strip_non_ascii ) {
1308
			$columns_sql = implode( ',', array_map( array( $this, 'strip_non_ascii_sql' ), $sanitized_columns ) );
1309
		} else {
1310
			$columns_sql = implode( ',', $sanitized_columns );
1311
		}
1312
1313
		if ( null !== $min_id && null !== $max_id ) {
1314
			if ( $min_id === $max_id ) {
1315
				$min_id     = intval( $min_id );
1316
				$where_sql .= " AND $id_column = $min_id LIMIT 1";
1317
			} else {
1318
				$min_id     = intval( $min_id );
1319
				$max_id     = intval( $max_id );
1320
				$size       = $max_id - $min_id;
1321
				$where_sql .= " AND $id_column >= $min_id AND $id_column <= $max_id LIMIT $size";
1322
			}
1323
		} else {
1324
			if ( null !== $min_id ) {
1325
				$min_id     = intval( $min_id );
1326
				$where_sql .= " AND $id_column >= $min_id";
1327
			}
1328
1329
			if ( null !== $max_id ) {
1330
				$max_id     = intval( $max_id );
1331
				$where_sql .= " AND $id_column <= $max_id";
1332
			}
1333
		}
1334
1335
		$query = <<<ENDSQL
1336
			SELECT CAST( SUM( CRC32( CONCAT_WS( '#', '%s', {$columns_sql} ) ) ) AS UNSIGNED INT )
1337
			FROM $table
1338
			WHERE $where_sql;
1339
ENDSQL;
1340
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1341
		$result = $wpdb->get_var( $wpdb->prepare( $query, $salt ) );
1342
		if ( $wpdb->last_error ) {
1343
			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...
1344
		}
1345
1346
		return $result;
1347
	}
1348
1349
	/**
1350
	 * Retrieve the type of the checksum.
1351
	 *
1352
	 * @access public
1353
	 *
1354
	 * @return string Type of the checksum.
1355
	 */
1356
	public function get_checksum_type() {
1357
		return 'sum';
1358
	}
1359
1360
	/**
1361
	 * Count the meta values in a table, within a specified range.
1362
	 *
1363
	 * @access private
1364
	 *
1365
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
1366
	 *
1367
	 * @param string $table     Table name.
1368
	 * @param string $where_sql Additional WHERE SQL.
1369
	 * @param int    $min_id    Minimum meta ID.
1370
	 * @param int    $max_id    Maximum meta ID.
1371
	 * @return int Number of meta values.
1372
	 */
1373
	private function meta_count( $table, $where_sql, $min_id, $max_id ) {
1374
		global $wpdb;
1375
1376
		if ( null !== $min_id ) {
1377
			$where_sql .= ' AND meta_id >= ' . intval( $min_id );
1378
		}
1379
1380
		if ( null !== $max_id ) {
1381
			$where_sql .= ' AND meta_id <= ' . intval( $max_id );
1382
		}
1383
1384
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1385
		return $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE $where_sql" );
1386
	}
1387
1388
	/**
1389
	 * Wraps a column name in SQL which strips non-ASCII chars.
1390
	 * This helps normalize data to avoid checksum differences caused by
1391
	 * badly encoded data in the DB.
1392
	 *
1393
	 * @param string $column_name Name of the column.
1394
	 * @return string Column name, without the non-ASCII chars.
1395
	 */
1396
	public function strip_non_ascii_sql( $column_name ) {
1397
		return "REPLACE( CONVERT( $column_name USING ascii ), '?', '' )";
1398
	}
1399
1400
	/**
1401
	 * Used in methods that are not implemented and shouldn't be invoked.
1402
	 *
1403
	 * @access private
1404
	 * @throws \Exception If this method is invoked.
1405
	 */
1406
	private function invalid_call() {
1407
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
1408
		$backtrace = debug_backtrace();
1409
		$caller    = $backtrace[1]['function'];
1410
		throw new \Exception( "This function $caller is not supported on the WP Replicastore" );
1411
	}
1412
}
1413