Completed
Push — dependabot/npm_and_yarn/ini-1.... ( bb632f...6a445d )
by Yaroslav
100:51 queued 92:02
created

Replicastore   F

Complexity

Total Complexity 132

Size/Duplication

Total Lines 1359
Duplicated Lines 3.24 %

Coupling/Cohesion

Components 3
Dependencies 2

Importance

Changes 0
Metric Value
dl 44
loc 1359
rs 0.8
c 0
b 0
f 0
wmc 132
lcom 3
cbo 2

66 Methods

Rating   Name   Duplication   Size   Complexity  
A get_min_max_object_id() 0 19 4
B delete_object_terms() 0 45 5
A user_count() 0 3 1
A get_user() 0 3 1
A upsert_user() 0 3 1
A delete_user() 0 3 1
A upsert_user_locale() 0 3 1
A delete_user_locale() 0 3 1
A get_user_locale() 0 3 1
A get_allowed_mime_types() 0 3 1
A update_object_terms() 0 4 1
A comment_count() 22 22 4
A update_option() 0 3 1
A get_option() 0 3 1
A delete_option() 0 3 1
B comment_status_to_approval_value() 0 20 10
A get_comments() 0 12 2
A get_comment() 0 3 1
A upsert_comment() 0 47 4
A trash_comment() 0 3 1
A delete_comment() 0 3 1
A spam_comment() 0 3 1
A trashed_post_comments() 0 3 1
A untrashed_post_comments() 0 3 1
A set_theme_info() 0 3 1
A current_theme_supports() 0 3 1
A get_metadata() 0 3 1
A upsert_metadata() 0 42 3
A delete_metadata() 0 18 4
A delete_batch_metadata() 0 16 3
A get_constant() 0 9 2
A set_constant() 0 3 1
A get_updates() 0 9 2
A set_updates() 0 5 1
A get_callable() 0 9 2
A set_callable() 0 3 1
A get_site_option() 0 3 1
A update_site_option() 0 3 1
A delete_site_option() 0 3 1
A get_terms() 0 7 3
A get_term() 0 14 4
A ensure_taxonomy() 0 19 3
A get_the_terms() 0 3 1
A update_term() 0 33 2
A delete_term() 0 4 1
A checksum_all() 0 19 1
A reset() 0 27 3
A full_sync_start() 0 3 1
A full_sync_end() 0 3 1
A term_count() 0 4 1
A term_taxonomy_count() 0 4 1
A term_relationship_count() 0 4 1
A post_count() 22 22 4
A get_posts() 0 14 2
A get_post() 0 3 1
B upsert_post() 0 59 4
A delete_post() 0 3 1
A posts_checksum() 0 3 1
A post_meta_checksum() 0 3 1
A comments_checksum() 0 3 1
A comment_meta_checksum() 0 3 1
A summarize_checksum_histogram() 0 7 2
A get_checksum_type() 0 3 1
A invalid_call() 0 6 1
A calculate_buckets() 0 25 5
C checksum_histogram() 0 60 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Replicastore often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Replicastore, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Sync replicastore.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync;
9
10
use Automattic\Jetpack\Sync\Replicastore\Table_Checksum;
11
use Exception;
12
use WP_Error;
13
14
/**
15
 * An implementation of Replicastore Interface which returns data stored in a WordPress.org DB.
16
 * This is useful to compare values in the local WP DB to values in the synced replica store
17
 */
18
class Replicastore implements Replicastore_Interface {
19
	/**
20
	 * Empty and reset the replicastore.
21
	 *
22
	 * @access public
23
	 */
24
	public function reset() {
25
		global $wpdb;
26
27
		$wpdb->query( "DELETE FROM $wpdb->posts" );
28
29
		// Delete comments from cache.
30
		$comment_ids = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments" );
31
		if ( ! empty( $comment_ids ) ) {
32
			clean_comment_cache( $comment_ids );
33
		}
34
		$wpdb->query( "DELETE FROM $wpdb->comments" );
35
36
		// Also need to delete terms from cache.
37
		$term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" );
38
		foreach ( $term_ids as $term_id ) {
39
			wp_cache_delete( $term_id, 'terms' );
40
		}
41
42
		$wpdb->query( "DELETE FROM $wpdb->terms" );
43
44
		$wpdb->query( "DELETE FROM $wpdb->term_taxonomy" );
45
		$wpdb->query( "DELETE FROM $wpdb->term_relationships" );
46
47
		// Callables and constants.
48
		$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" );
49
		$wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" );
50
	}
51
52
	/**
53
	 * Ran when full sync has just started.
54
	 *
55
	 * @access public
56
	 *
57
	 * @param array $config Full sync configuration for this sync module.
58
	 */
59
	public function full_sync_start( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
60
		$this->reset();
61
	}
62
63
	/**
64
	 * Ran when full sync has just finished.
65
	 *
66
	 * @access public
67
	 *
68
	 * @param string $checksum Deprecated since 7.3.0.
69
	 */
70
	public function full_sync_end( $checksum ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
71
		// Noop right now.
72
	}
73
74
	/**
75
	 * Retrieve the number of terms.
76
	 *
77
	 * @access public
78
	 *
79
	 * @return int Number of terms.
80
	 */
81
	public function term_count() {
82
		global $wpdb;
83
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->terms" );
84
	}
85
86
	/**
87
	 * Retrieve the number of rows in the `term_taxonomy` table.
88
	 *
89
	 * @access public
90
	 *
91
	 * @return int Number of terms.
92
	 */
93
	public function term_taxonomy_count() {
94
		global $wpdb;
95
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_taxonomy" );
96
	}
97
98
	/**
99
	 * Retrieve the number of term relationships.
100
	 *
101
	 * @access public
102
	 *
103
	 * @return int Number of rows in the term relationships table.
104
	 */
105
	public function term_relationship_count() {
106
		global $wpdb;
107
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_relationships" );
108
	}
109
110
	/**
111
	 * Retrieve the number of posts with a particular post status within a certain range.
112
	 *
113
	 * @access public
114
	 *
115
	 * @todo Prepare the SQL query before executing it.
116
	 *
117
	 * @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...
118
	 * @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...
119
	 * @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...
120
	 * @return int Number of posts.
121
	 */
122 View Code Duplication
	public function post_count( $status = null, $min_id = null, $max_id = null ) {
123
		global $wpdb;
124
125
		$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...
126
127
		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...
128
			$where = "post_status = '" . esc_sql( $status ) . "'";
129
		} else {
130
			$where = '1=1';
131
		}
132
133
		if ( ! empty( $min_id ) ) {
134
			$where .= ' AND ID >= ' . (int) $min_id;
135
		}
136
137
		if ( ! empty( $max_id ) ) {
138
			$where .= ' AND ID <= ' . (int) $max_id;
139
		}
140
141
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
142
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
143
	}
144
145
	/**
146
	 * Retrieve the posts with a particular post status.
147
	 *
148
	 * @access public
149
	 *
150
	 * @todo Implement range and actually use max_id/min_id arguments.
151
	 *
152
	 * @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...
153
	 * @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...
154
	 * @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...
155
	 * @return array Array of posts.
156
	 */
157
	public function get_posts( $status = null, $min_id = null, $max_id = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
158
		$args = array(
159
			'orderby'        => 'ID',
160
			'posts_per_page' => -1,
161
		);
162
163
		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...
164
			$args['post_status'] = $status;
165
		} else {
166
			$args['post_status'] = 'any';
167
		}
168
169
		return get_posts( $args );
170
	}
171
172
	/**
173
	 * Retrieve a post object by the post ID.
174
	 *
175
	 * @access public
176
	 *
177
	 * @param int $id Post ID.
178
	 * @return \WP_Post Post object.
179
	 */
180
	public function get_post( $id ) {
181
		return get_post( $id );
182
	}
183
184
	/**
185
	 * Update or insert a post.
186
	 *
187
	 * @access public
188
	 *
189
	 * @param \WP_Post $post   Post object.
190
	 * @param bool     $silent Whether to perform a silent action. Not used in this implementation.
191
	 */
192
	public function upsert_post( $post, $silent = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
193
		global $wpdb;
194
195
		// Reject the post if it's not a \WP_Post.
196
		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...
197
			return;
198
		}
199
200
		$post = $post->to_array();
201
202
		// Reject posts without an ID.
203
		if ( ! isset( $post['ID'] ) ) {
204
			return;
205
		}
206
207
		$now     = current_time( 'mysql' );
208
		$now_gmt = get_gmt_from_date( $now );
209
210
		$defaults = array(
211
			'ID'                    => 0,
212
			'post_author'           => '0',
213
			'post_content'          => '',
214
			'post_content_filtered' => '',
215
			'post_title'            => '',
216
			'post_name'             => '',
217
			'post_excerpt'          => '',
218
			'post_status'           => 'draft',
219
			'post_type'             => 'post',
220
			'comment_status'        => 'closed',
221
			'comment_count'         => '0',
222
			'ping_status'           => '',
223
			'post_password'         => '',
224
			'to_ping'               => '',
225
			'pinged'                => '',
226
			'post_parent'           => 0,
227
			'menu_order'            => 0,
228
			'guid'                  => '',
229
			'post_date'             => $now,
230
			'post_date_gmt'         => $now_gmt,
231
			'post_modified'         => $now,
232
			'post_modified_gmt'     => $now_gmt,
233
		);
234
235
		$post = array_intersect_key( $post, $defaults );
236
237
		$post = sanitize_post( $post, 'db' );
238
239
		unset( $post['filter'] );
240
241
		$exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
242
243
		if ( $exists ) {
244
			$wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
245
		} else {
246
			$wpdb->insert( $wpdb->posts, $post );
247
		}
248
249
		clean_post_cache( $post['ID'] );
250
	}
251
252
	/**
253
	 * Delete a post by the post ID.
254
	 *
255
	 * @access public
256
	 *
257
	 * @param int $post_id Post ID.
258
	 */
259
	public function delete_post( $post_id ) {
260
		wp_delete_post( $post_id, true );
261
	}
262
263
	/**
264
	 * Retrieve the checksum for posts within a range.
265
	 *
266
	 * @access public
267
	 *
268
	 * @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...
269
	 * @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...
270
	 * @return int The checksum.
271
	 */
272
	public function posts_checksum( $min_id = null, $max_id = null ) {
273
		return $this->summarize_checksum_histogram( $this->checksum_histogram( 'posts', null, $min_id, $max_id ) );
0 ignored issues
show
Bug introduced by
It seems like $this->checksum_histogra...null, $min_id, $max_id) targeting Automattic\Jetpack\Sync\...e::checksum_histogram() can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $min_id defined by parameter $min_id on line 272 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $max_id defined by parameter $max_id on line 272 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
274
	}
275
276
	/**
277
	 * Retrieve the checksum for post meta within a range.
278
	 *
279
	 * @access public
280
	 *
281
	 * @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...
282
	 * @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...
283
	 * @return int The checksum.
284
	 */
285
	public function post_meta_checksum( $min_id = null, $max_id = null ) {
286
		return $this->summarize_checksum_histogram( $this->checksum_histogram( 'postmeta', null, $min_id, $max_id ) );
0 ignored issues
show
Bug introduced by
It seems like $this->checksum_histogra...null, $min_id, $max_id) targeting Automattic\Jetpack\Sync\...e::checksum_histogram() can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $min_id defined by parameter $min_id on line 285 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $max_id defined by parameter $max_id on line 285 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
287
	}
288
289
	/**
290
	 * Retrieve the number of comments with a particular comment status within a certain range.
291
	 *
292
	 * @access public
293
	 *
294
	 * @todo Prepare the SQL query before executing it.
295
	 *
296
	 * @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...
297
	 * @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...
298
	 * @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...
299
	 * @return int Number of comments.
300
	 */
301 View Code Duplication
	public function comment_count( $status = null, $min_id = null, $max_id = null ) {
302
		global $wpdb;
303
304
		$comment_approved = $this->comment_status_to_approval_value( $status );
305
306
		if ( false !== $comment_approved ) {
307
			$where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
308
		} else {
309
			$where = '1=1';
310
		}
311
312
		if ( ! empty( $min_id ) ) {
313
			$where .= ' AND comment_ID >= ' . (int) $min_id;
314
		}
315
316
		if ( ! empty( $max_id ) ) {
317
			$where .= ' AND comment_ID <= ' . (int) $max_id;
318
		}
319
320
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
321
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
322
	}
323
324
	/**
325
	 * Translate a comment status to a value of the comment_approved field.
326
	 *
327
	 * @access protected
328
	 *
329
	 * @param string $status Comment status.
330
	 * @return string|bool New comment_approved value, false if the status doesn't affect it.
331
	 */
332
	protected function comment_status_to_approval_value( $status ) {
333
		switch ( (string) $status ) {
334
			case 'approve':
335
			case '1':
336
				return '1';
337
			case 'hold':
338
			case '0':
339
				return '0';
340
			case 'spam':
341
				return 'spam';
342
			case 'trash':
343
				return 'trash';
344
			case 'post-trashed':
345
				return 'post-trashed';
346
			case 'any':
347
			case 'all':
348
			default:
349
				return false;
350
		}
351
	}
352
353
	/**
354
	 * Retrieve the comments with a particular comment status.
355
	 *
356
	 * @access public
357
	 *
358
	 * @todo Implement range and actually use max_id/min_id arguments.
359
	 *
360
	 * @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...
361
	 * @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...
362
	 * @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...
363
	 * @return array Array of comments.
364
	 */
365
	public function get_comments( $status = null, $min_id = null, $max_id = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
366
		$args = array(
367
			'orderby' => 'ID',
368
			'status'  => 'all',
369
		);
370
371
		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...
372
			$args['status'] = $status;
373
		}
374
375
		return get_comments( $args );
376
	}
377
378
	/**
379
	 * Retrieve a comment object by the comment ID.
380
	 *
381
	 * @access public
382
	 *
383
	 * @param int $id Comment ID.
384
	 * @return \WP_Comment Comment object.
385
	 */
386
	public function get_comment( $id ) {
387
		return \WP_Comment::get_instance( $id );
388
	}
389
390
	/**
391
	 * Update or insert a comment.
392
	 *
393
	 * @access public
394
	 *
395
	 * @param \WP_Comment $comment Comment object.
396
	 */
397
	public function upsert_comment( $comment ) {
398
		global $wpdb;
399
400
		$comment = $comment->to_array();
401
402
		// Filter by fields on comment table.
403
		$comment_fields_whitelist = array(
404
			'comment_ID',
405
			'comment_post_ID',
406
			'comment_author',
407
			'comment_author_email',
408
			'comment_author_url',
409
			'comment_author_IP',
410
			'comment_date',
411
			'comment_date_gmt',
412
			'comment_content',
413
			'comment_karma',
414
			'comment_approved',
415
			'comment_agent',
416
			'comment_type',
417
			'comment_parent',
418
			'user_id',
419
		);
420
421
		foreach ( $comment as $key => $value ) {
422
			if ( ! in_array( $key, $comment_fields_whitelist, true ) ) {
423
				unset( $comment[ $key ] );
424
			}
425
		}
426
427
		$exists = $wpdb->get_var(
428
			$wpdb->prepare(
429
				"SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
430
				$comment['comment_ID']
431
			)
432
		);
433
434
		if ( $exists ) {
435
			$wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
436
		} else {
437
			$wpdb->insert( $wpdb->comments, $comment );
438
		}
439
		// Remove comment from cache.
440
		clean_comment_cache( $comment['comment_ID'] );
441
442
		wp_update_comment_count( $comment['comment_post_ID'] );
443
	}
444
445
	/**
446
	 * Trash a comment by the comment ID.
447
	 *
448
	 * @access public
449
	 *
450
	 * @param int $comment_id Comment ID.
451
	 */
452
	public function trash_comment( $comment_id ) {
453
		wp_delete_comment( $comment_id );
454
	}
455
456
	/**
457
	 * Delete a comment by the comment ID.
458
	 *
459
	 * @access public
460
	 *
461
	 * @param int $comment_id Comment ID.
462
	 */
463
	public function delete_comment( $comment_id ) {
464
		wp_delete_comment( $comment_id, true );
465
	}
466
467
	/**
468
	 * Mark a comment by the comment ID as spam.
469
	 *
470
	 * @access public
471
	 *
472
	 * @param int $comment_id Comment ID.
473
	 */
474
	public function spam_comment( $comment_id ) {
475
		wp_spam_comment( $comment_id );
476
	}
477
478
	/**
479
	 * Trash the comments of a post.
480
	 *
481
	 * @access public
482
	 *
483
	 * @param int   $post_id  Post ID.
484
	 * @param array $statuses Post statuses. Not used in this implementation.
485
	 */
486
	public function trashed_post_comments( $post_id, $statuses ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
487
		wp_trash_post_comments( $post_id );
488
	}
489
490
	/**
491
	 * Untrash the comments of a post.
492
	 *
493
	 * @access public
494
	 *
495
	 * @param int $post_id Post ID.
496
	 */
497
	public function untrashed_post_comments( $post_id ) {
498
		wp_untrash_post_comments( $post_id );
499
	}
500
501
	/**
502
	 * Retrieve the checksum for comments within a range.
503
	 *
504
	 * @access public
505
	 *
506
	 * @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...
507
	 * @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...
508
	 * @return int The checksum.
509
	 */
510
	public function comments_checksum( $min_id = null, $max_id = null ) {
511
		return $this->summarize_checksum_histogram( $this->checksum_histogram( 'comments', null, $min_id, $max_id ) );
0 ignored issues
show
Bug introduced by
It seems like $this->checksum_histogra...null, $min_id, $max_id) targeting Automattic\Jetpack\Sync\...e::checksum_histogram() can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $min_id defined by parameter $min_id on line 510 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $max_id defined by parameter $max_id on line 510 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
512
	}
513
514
	/**
515
	 * Retrieve the checksum for comment meta within a range.
516
	 *
517
	 * @access public
518
	 *
519
	 * @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...
520
	 * @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...
521
	 * @return int The checksum.
522
	 */
523
	public function comment_meta_checksum( $min_id = null, $max_id = null ) {
524
		return $this->summarize_checksum_histogram( $this->checksum_histogram( 'commentmeta', null, $min_id, $max_id ) );
0 ignored issues
show
Bug introduced by
It seems like $this->checksum_histogra...null, $min_id, $max_id) targeting Automattic\Jetpack\Sync\...e::checksum_histogram() can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $min_id defined by parameter $min_id on line 523 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $max_id defined by parameter $max_id on line 523 can also be of type integer; however, Automattic\Jetpack\Sync\...e::checksum_histogram() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
525
	}
526
527
	/**
528
	 * Update the value of an option.
529
	 *
530
	 * @access public
531
	 *
532
	 * @param string $option Option name.
533
	 * @param mixed  $value  Option value.
534
	 * @return bool False if value was not updated and true if value was updated.
535
	 */
536
	public function update_option( $option, $value ) {
537
		return update_option( $option, $value );
538
	}
539
540
	/**
541
	 * Retrieve an option value based on an option name.
542
	 *
543
	 * @access public
544
	 *
545
	 * @param string $option  Name of option to retrieve.
546
	 * @param mixed  $default Optional. Default value to return if the option does not exist.
547
	 * @return mixed Value set for the option.
548
	 */
549
	public function get_option( $option, $default = false ) {
550
		return get_option( $option, $default );
551
	}
552
553
	/**
554
	 * Remove an option by name.
555
	 *
556
	 * @access public
557
	 *
558
	 * @param string $option Name of option to remove.
559
	 * @return bool True, if option is successfully deleted. False on failure.
560
	 */
561
	public function delete_option( $option ) {
562
		return delete_option( $option );
563
	}
564
565
	/**
566
	 * Change the info of the current theme.
567
	 *
568
	 * @access public
569
	 *
570
	 * @param array $theme_info Theme info array.
571
	 */
572
	public function set_theme_info( $theme_info ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
573
		// Noop.
574
	}
575
576
	/**
577
	 * Whether the current theme supports a certain feature.
578
	 *
579
	 * @access public
580
	 *
581
	 * @param string $feature Name of the feature.
582
	 */
583
	public function current_theme_supports( $feature ) {
584
		return current_theme_supports( $feature );
585
	}
586
587
	/**
588
	 * Retrieve metadata for the specified object.
589
	 *
590
	 * @access public
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 bool   $single     If true, return only the first value of the specified meta_key.
596
	 *
597
	 * @return mixed Single metadata value, or array of values.
598
	 */
599
	public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
600
		return get_metadata( $type, $object_id, $meta_key, $single );
601
	}
602
603
	/**
604
	 * Stores remote meta key/values alongside an ID mapping key.
605
	 *
606
	 * @access public
607
	 *
608
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
609
	 *
610
	 * @param string $type       Meta type.
611
	 * @param int    $object_id  ID of the object.
612
	 * @param string $meta_key   Meta key.
613
	 * @param mixed  $meta_value Meta value.
614
	 * @param int    $meta_id    ID of the meta.
615
	 *
616
	 * @return bool False if meta table does not exist, true otherwise.
617
	 */
618
	public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
619
		$table = _get_meta_table( $type );
620
		if ( ! $table ) {
621
			return false;
622
		}
623
624
		global $wpdb;
625
626
		$exists = $wpdb->get_var(
627
			$wpdb->prepare(
628
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
629
				"SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
630
				$meta_id
631
			)
632
		);
633
634
		if ( $exists ) {
635
			$wpdb->update(
636
				$table,
637
				array(
638
					'meta_key'   => $meta_key,
639
					'meta_value' => maybe_serialize( $meta_value ),
640
				),
641
				array( 'meta_id' => $meta_id )
642
			);
643
		} else {
644
			$object_id_field = $type . '_id';
645
			$wpdb->insert(
646
				$table,
647
				array(
648
					'meta_id'        => $meta_id,
649
					$object_id_field => $object_id,
650
					'meta_key'       => $meta_key,
651
					'meta_value'     => maybe_serialize( $meta_value ),
652
				)
653
			);
654
		}
655
656
		wp_cache_delete( $object_id, $type . '_meta' );
657
658
		return true;
659
	}
660
661
	/**
662
	 * Delete metadata for the specified object.
663
	 *
664
	 * @access public
665
	 *
666
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
667
	 *
668
	 * @param string $type      Meta type.
669
	 * @param int    $object_id ID of the object.
670
	 * @param array  $meta_ids  IDs of the meta objects to delete.
671
	 */
672
	public function delete_metadata( $type, $object_id, $meta_ids ) {
673
		global $wpdb;
674
675
		$table = _get_meta_table( $type );
676
		if ( ! $table ) {
677
			return false;
678
		}
679
680
		foreach ( $meta_ids as $meta_id ) {
681
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
682
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
683
		}
684
685
		// If we don't have an object ID what do we do - invalidate ALL meta?
686
		if ( $object_id ) {
687
			wp_cache_delete( $object_id, $type . '_meta' );
688
		}
689
	}
690
691
	/**
692
	 * Delete metadata with a certain key for the specified objects.
693
	 *
694
	 * @access public
695
	 *
696
	 * @todo Test this out to make sure it works as expected.
697
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
698
	 *
699
	 * @param string $type       Meta type.
700
	 * @param array  $object_ids IDs of the objects.
701
	 * @param string $meta_key   Meta key.
702
	 */
703
	public function delete_batch_metadata( $type, $object_ids, $meta_key ) {
704
		global $wpdb;
705
706
		$table = _get_meta_table( $type );
707
		if ( ! $table ) {
708
			return false;
709
		}
710
		$column = sanitize_key( $type . '_id' );
711
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
712
		$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE $column IN (%s) && meta_key = %s", implode( ',', $object_ids ), $meta_key ) );
713
714
		// If we don't have an object ID what do we do - invalidate ALL meta?
715
		foreach ( $object_ids as $object_id ) {
716
			wp_cache_delete( $object_id, $type . '_meta' );
717
		}
718
	}
719
720
	/**
721
	 * Retrieve value of a constant based on the constant name.
722
	 *
723
	 * We explicitly return null instead of false if the constant doesn't exist.
724
	 *
725
	 * @access public
726
	 *
727
	 * @param string $constant Name of constant to retrieve.
728
	 * @return mixed Value set for the constant.
729
	 */
730
	public function get_constant( $constant ) {
731
		$value = get_option( 'jetpack_constant_' . $constant );
732
733
		if ( $value ) {
734
			return $value;
735
		}
736
737
		return null;
738
	}
739
740
	/**
741
	 * Set the value of a constant.
742
	 *
743
	 * @access public
744
	 *
745
	 * @param string $constant Name of constant to retrieve.
746
	 * @param mixed  $value    Value set for the constant.
747
	 */
748
	public function set_constant( $constant, $value ) {
749
		update_option( 'jetpack_constant_' . $constant, $value );
750
	}
751
752
	/**
753
	 * Retrieve the number of the available updates of a certain type.
754
	 * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
755
	 *
756
	 * @access public
757
	 *
758
	 * @param string $type Type of updates to retrieve.
759
	 * @return int|null Number of updates available, `null` if type is invalid or missing.
760
	 */
761
	public function get_updates( $type ) {
762
		$all_updates = get_option( 'jetpack_updates', array() );
763
764
		if ( isset( $all_updates[ $type ] ) ) {
765
			return $all_updates[ $type ];
766
		} else {
767
			return null;
768
		}
769
	}
770
771
	/**
772
	 * Set the available updates of a certain type.
773
	 * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
774
	 *
775
	 * @access public
776
	 *
777
	 * @param string $type    Type of updates to set.
778
	 * @param int    $updates Total number of updates.
779
	 */
780
	public function set_updates( $type, $updates ) {
781
		$all_updates          = get_option( 'jetpack_updates', array() );
782
		$all_updates[ $type ] = $updates;
783
		update_option( 'jetpack_updates', $all_updates );
784
	}
785
786
	/**
787
	 * Retrieve a callable value based on its name.
788
	 *
789
	 * @access public
790
	 *
791
	 * @param string $name Name of the callable to retrieve.
792
	 * @return mixed Value of the callable.
793
	 */
794
	public function get_callable( $name ) {
795
		$value = get_option( 'jetpack_' . $name );
796
797
		if ( $value ) {
798
			return $value;
799
		}
800
801
		return null;
802
	}
803
804
	/**
805
	 * Update the value of a callable.
806
	 *
807
	 * @access public
808
	 *
809
	 * @param string $name  Callable name.
810
	 * @param mixed  $value Callable value.
811
	 */
812
	public function set_callable( $name, $value ) {
813
		update_option( 'jetpack_' . $name, $value );
814
	}
815
816
	/**
817
	 * Retrieve a network option value based on a network option name.
818
	 *
819
	 * @access public
820
	 *
821
	 * @param string $option Name of network option to retrieve.
822
	 * @return mixed Value set for the network option.
823
	 */
824
	public function get_site_option( $option ) {
825
		return get_option( 'jetpack_network_' . $option );
826
	}
827
828
	/**
829
	 * Update the value of a network option.
830
	 *
831
	 * @access public
832
	 *
833
	 * @param string $option Network option name.
834
	 * @param mixed  $value  Network option value.
835
	 * @return bool False if value was not updated and true if value was updated.
836
	 */
837
	public function update_site_option( $option, $value ) {
838
		return update_option( 'jetpack_network_' . $option, $value );
839
	}
840
841
	/**
842
	 * Remove a network option by name.
843
	 *
844
	 * @access public
845
	 *
846
	 * @param string $option Name of option to remove.
847
	 * @return bool True, if option is successfully deleted. False on failure.
848
	 */
849
	public function delete_site_option( $option ) {
850
		return delete_option( 'jetpack_network_' . $option );
851
	}
852
853
	/**
854
	 * Retrieve the terms from a particular taxonomy.
855
	 *
856
	 * @access public
857
	 *
858
	 * @param string $taxonomy Taxonomy slug.
859
	 * @return array|\WP_Error Array of terms or WP_Error object on failure.
860
	 */
861
	public function get_terms( $taxonomy ) {
862
		$t = $this->ensure_taxonomy( $taxonomy );
863
		if ( ! $t || is_wp_error( $t ) ) {
864
			return $t;
865
		}
866
		return get_terms( $taxonomy );
867
	}
868
869
	/**
870
	 * Retrieve a particular term.
871
	 *
872
	 * @access public
873
	 *
874
	 * @param string $taxonomy   Taxonomy slug.
875
	 * @param int    $term_id    ID of the term.
876
	 * @param string $term_key   ID Field `term_id` or `term_taxonomy_id`.
877
	 * @return \WP_Term|\WP_Error Term object on success, \WP_Error object on failure.
878
	 */
879
	public function get_term( $taxonomy, $term_id, $term_key = 'term_id' ) {
880
881
		// Full Sync will pass false for the $taxonomy so a check for term_taxonomy_id is needed before ensure_taxonomy.
882
		if ( 'term_taxonomy_id' === $term_key ) {
883
			return get_term_by( 'term_taxonomy_id', $term_id );
884
		}
885
886
		$t = $this->ensure_taxonomy( $taxonomy );
887
		if ( ! $t || is_wp_error( $t ) ) {
888
			return $t;
889
		}
890
891
		return get_term( $term_id, $taxonomy );
892
	}
893
894
	/**
895
	 * Verify a taxonomy is legitimate and register it if necessary.
896
	 *
897
	 * @access private
898
	 *
899
	 * @param string $taxonomy Taxonomy slug.
900
	 * @return bool|void|\WP_Error True if already exists; void if it was registered; \WP_Error on error.
901
	 */
902
	private function ensure_taxonomy( $taxonomy ) {
903
		if ( ! taxonomy_exists( $taxonomy ) ) {
904
			// Try re-registering synced taxonomies.
905
			$taxonomies = $this->get_callable( 'taxonomies' );
906
			if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
907
				// Doesn't exist, or somehow hasn't been synced.
908
				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...
909
			}
910
			$t = $taxonomies[ $taxonomy ];
911
912
			return register_taxonomy(
913
				$taxonomy,
914
				$t->object_type,
915
				(array) $t
916
			);
917
		}
918
919
		return true;
920
	}
921
922
	/**
923
	 * Retrieve all terms from a taxonomy that are related to an object with a particular ID.
924
	 *
925
	 * @access public
926
	 *
927
	 * @param int    $object_id Object ID.
928
	 * @param string $taxonomy  Taxonomy slug.
929
	 * @return array|bool|\WP_Error Array of terms on success, `false` if no terms or post doesn't exist, \WP_Error on failure.
930
	 */
931
	public function get_the_terms( $object_id, $taxonomy ) {
932
		return get_the_terms( $object_id, $taxonomy );
933
	}
934
935
	/**
936
	 * Insert or update a term.
937
	 *
938
	 * @access public
939
	 *
940
	 * @param \WP_Term $term_object Term object.
941
	 * @return array|bool|\WP_Error Array of term_id and term_taxonomy_id if updated, true if inserted, \WP_Error on failure.
942
	 */
943
	public function update_term( $term_object ) {
944
		$taxonomy = $term_object->taxonomy;
945
		global $wpdb;
946
		$exists = $wpdb->get_var(
947
			$wpdb->prepare(
948
				"SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
949
				$term_object->term_id
950
			)
951
		);
952
		if ( ! $exists ) {
953
			$term_object   = sanitize_term( clone $term_object, $taxonomy, 'db' );
954
			$term          = array(
955
				'term_id'    => $term_object->term_id,
956
				'name'       => $term_object->name,
957
				'slug'       => $term_object->slug,
958
				'term_group' => $term_object->term_group,
959
			);
960
			$term_taxonomy = array(
961
				'term_taxonomy_id' => $term_object->term_taxonomy_id,
962
				'term_id'          => $term_object->term_id,
963
				'taxonomy'         => $term_object->taxonomy,
964
				'description'      => $term_object->description,
965
				'parent'           => (int) $term_object->parent,
966
				'count'            => (int) $term_object->count,
967
			);
968
			$wpdb->insert( $wpdb->terms, $term );
969
			$wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
970
971
			return true;
972
		}
973
974
		return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
975
	}
976
977
	/**
978
	 * Delete a term by the term ID and its corresponding taxonomy.
979
	 *
980
	 * @access public
981
	 *
982
	 * @param int    $term_id  Term ID.
983
	 * @param string $taxonomy Taxonomy slug.
984
	 * @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.
985
	 */
986
	public function delete_term( $term_id, $taxonomy ) {
987
		$this->ensure_taxonomy( $taxonomy );
988
		return wp_delete_term( $term_id, $taxonomy );
989
	}
990
991
	/**
992
	 * Add/update terms of a particular taxonomy of an object with the specified ID.
993
	 *
994
	 * @access public
995
	 *
996
	 * @param int              $object_id The object to relate to.
997
	 * @param string           $taxonomy  The context in which to relate the term to the object.
998
	 * @param string|int|array $terms     A single term slug, single term id, or array of either term slugs or ids.
999
	 * @param bool             $append    Optional. If false will delete difference of terms. Default false.
1000
	 */
1001
	public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
1002
		$this->ensure_taxonomy( $taxonomy );
1003
		wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
1004
	}
1005
1006
	/**
1007
	 * Remove certain term relationships from the specified object.
1008
	 *
1009
	 * @access public
1010
	 *
1011
	 * @todo Refactor to not use interpolated values when preparing the SQL query.
1012
	 *
1013
	 * @param int   $object_id ID of the object.
1014
	 * @param array $tt_ids    Term taxonomy IDs.
1015
	 * @return bool True on success, false on failure.
1016
	 */
1017
	public function delete_object_terms( $object_id, $tt_ids ) {
1018
		global $wpdb;
1019
1020
		if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
1021
			// Escape.
1022
			$tt_ids_sanitized = array_map( 'intval', $tt_ids );
1023
1024
			$taxonomies = array();
1025
			foreach ( $tt_ids_sanitized as $tt_id ) {
1026
				$term                            = get_term_by( 'term_taxonomy_id', $tt_id );
1027
				$taxonomies[ $term->taxonomy ][] = $tt_id;
1028
			}
1029
			$in_tt_ids = implode( ', ', $tt_ids_sanitized );
1030
1031
			/**
1032
			 * Fires immediately before an object-term relationship is deleted.
1033
			 *
1034
			 * @since 2.9.0
1035
			 *
1036
			 * @param int   $object_id Object ID.
1037
			 * @param array $tt_ids    An array of term taxonomy IDs.
1038
			 */
1039
			do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized );
1040
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1041
			$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
1042
			foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
1043
				$this->ensure_taxonomy( $taxonomy );
1044
				wp_cache_delete( $object_id, $taxonomy . '_relationships' );
1045
				/**
1046
				 * Fires immediately after an object-term relationship is deleted.
1047
				 *
1048
				 * @since 2.9.0
1049
				 *
1050
				 * @param int   $object_id Object ID.
1051
				 * @param array $tt_ids    An array of term taxonomy IDs.
1052
				 */
1053
				do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
1054
				wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
1055
			}
1056
1057
			return (bool) $deleted;
1058
		}
1059
1060
		return false;
1061
	}
1062
1063
	/**
1064
	 * Retrieve the number of users.
1065
	 * Not supported in this replicastore.
1066
	 *
1067
	 * @access public
1068
	 */
1069
	public function user_count() {
1070
		// Noop.
1071
	}
1072
1073
	/**
1074
	 * Retrieve a user object by the user ID.
1075
	 *
1076
	 * @access public
1077
	 *
1078
	 * @param int $user_id User ID.
1079
	 * @return \WP_User User object.
1080
	 */
1081
	public function get_user( $user_id ) {
1082
		return \WP_User::get_instance( $user_id );
1083
	}
1084
1085
	/**
1086
	 * Insert or update a user.
1087
	 * Not supported in this replicastore.
1088
	 *
1089
	 * @access public
1090
	 * @throws Exception If this method is invoked.
1091
	 *
1092
	 * @param \WP_User $user User object.
1093
	 */
1094
	public function upsert_user( $user ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1095
		$this->invalid_call();
1096
	}
1097
1098
	/**
1099
	 * Delete a user.
1100
	 * Not supported in this replicastore.
1101
	 *
1102
	 * @access public
1103
	 * @throws Exception If this method is invoked.
1104
	 *
1105
	 * @param int $user_id User ID.
1106
	 */
1107
	public function delete_user( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1108
		$this->invalid_call();
1109
	}
1110
1111
	/**
1112
	 * Update/insert user locale.
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
	 * @param string $local   The user locale.
1120
	 */
1121
	public function upsert_user_locale( $user_id, $local ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1122
		$this->invalid_call();
1123
	}
1124
1125
	/**
1126
	 * Delete user locale.
1127
	 * Not supported in this replicastore.
1128
	 *
1129
	 * @access public
1130
	 * @throws Exception If this method is invoked.
1131
	 *
1132
	 * @param int $user_id User ID.
1133
	 */
1134
	public function delete_user_locale( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1135
		$this->invalid_call();
1136
	}
1137
1138
	/**
1139
	 * Retrieve the user locale.
1140
	 *
1141
	 * @access public
1142
	 *
1143
	 * @param int $user_id User ID.
1144
	 * @return string The user locale.
1145
	 */
1146
	public function get_user_locale( $user_id ) {
1147
		return get_user_locale( $user_id );
1148
	}
1149
1150
	/**
1151
	 * Retrieve the allowed mime types for the user.
1152
	 * Not supported in this replicastore.
1153
	 *
1154
	 * @access public
1155
	 *
1156
	 * @param int $user_id User ID.
1157
	 */
1158
	public function get_allowed_mime_types( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1159
		// Noop.
1160
	}
1161
1162
	/**
1163
	 * Retrieve all the checksums we are interested in.
1164
	 * Currently that is posts, comments, post meta and comment meta.
1165
	 *
1166
	 * @access public
1167
	 *
1168
	 * @return array Checksums.
1169
	 */
1170
	public function checksum_all() {
1171
		$post_checksum               = $this->checksum_histogram( 'posts' );
1172
		$comments_checksum           = $this->checksum_histogram( 'comments' );
1173
		$post_meta_checksum          = $this->checksum_histogram( 'postmeta' );
1174
		$comment_meta_checksum       = $this->checksum_histogram( 'commentmeta' );
1175
		$terms_checksum              = $this->checksum_histogram( 'terms' );
1176
		$term_relationships_checksum = $this->checksum_histogram( 'term_relationships' );
1177
		$term_taxonomy_checksum      = $this->checksum_histogram( 'term_taxonomy' );
1178
1179
		return array(
1180
			'posts'              => $this->summarize_checksum_histogram( $post_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $post_checksum defined by $this->checksum_histogram('posts') on line 1171 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1181
			'comments'           => $this->summarize_checksum_histogram( $comments_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $comments_checksum defined by $this->checksum_histogram('comments') on line 1172 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1182
			'post_meta'          => $this->summarize_checksum_histogram( $post_meta_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $post_meta_checksum defined by $this->checksum_histogram('postmeta') on line 1173 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1183
			'comment_meta'       => $this->summarize_checksum_histogram( $comment_meta_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $comment_meta_checksum defined by $this->checksum_histogram('commentmeta') on line 1174 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1184
			'terms'              => $this->summarize_checksum_histogram( $terms_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $terms_checksum defined by $this->checksum_histogram('terms') on line 1175 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1185
			'term_relationships' => $this->summarize_checksum_histogram( $term_relationships_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $term_relationships_checksum defined by $this->checksum_histogram('term_relationships') on line 1176 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1186
			'term_taxonomy'      => $this->summarize_checksum_histogram( $term_taxonomy_checksum ),
0 ignored issues
show
Bug introduced by
It seems like $term_taxonomy_checksum defined by $this->checksum_histogram('term_taxonomy') on line 1177 can also be of type object<WP_Error>; however, Automattic\Jetpack\Sync\...ze_checksum_histogram() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1187
		);
1188
	}
1189
1190
	/**
1191
	 * Return the summarized checksum from buckets or the WP_Error.
1192
	 *
1193
	 * @param array $histogram checksum_histogram result.
1194
	 *
1195
	 * @return int|WP_Error checksum or Error.
1196
	 */
1197
	protected function summarize_checksum_histogram( $histogram ) {
1198
		if ( is_wp_error( $histogram ) ) {
1199
			return $histogram;
1200
		} else {
1201
			return array_sum( $histogram );
1202
		}
1203
	}
1204
1205
	/**
1206
	 * Grabs the minimum and maximum object ids for the given parameters.
1207
	 *
1208
	 * @access public
1209
	 *
1210
	 * @param string $id_field     The id column in the table to query.
1211
	 * @param string $object_table The table to query.
1212
	 * @param string $where        A sql where clause without 'WHERE'.
1213
	 * @param int    $bucket_size  The maximum amount of objects to include in the query.
1214
	 *                             For `term_relationships` table, the bucket size will refer to the amount
1215
	 *                             of distinct object ids. This will likely include more database rows than
1216
	 *                             the bucket size implies.
1217
	 *
1218
	 * @return object An object with min_id and max_id properties.
1219
	 */
1220
	public function get_min_max_object_id( $id_field, $object_table, $where, $bucket_size ) {
1221
		global $wpdb;
1222
1223
		// The term relationship table's unique key is a combination of 2 columns. `DISTINCT` helps us get a more acurate query.
1224
		$distinct_sql = ( $wpdb->term_relationships === $object_table ) ? 'DISTINCT' : '';
1225
		$where_sql    = $where ? "WHERE $where" : '';
1226
1227
		// Since MIN() and MAX() do not work with LIMIT, we'll need to adjust the dataset we query if a limit is present.
1228
		// With a limit present, we'll look at a dataset consisting of object_ids that meet the constructs of the $where clause.
1229
		// Without a limit, we can use the actual table as a dataset.
1230
		$from = $bucket_size ?
1231
			"( SELECT $distinct_sql $id_field FROM $object_table $where_sql ORDER BY $id_field ASC LIMIT $bucket_size ) as ids" :
1232
			"$object_table $where_sql ORDER BY $id_field ASC";
1233
1234
		return $wpdb->get_row(
1235
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1236
			"SELECT MIN($id_field) as min, MAX($id_field) as max FROM $from"
1237
		);
1238
	}
1239
1240
	/**
1241
	 * Retrieve the checksum histogram for a specific object type.
1242
	 *
1243
	 * @access public
1244
	 *
1245
	 * @param string $table              Object type.
1246
	 * @param null   $buckets            Number of buckets to split the objects to.
1247
	 * @param null   $start_id           Minimum object ID.
1248
	 * @param null   $end_id             Maximum object ID.
1249
	 * @param null   $columns            Table columns to calculate the checksum from.
1250
	 * @param bool   $strip_non_ascii    Whether to strip non-ASCII characters.
1251
	 * @param string $salt               Salt, used for $wpdb->prepare()'s args.
1252
	 * @param bool   $only_range_edges   Only return the range edges and not the actual checksums.
1253
	 * @param bool   $detailed_drilldown If the call should return a detailed drilldown for the checksum or only the checksum.
1254
	 *
1255
	 * @return array|WP_Error The checksum histogram.
1256
	 * @throws Exception Throws an exception if data validation fails inside `Table_Checksum` calls.
1257
	 */
1258
	public function checksum_histogram( $table, $buckets = null, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '', $only_range_edges = false, $detailed_drilldown = false ) {
1259
		global $wpdb;
1260
1261
		$wpdb->queries = array();
1262
		try {
1263
			$checksum_table = new Table_Checksum( $table, $salt );
1264
		} catch ( Exception $ex ) {
1265
			return new WP_Error( 'checksum_disabled', $ex->getMessage() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'checksum_disabled'.

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...
1266
		}
1267
1268
		// Validate / Determine Buckets.
1269
		if ( is_null( $buckets ) || $buckets < 1 ) {
1270
			$buckets = $this->calculate_buckets( $table, $start_id, $end_id );
1271
		}
1272
		if ( is_wp_error( $buckets ) ) {
1273
			return $buckets;
1274
		}
1275
1276
		$range_edges = $checksum_table->get_range_edges( $start_id, $end_id );
1277
1278
		if ( $only_range_edges ) {
1279
			return $range_edges;
1280
		}
1281
1282
		$object_count = $range_edges['item_count'];
1283
1284
		$bucket_size     = (int) ceil( $object_count / $buckets );
1285
		$previous_max_id = max( 0, $range_edges['min_range'] );
1286
		$histogram       = array();
1287
1288
		do {
1289
			$ids_range = $checksum_table->get_range_edges( $previous_max_id, null, $bucket_size );
1290
1291
			if ( empty( $ids_range['min_range'] ) || empty( $ids_range['max_range'] ) ) {
1292
				// Nothing to checksum here...
1293
				break;
1294
			}
1295
1296
			// Get the checksum value.
1297
			$batch_checksum = $checksum_table->calculate_checksum( $ids_range['min_range'], $ids_range['max_range'], null, $detailed_drilldown );
1298
1299
			if ( is_wp_error( $batch_checksum ) ) {
1300
				return $batch_checksum;
1301
			}
1302
1303
			if ( $ids_range['min_range'] === $ids_range['max_range'] ) {
1304
				$histogram[ $ids_range['min_range'] ] = $batch_checksum;
1305
			} else {
1306
				$histogram[ "{$ids_range[ 'min_range' ]}-{$ids_range[ 'max_range' ]}" ] = $batch_checksum;
1307
			}
1308
1309
			$previous_max_id = $ids_range['max_range'] + 1;
1310
			// If we've reached the max_range lets bail out.
1311
			if ( $previous_max_id >= $range_edges['max_range'] ) {
1312
				break;
1313
			}
1314
		} while ( true );
1315
1316
		return $histogram;
1317
	}
1318
1319
	/**
1320
	 * Retrieve the type of the checksum.
1321
	 *
1322
	 * @access public
1323
	 *
1324
	 * @return string Type of the checksum.
1325
	 */
1326
	public function get_checksum_type() {
1327
		return 'sum';
1328
	}
1329
1330
	/**
1331
	 * Used in methods that are not implemented and shouldn't be invoked.
1332
	 *
1333
	 * @access private
1334
	 * @throws Exception If this method is invoked.
1335
	 */
1336
	private function invalid_call() {
1337
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
1338
		$backtrace = debug_backtrace();
1339
		$caller    = $backtrace[1]['function'];
1340
		throw new Exception( "This function $caller is not supported on the WP Replicastore" );
1341
	}
1342
1343
	/**
1344
	 * Determine number of buckets to use in full table checksum.
1345
	 *
1346
	 * @param string $table Object Type.
1347
	 * @param int    $start_id Min 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...
1348
	 * @param int    $end_id Max 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...
1349
	 * @return int|WP_Error Number of Buckets to use.
1350
	 */
1351
	private function calculate_buckets( $table, $start_id = null, $end_id = null ) {
1352
		// Get # of objects.
1353
		try {
1354
			$checksum_table = new Table_Checksum( $table );
1355
		} catch ( Exception $ex ) {
1356
			return new WP_Error( 'checksum_disabled', $ex->getMessage() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'checksum_disabled'.

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...
1357
		}
1358
		$range_edges  = $checksum_table->get_range_edges( $start_id, $end_id );
1359
		$object_count = $range_edges['item_count'];
1360
1361
		// Ensure no division by 0.
1362
		if ( 0 === (int) $object_count ) {
1363
			return 1;
1364
		}
1365
1366
		// Default Bucket sizes.
1367
		$bucket_size = 10000; // Default bucket size is 10,000 items.
1368
		switch ( $table ) {
1369
			case 'postmeta':
1370
			case 'commentmeta':
1371
				$bucket_size = 5000; // Meta bucket size is restricted to 5,000 items.
1372
		}
1373
1374
		return (int) ceil( $object_count / $bucket_size );
1375
	}
1376
}
1377