Completed
Push — add/sync-modules-for-term-tabl... ( b9611f )
by
unknown
06:45
created

Replicastore::checksum_histogram()   F

Complexity

Conditions 19
Paths 322

Size

Total Lines 107

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
nc 322
nop 7
dl 0
loc 107
rs 1.7266
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Automattic\Jetpack\Sync;
4
5
/**
6
 * An implementation of Replicastore Interface which returns data stored in a WordPress.org DB.
7
 * This is useful to compare values in the local WP DB to values in the synced replica store
8
 */
9
class Replicastore implements Replicastore_Interface {
10
11
	public function reset() {
12
		global $wpdb;
13
14
		$wpdb->query( "DELETE FROM $wpdb->posts" );
15
		$wpdb->query( "DELETE FROM $wpdb->comments" );
16
17
		// also need to delete terms from cache
18
		$term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" );
19
		foreach ( $term_ids as $term_id ) {
20
			wp_cache_delete( $term_id, 'terms' );
21
		}
22
23
		$wpdb->query( "DELETE FROM $wpdb->terms" );
24
25
		$wpdb->query( "DELETE FROM $wpdb->term_taxonomy" );
26
		$wpdb->query( "DELETE FROM $wpdb->term_relationships" );
27
28
		// callables and constants
29
		$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" );
30
		$wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" );
31
	}
32
33
	function full_sync_start( $config ) {
34
		$this->reset();
35
	}
36
37
	function full_sync_end( $checksum ) {
38
		// noop right now
39
	}
40
41
	public function term_count() {
42
		global $wpdb;
43
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->terms" );
44
	}
45
46
	public function termmeta_count() {
47
		global $wpdb;
48
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->termmeta" );
49
	}
50
51
	public function term_taxonomy_count() {
52
		global $wpdb;
53
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_taxonomy" );
54
	}
55
56
	public function term_relationship_count() {
57
		global $wpdb;
58
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_relationships" );
59
	}
60
61 View Code Duplication
	public function post_count( $status = null, $min_id = null, $max_id = null ) {
62
		global $wpdb;
63
64
		$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...
65
66
		if ( $status ) {
67
			$where = "post_status = '" . esc_sql( $status ) . "'";
68
		} else {
69
			$where = '1=1';
70
		}
71
72
		if ( null != $min_id ) {
73
			$where .= ' AND ID >= ' . intval( $min_id );
74
		}
75
76
		if ( null != $max_id ) {
77
			$where .= ' AND ID <= ' . intval( $max_id );
78
		}
79
80
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
81
	}
82
83
	// TODO: actually use max_id/min_id
84
	public function get_posts( $status = null, $min_id = null, $max_id = null ) {
85
		$args = array(
86
			'orderby'        => 'ID',
87
			'posts_per_page' => -1,
88
		);
89
90
		if ( $status ) {
91
			$args['post_status'] = $status;
92
		} else {
93
			$args['post_status'] = 'any';
94
		}
95
96
		return get_posts( $args );
97
	}
98
99
	public function get_post( $id ) {
100
		return get_post( $id );
101
	}
102
103
	public function upsert_post( $post, $silent = false ) {
104
		global $wpdb;
105
106
		// reject the post if it's not a WP_Post
107
		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...
108
			return;
109
		}
110
111
		$post = $post->to_array();
112
113
		// reject posts without an ID
114
		if ( ! isset( $post['ID'] ) ) {
115
			return;
116
		}
117
118
		$now     = current_time( 'mysql' );
119
		$now_gmt = get_gmt_from_date( $now );
120
121
		$defaults = array(
122
			'ID'                    => 0,
123
			'post_author'           => '0',
124
			'post_content'          => '',
125
			'post_content_filtered' => '',
126
			'post_title'            => '',
127
			'post_name'             => '',
128
			'post_excerpt'          => '',
129
			'post_status'           => 'draft',
130
			'post_type'             => 'post',
131
			'comment_status'        => 'closed',
132
			'comment_count'         => '0',
133
			'ping_status'           => '',
134
			'post_password'         => '',
135
			'to_ping'               => '',
136
			'pinged'                => '',
137
			'post_parent'           => 0,
138
			'menu_order'            => 0,
139
			'guid'                  => '',
140
			'post_date'             => $now,
141
			'post_date_gmt'         => $now_gmt,
142
			'post_modified'         => $now,
143
			'post_modified_gmt'     => $now_gmt,
144
		);
145
146
		$post = array_intersect_key( $post, $defaults );
147
148
		$post = sanitize_post( $post, 'db' );
149
150
		unset( $post['filter'] );
151
152
		$exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
153
154
		if ( $exists ) {
155
			$wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
156
		} else {
157
			$wpdb->insert( $wpdb->posts, $post );
158
		}
159
160
		clean_post_cache( $post['ID'] );
161
	}
162
163
	public function delete_post( $post_id ) {
164
		wp_delete_post( $post_id, true );
165
	}
166
167
	public function posts_checksum( $min_id = null, $max_id = null ) {
168
		global $wpdb;
169
		return $this->table_checksum( $wpdb->posts, Defaults::$default_post_checksum_columns, 'ID', Settings::get_blacklisted_post_types_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_post_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

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

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

Loading history...
170
	}
171
172
	public function post_meta_checksum( $min_id = null, $max_id = null ) {
173
		global $wpdb;
174
		return $this->table_checksum( $wpdb->postmeta, Defaults::$default_post_meta_checksum_columns, 'meta_id', Settings::get_whitelisted_post_meta_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_post_meta_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

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

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

Loading history...
175
	}
176
177 View Code Duplication
	public function comment_count( $status = null, $min_id = null, $max_id = null ) {
178
		global $wpdb;
179
180
		$comment_approved = $this->comment_status_to_approval_value( $status );
181
182
		if ( $comment_approved !== false ) {
183
			$where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
184
		} else {
185
			$where = '1=1';
186
		}
187
188
		if ( $min_id != null ) {
189
			$where .= ' AND comment_ID >= ' . intval( $min_id );
190
		}
191
192
		if ( $max_id != null ) {
193
			$where .= ' AND comment_ID <= ' . intval( $max_id );
194
		}
195
196
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
197
	}
198
199
	private function comment_status_to_approval_value( $status ) {
200
		switch ( $status ) {
201
			case 'approve':
202
				return '1';
203
			case 'hold':
204
				return '0';
205
			case 'spam':
206
				return 'spam';
207
			case 'trash':
208
				return 'trash';
209
			case 'any':
210
				return false;
211
			case 'all':
212
				return false;
213
			default:
214
				return false;
215
		}
216
	}
217
218
	// TODO: actually use max_id/min_id
219
	public function get_comments( $status = null, $min_id = null, $max_id = null ) {
220
		$args = array(
221
			'orderby' => 'ID',
222
			'status'  => 'all',
223
		);
224
225
		if ( $status ) {
226
			$args['status'] = $status;
227
		}
228
229
		return get_comments( $args );
230
	}
231
232
	public function get_comment( $id ) {
233
		return \WP_Comment::get_instance( $id );
234
	}
235
236
	public function upsert_comment( $comment ) {
237
		global $wpdb;
238
239
		$comment = $comment->to_array();
240
241
		// filter by fields on comment table
242
		$comment_fields_whitelist = array(
243
			'comment_ID',
244
			'comment_post_ID',
245
			'comment_author',
246
			'comment_author_email',
247
			'comment_author_url',
248
			'comment_author_IP',
249
			'comment_date',
250
			'comment_date_gmt',
251
			'comment_content',
252
			'comment_karma',
253
			'comment_approved',
254
			'comment_agent',
255
			'comment_type',
256
			'comment_parent',
257
			'user_id',
258
		);
259
260
		foreach ( $comment as $key => $value ) {
261
			if ( ! in_array( $key, $comment_fields_whitelist ) ) {
262
				unset( $comment[ $key ] );
263
			}
264
		}
265
266
		$exists = $wpdb->get_var(
267
			$wpdb->prepare(
268
				"SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
269
				$comment['comment_ID']
270
			)
271
		);
272
273
		if ( $exists ) {
274
			$wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
275
		} else {
276
			$wpdb->insert( $wpdb->comments, $comment );
277
		}
278
279
		wp_update_comment_count( $comment['comment_post_ID'] );
280
	}
281
282
	public function trash_comment( $comment_id ) {
283
		wp_delete_comment( $comment_id );
284
	}
285
286
	public function delete_comment( $comment_id ) {
287
		wp_delete_comment( $comment_id, true );
288
	}
289
290
	public function spam_comment( $comment_id ) {
291
		wp_spam_comment( $comment_id );
292
	}
293
294
	public function trashed_post_comments( $post_id, $statuses ) {
295
		wp_trash_post_comments( $post_id );
296
	}
297
298
	public function untrashed_post_comments( $post_id ) {
299
		wp_untrash_post_comments( $post_id );
300
	}
301
302
	public function comments_checksum( $min_id = null, $max_id = null ) {
303
		global $wpdb;
304
		return $this->table_checksum( $wpdb->comments, Defaults::$default_comment_checksum_columns, 'comment_ID', Settings::get_comments_filter_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_comment_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

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

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

Loading history...
305
	}
306
307
	public function comment_meta_checksum( $min_id = null, $max_id = null ) {
308
		global $wpdb;
309
		return $this->table_checksum( $wpdb->commentmeta, Defaults::$default_comment_meta_checksum_columns, 'meta_id', Settings::get_whitelisted_comment_meta_sql(), $min_id, $max_id );
0 ignored issues
show
Bug introduced by
The property default_comment_meta_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

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

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

Loading history...
310
	}
311
312
	public function options_checksum() {
313
		global $wpdb;
314
		$options_whitelist = "'" . implode( "', '", Defaults::$default_options_whitelist ) . "'";
0 ignored issues
show
Bug introduced by
The property default_options_whitelist cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

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

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

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

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

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

Loading history...
318
	}
319
320
321
	public function update_option( $option, $value ) {
322
		return update_option( $option, $value );
323
	}
324
325
	public function get_option( $option, $default = false ) {
326
		return get_option( $option, $default );
327
	}
328
329
	public function delete_option( $option ) {
330
		return delete_option( $option );
331
	}
332
333
	public function set_theme_support( $theme_support ) {
334
		// noop
335
	}
336
337
	public function current_theme_supports( $feature ) {
338
		return current_theme_supports( $feature );
339
	}
340
341
	public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
342
		return get_metadata( $type, $object_id, $meta_key, $single );
343
	}
344
345
	/**
346
	 *
347
	 * Stores remote meta key/values alongside an ID mapping key
348
	 *
349
	 * @param $type
350
	 * @param $object_id
351
	 * @param $meta_key
352
	 * @param $meta_value
353
	 * @param $meta_id
354
	 *
355
	 * @return bool
356
	 */
357
	public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
358
359
		$table = _get_meta_table( $type );
360
		if ( ! $table ) {
361
			return false;
362
		}
363
364
		global $wpdb;
365
366
		$exists = $wpdb->get_var(
367
			$wpdb->prepare(
368
				"SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
369
				$meta_id
370
			)
371
		);
372
373
		if ( $exists ) {
374
			$wpdb->update(
375
				$table,
376
				array(
377
					'meta_key'   => $meta_key,
378
					'meta_value' => maybe_serialize( $meta_value ),
379
				),
380
				array( 'meta_id' => $meta_id )
381
			);
382
		} else {
383
			$object_id_field = $type . '_id';
384
			$wpdb->insert(
385
				$table,
386
				array(
387
					'meta_id'        => $meta_id,
388
					$object_id_field => $object_id,
389
					'meta_key'       => $meta_key,
390
					'meta_value'     => maybe_serialize( $meta_value ),
391
				)
392
			);
393
		}
394
395
		wp_cache_delete( $object_id, $type . '_meta' );
396
397
		return true;
398
	}
399
400
	public function delete_metadata( $type, $object_id, $meta_ids ) {
401
		global $wpdb;
402
403
		$table = _get_meta_table( $type );
404
		if ( ! $table ) {
405
			return false;
406
		}
407
408
		foreach ( $meta_ids as $meta_id ) {
409
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
410
		}
411
412
		// if we don't have an object ID what do we do - invalidate ALL meta?
413
		if ( $object_id ) {
414
			wp_cache_delete( $object_id, $type . '_meta' );
415
		}
416
	}
417
418
	// todo: test this out to make sure it works as expected.
419
	public function delete_batch_metadata( $type, $object_ids, $meta_key ) {
420
		global $wpdb;
421
422
		$table = _get_meta_table( $type );
423
		if ( ! $table ) {
424
			return false;
425
		}
426
		$column = sanitize_key( $type . '_id' );
427
		$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE $column IN (%s) && meta_key = %s", implode( ',', $object_ids ), $meta_key ) );
428
429
		// if we don't have an object ID what do we do - invalidate ALL meta?
430
		foreach ( $object_ids as $object_id ) {
431
			wp_cache_delete( $object_id, $type . '_meta' );
432
		}
433
	}
434
435
	// constants
436
	public function get_constant( $constant ) {
437
		$value = get_option( 'jetpack_constant_' . $constant );
438
439
		if ( $value ) {
440
			return $value;
441
		}
442
443
		return null;
444
	}
445
446
	public function set_constant( $constant, $value ) {
447
		update_option( 'jetpack_constant_' . $constant, $value );
448
	}
449
450
	public function get_updates( $type ) {
451
		$all_updates = get_option( 'jetpack_updates', array() );
452
453
		if ( isset( $all_updates[ $type ] ) ) {
454
			return $all_updates[ $type ];
455
		} else {
456
			return null;
457
		}
458
	}
459
460
	public function set_updates( $type, $updates ) {
461
		$all_updates          = get_option( 'jetpack_updates', array() );
462
		$all_updates[ $type ] = $updates;
463
		update_option( 'jetpack_updates', $all_updates );
464
	}
465
466
	// functions
467
	public function get_callable( $name ) {
468
		$value = get_option( 'jetpack_' . $name );
469
470
		if ( $value ) {
471
			return $value;
472
		}
473
474
		return null;
475
	}
476
477
	public function set_callable( $name, $value ) {
478
		update_option( 'jetpack_' . $name, $value );
479
	}
480
481
	// network options
482
	public function get_site_option( $option ) {
483
		return get_option( 'jetpack_network_' . $option );
484
	}
485
486
	public function update_site_option( $option, $value ) {
487
		return update_option( 'jetpack_network_' . $option, $value );
488
	}
489
490
	public function delete_site_option( $option ) {
491
		return delete_option( 'jetpack_network_' . $option );
492
	}
493
494
	// terms
495
	// terms
496
	public function get_terms( $taxonomy ) {
497
		return get_terms( $taxonomy );
498
	}
499
500
	public function get_term( $taxonomy, $term_id, $is_term_id = true ) {
501
		$t = $this->ensure_taxonomy( $taxonomy );
502
		if ( ! $t || is_wp_error( $t ) ) {
503
			return $t;
504
		}
505
506
		return get_term( $term_id, $taxonomy );
507
	}
508
509
	private function ensure_taxonomy( $taxonomy ) {
510
		if ( ! taxonomy_exists( $taxonomy ) ) {
511
			// try re-registering synced taxonomies
512
			$taxonomies = $this->get_callable( 'taxonomies' );
513
			if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
514
				// doesn't exist, or somehow hasn't been synced
515
				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...
516
			}
517
			$t = $taxonomies[ $taxonomy ];
518
519
			return register_taxonomy(
520
				$taxonomy,
521
				$t->object_type,
522
				(array) $t
523
			);
524
		}
525
526
		return true;
527
	}
528
529
	public function get_the_terms( $object_id, $taxonomy ) {
530
		return get_the_terms( $object_id, $taxonomy );
531
	}
532
533
	public function update_term( $term_object ) {
534
		$taxonomy = $term_object->taxonomy;
535
		global $wpdb;
536
		$exists = $wpdb->get_var(
537
			$wpdb->prepare(
538
				"SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
539
				$term_object->term_id
540
			)
541
		);
542
		if ( ! $exists ) {
543
			$term_object   = sanitize_term( clone( $term_object ), $taxonomy, 'db' );
544
			$term          = array(
545
				'term_id'    => $term_object->term_id,
546
				'name'       => $term_object->name,
547
				'slug'       => $term_object->slug,
548
				'term_group' => $term_object->term_group,
549
			);
550
			$term_taxonomy = array(
551
				'term_taxonomy_id' => $term_object->term_taxonomy_id,
552
				'term_id'          => $term_object->term_id,
553
				'taxonomy'         => $term_object->taxonomy,
554
				'description'      => $term_object->description,
555
				'parent'           => (int) $term_object->parent,
556
				'count'            => (int) $term_object->count,
557
			);
558
			$wpdb->insert( $wpdb->terms, $term );
559
			$wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
560
561
			return true;
562
		}
563
564
		return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
565
	}
566
567
	public function delete_term( $term_id, $taxonomy ) {
568
		return wp_delete_term( $term_id, $taxonomy );
569
	}
570
571
	public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
572
		wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
573
	}
574
575
	public function delete_object_terms( $object_id, $tt_ids ) {
576
		global $wpdb;
577
578
		if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
579
			// escape
580
			$tt_ids_sanitized = array_map( 'intval', $tt_ids );
581
582
			$taxonomies = array();
583
			foreach ( $tt_ids_sanitized as $tt_id ) {
584
				$term                            = get_term_by( 'term_taxonomy_id', $tt_id );
585
				$taxonomies[ $term->taxonomy ][] = $tt_id;
586
			}
587
			$in_tt_ids = implode( ', ', $tt_ids_sanitized );
588
589
			/**
590
			 * Fires immediately before an object-term relationship is deleted.
591
			 *
592
			 * @since 2.9.0
593
			 *
594
			 * @param int $object_id Object ID.
595
			 * @param array $tt_ids An array of term taxonomy IDs.
596
			 */
597
			do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized );
598
			$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
599
			foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
600
				$this->ensure_taxonomy( $taxonomy );
601
				wp_cache_delete( $object_id, $taxonomy . '_relationships' );
602
				/**
603
				 * Fires immediately after an object-term relationship is deleted.
604
				 *
605
				 * @since 2.9.0
606
				 *
607
				 * @param int $object_id Object ID.
608
				 * @param array $tt_ids An array of term taxonomy IDs.
609
				 */
610
				do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
611
				wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
612
			}
613
614
			return (bool) $deleted;
615
		}
616
617
		return false;
618
	}
619
620
	// users
621
	public function user_count() {
622
623
	}
624
625
	public function get_user( $user_id ) {
626
		return \WP_User::get_instance( $user_id );
627
	}
628
629
	public function upsert_user( $user ) {
630
		$this->invalid_call();
631
	}
632
633
	public function delete_user( $user_id ) {
634
		$this->invalid_call();
635
	}
636
637
	public function upsert_user_locale( $user_id, $local ) {
638
		$this->invalid_call();
639
	}
640
641
	public function delete_user_locale( $user_id ) {
642
		$this->invalid_call();
643
	}
644
645
	public function get_user_locale( $user_id ) {
646
		return get_user_locale( $user_id );
647
	}
648
649
	public function get_allowed_mime_types( $user_id ) {
650
651
	}
652
653
	public function checksum_all() {
654
		$post_meta_checksum    = $this->checksum_histogram( 'post_meta', 1 );
655
		$comment_meta_checksum = $this->checksum_histogram( 'comment_meta', 1 );
656
657
		return array(
658
			'posts'        => $this->posts_checksum(),
659
			'comments'     => $this->comments_checksum(),
660
			'post_meta'    => reset( $post_meta_checksum ),
661
			'comment_meta' => reset( $comment_meta_checksum ),
662
		);
663
	}
664
665
	function get_checksum_columns_for_object_type( $object_type ) {
666
		switch( $object_type ) {
667
			case 'posts':
668
				return Defaults::$default_post_checksum_columns;
0 ignored issues
show
Bug introduced by
The property default_post_checksum_columns cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
683
			default:
684
				return false;
685
		}
686
	}
687
688
	function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '' ) {
689
		global $wpdb;
690
691
		$wpdb->queries = array();
692
693
		if ( empty( $columns ) ) {
694
			$columns = $this->get_checksum_columns_for_object_type( $object_type );
695
		}
696
697
		switch ( $object_type ) {
698
			case 'posts':
699
				$object_count = $this->post_count( null, $start_id, $end_id );
700
				$object_table = $wpdb->posts;
701
				$id_field     = 'ID';
702
				$where_sql    = Settings::get_blacklisted_post_types_sql();
703
				break;
704
			case 'post_meta':
705
				$object_table = $wpdb->postmeta;
706
				$where_sql    = Settings::get_whitelisted_post_meta_sql();
707
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
708
				$id_field     = 'meta_id';
709
				break;
710
			case 'comments':
711
				$object_count = $this->comment_count( null, $start_id, $end_id );
712
				$object_table = $wpdb->comments;
713
				$id_field     = 'comment_ID';
714
				$where_sql    = Settings::get_comments_filter_sql();
715
				break;
716
			case 'comment_meta':
717
				$object_table = $wpdb->commentmeta;
718
				$where_sql    = Settings::get_whitelisted_comment_meta_sql();
719
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
720
				$id_field     = 'meta_id';
721
				break;
722
			case 'terms':
723
				$object_table = $wpdb->terms;
724
				$object_count = $this->term_count();
725
				$id_field     = 'term_id';
726
				$where_sql    = '1=1';
727
				break;
728
			case 'termmeta':
729
				$object_table = $wpdb->termmeta;
730
				$object_count = $this->termmeta_count();
731
				$id_field     = 'meta_id';
732
				$where_sql    = '1=1';
733
				break;
734
			case 'term_taxonomy':
735
				$object_table = $wpdb->term_taxonomy;
736
				$object_count = $this->term_taxonomy_count();
737
				$id_field     = 'term_taxonomy_id';
738
				$where_sql    = '1=1';
739
				break;
740
			case 'term_relationships':
741
				$object_table = $wpdb->term_relationships;
742
				$object_count = $this->term_relationship_count();
743
				$id_field = 'object_id';
744
				$where_sql    = '1=1';
745
				break;
746
			default:
747
				return false;
748
		}
749
750
		$bucket_size     = intval( ceil( $object_count / $buckets ) );
751
		$previous_max_id = 0;
752
		$histogram       = array();
753
754
		$where = '1=1';
755
756
		if ( $start_id ) {
757
			$where .= " AND $id_field >= " . intval( $start_id );
758
		}
759
760
		if ( $end_id ) {
761
			$where .= " AND $id_field <= " . intval( $end_id );
762
		}
763
764
		do {
765
			list( $first_id, $last_id ) = $wpdb->get_row(
766
				"SELECT MIN($id_field) as min_id, MAX($id_field) as max_id FROM ( SELECT $id_field FROM $object_table WHERE $where AND $id_field > $previous_max_id ORDER BY $id_field ASC LIMIT $bucket_size ) as ids",
767
				ARRAY_N
768
			);
769
770
			if ( null === $first_id || null === $last_id ) {
771
				// Nothing to checksum here...
772
				break;
773
			}
774
775
			// get the checksum value
776
			$value = $this->table_checksum( $object_table, $columns, $id_field, $where_sql, $first_id, $last_id, $strip_non_ascii, $salt );
777
778
			if ( is_wp_error( $value ) ) {
779
				return $value;
780
			}
781
782
			if ( null === $first_id || null === $last_id ) {
783
				break;
784
			} elseif ( $first_id === $last_id ) {
785
				$histogram[ $first_id ] = $value;
786
			} else {
787
				$histogram[ "{$first_id}-{$last_id}" ] = $value;
788
			}
789
790
			$previous_max_id = $last_id;
791
		} while ( true );
792
793
		return $histogram;
794
	}
795
796
	private function table_checksum( $table, $columns, $id_column, $where_sql = '1=1', $min_id = null, $max_id = null, $strip_non_ascii = true, $salt = '' ) {
797
		global $wpdb;
798
799
		// sanitize to just valid MySQL column names
800
		$sanitized_columns = preg_grep( '/^[0-9,a-z,A-Z$_]+$/i', $columns );
801
802
		if ( $strip_non_ascii ) {
803
			$columns_sql = implode( ',', array_map( array( $this, 'strip_non_ascii_sql' ), $sanitized_columns ) );
804
		} else {
805
			$columns_sql = implode( ',', $sanitized_columns );
806
		}
807
808
		if ( null !== $min_id && null !== $max_id ) {
809
			if ( $min_id === $max_id ) {
810
				$min_id     = intval( $min_id );
811
				$where_sql .= " AND $id_column = $min_id LIMIT 1";
812
			} else {
813
				$min_id     = intval( $min_id );
814
				$max_id     = intval( $max_id );
815
				$size       = $max_id - $min_id;
816
				$where_sql .= " AND $id_column >= $min_id AND $id_column <= $max_id LIMIT $size";
817
			}
818
		} else {
819
			if ( null !== $min_id ) {
820
				$min_id     = intval( $min_id );
821
				$where_sql .= " AND $id_column >= $min_id";
822
			}
823
824
			if ( null !== $max_id ) {
825
				$max_id     = intval( $max_id );
826
				$where_sql .= " AND $id_column <= $max_id";
827
			}
828
		}
829
830
		$query  = <<<ENDSQL
831
			SELECT CAST( SUM( CRC32( CONCAT_WS( '#', '%s', {$columns_sql} ) ) ) AS UNSIGNED INT )
832
			FROM $table
833
			WHERE $where_sql;
834
ENDSQL;
835
		$result = $wpdb->get_var( $wpdb->prepare( $query, $salt ) );
836
		if ( $wpdb->last_error ) {
837
			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...
838
		}
839
840
		return $result;
841
	}
842
843
	public function get_checksum_type() {
844
		return 'sum';
845
	}
846
847
	private function meta_count( $table, $where_sql, $min_id, $max_id ) {
848
		global $wpdb;
849
850
		if ( $min_id != null ) {
851
			$where_sql .= ' AND meta_id >= ' . intval( $min_id );
852
		}
853
854
		if ( $max_id != null ) {
855
			$where_sql .= ' AND meta_id <= ' . intval( $max_id );
856
		}
857
858
		return $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE $where_sql" );
859
	}
860
861
	/**
862
	 * Wraps a column name in SQL which strips non-ASCII chars.
863
	 * This helps normalize data to avoid checksum differences caused by
864
	 * badly encoded data in the DB
865
	 */
866
	function strip_non_ascii_sql( $column_name ) {
867
		return "REPLACE( CONVERT( $column_name USING ascii ), '?', '' )";
868
	}
869
870
	private function invalid_call() {
871
		$backtrace = debug_backtrace();
872
		$caller    = $backtrace[1]['function'];
873
		throw new \Exception( "This function $caller is not supported on the WP Replicastore" );
874
	}
875
}
876