Completed
Push — sync/delete_batch_metadata ( 054e5f )
by
unknown
275:35 queued 267:17
created

Replicastore::full_sync_start()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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