Completed
Push — try/sync-package ( 228b13 )
by Marin
07:37
created

WP_Replicastore::delete_metadata()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 5
nop 3
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
namespace Automattic\Jetpack\Sync;
4
5
/**
6
 * An implementation of Replicastore 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 WP_Replicastore implements Replicastore {
10
	public function reset() {
11
		global $wpdb;
12
13
		$wpdb->query( "DELETE FROM $wpdb->posts" );
14
		$wpdb->query( "DELETE FROM $wpdb->comments" );
15
16
		// also need to delete terms from cache
17
		$term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" );
18
		foreach ( $term_ids as $term_id ) {
19
			wp_cache_delete( $term_id, 'terms' );
20
		}
21
22
		$wpdb->query( "DELETE FROM $wpdb->terms" );
23
24
		$wpdb->query( "DELETE FROM $wpdb->term_taxonomy" );
25
		$wpdb->query( "DELETE FROM $wpdb->term_relationships" );
26
27
		// callables and constants
28
		$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" );
29
		$wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" );
30
	}
31
32
	function full_sync_start( $config ) {
33
		$this->reset();
34
	}
35
36
	function full_sync_end( $checksum ) {
37
		// noop right now
38
	}
39
40 View Code Duplication
	public function post_count( $status = null, $min_id = null, $max_id = null ) {
41
		global $wpdb;
42
43
		$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...
44
45
		if ( $status ) {
46
			$where = "post_status = '" . esc_sql( $status ) . "'";
47
		} else {
48
			$where = '1=1';
49
		}
50
51
		if ( null != $min_id ) {
52
			$where .= ' AND ID >= ' . intval( $min_id );
53
		}
54
55
		if ( null != $max_id ) {
56
			$where .= ' AND ID <= ' . intval( $max_id );
57
		}
58
59
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
60
	}
61
62
	// TODO: actually use max_id/min_id
63
	public function get_posts( $status = null, $min_id = null, $max_id = null ) {
64
		$args = array(
65
			'orderby'        => 'ID',
66
			'posts_per_page' => -1,
67
		);
68
69
		if ( $status ) {
70
			$args['post_status'] = $status;
71
		} else {
72
			$args['post_status'] = 'any';
73
		}
74
75
		return get_posts( $args );
76
	}
77
78
	public function get_post( $id ) {
79
		return get_post( $id );
80
	}
81
82
	public function upsert_post( $post, $silent = false ) {
83
		global $wpdb;
84
85
		// reject the post if it's not a WP_Post
86
		if ( ! $post instanceof WP_Post ) {
0 ignored issues
show
Bug introduced by
The class Automattic\Jetpack\Sync\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...
87
			return;
88
		}
89
90
		$post = $post->to_array();
91
92
		// reject posts without an ID
93
		if ( ! isset( $post['ID'] ) ) {
94
			return;
95
		}
96
97
		$now     = current_time( 'mysql' );
98
		$now_gmt = get_gmt_from_date( $now );
99
100
		$defaults = array(
101
			'ID'                    => 0,
102
			'post_author'           => '0',
103
			'post_content'          => '',
104
			'post_content_filtered' => '',
105
			'post_title'            => '',
106
			'post_name'             => '',
107
			'post_excerpt'          => '',
108
			'post_status'           => 'draft',
109
			'post_type'             => 'post',
110
			'comment_status'        => 'closed',
111
			'comment_count'         => '0',
112
			'ping_status'           => '',
113
			'post_password'         => '',
114
			'to_ping'               => '',
115
			'pinged'                => '',
116
			'post_parent'           => 0,
117
			'menu_order'            => 0,
118
			'guid'                  => '',
119
			'post_date'             => $now,
120
			'post_date_gmt'         => $now_gmt,
121
			'post_modified'         => $now,
122
			'post_modified_gmt'     => $now_gmt,
123
		);
124
125
		$post = array_intersect_key( $post, $defaults );
126
127
		$post = sanitize_post( $post, 'db' );
128
129
		unset( $post['filter'] );
130
131
		$exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
132
133
		if ( $exists ) {
134
			$wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
135
		} else {
136
			$wpdb->insert( $wpdb->posts, $post );
137
		}
138
139
		clean_post_cache( $post['ID'] );
140
	}
141
142
	public function delete_post( $post_id ) {
143
		wp_delete_post( $post_id, true );
144
	}
145
146
	public function posts_checksum( $min_id = null, $max_id = null ) {
147
		global $wpdb;
148
		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...
149
	}
150
151
	public function post_meta_checksum( $min_id = null, $max_id = null ) {
152
		global $wpdb;
153
		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...
154
	}
155
156 View Code Duplication
	public function comment_count( $status = null, $min_id = null, $max_id = null ) {
157
		global $wpdb;
158
159
		$comment_approved = $this->comment_status_to_approval_value( $status );
160
161
		if ( $comment_approved !== false ) {
162
			$where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
163
		} else {
164
			$where = '1=1';
165
		}
166
167
		if ( $min_id != null ) {
168
			$where .= ' AND comment_ID >= ' . intval( $min_id );
169
		}
170
171
		if ( $max_id != null ) {
172
			$where .= ' AND comment_ID <= ' . intval( $max_id );
173
		}
174
175
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
176
	}
177
178
	private function comment_status_to_approval_value( $status ) {
179
		switch ( $status ) {
180
			case 'approve':
181
				return '1';
182
			case 'hold':
183
				return '0';
184
			case 'spam':
185
				return 'spam';
186
			case 'trash':
187
				return 'trash';
188
			case 'any':
189
				return false;
190
			case 'all':
191
				return false;
192
			default:
193
				return false;
194
		}
195
	}
196
197
	// TODO: actually use max_id/min_id
198
	public function get_comments( $status = null, $min_id = null, $max_id = null ) {
199
		$args = array(
200
			'orderby' => 'ID',
201
			'status'  => 'all',
202
		);
203
204
		if ( $status ) {
205
			$args['status'] = $status;
206
		}
207
208
		return get_comments( $args );
209
	}
210
211
	public function get_comment( $id ) {
212
		return WP_Comment::get_instance( $id );
213
	}
214
215
	public function upsert_comment( $comment ) {
216
		global $wpdb;
217
218
		$comment = $comment->to_array();
219
220
		// filter by fields on comment table
221
		$comment_fields_whitelist = array(
222
			'comment_ID',
223
			'comment_post_ID',
224
			'comment_author',
225
			'comment_author_email',
226
			'comment_author_url',
227
			'comment_author_IP',
228
			'comment_date',
229
			'comment_date_gmt',
230
			'comment_content',
231
			'comment_karma',
232
			'comment_approved',
233
			'comment_agent',
234
			'comment_type',
235
			'comment_parent',
236
			'user_id',
237
		);
238
239
		foreach ( $comment as $key => $value ) {
240
			if ( ! in_array( $key, $comment_fields_whitelist ) ) {
241
				unset( $comment[ $key ] );
242
			}
243
		}
244
245
		$exists = $wpdb->get_var(
246
			$wpdb->prepare(
247
				"SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
248
				$comment['comment_ID']
249
			)
250
		);
251
252
		if ( $exists ) {
253
			$wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
254
		} else {
255
			$wpdb->insert( $wpdb->comments, $comment );
256
		}
257
258
		wp_update_comment_count( $comment['comment_post_ID'] );
259
	}
260
261
	public function trash_comment( $comment_id ) {
262
		wp_delete_comment( $comment_id );
263
	}
264
265
	public function delete_comment( $comment_id ) {
266
		wp_delete_comment( $comment_id, true );
267
	}
268
269
	public function spam_comment( $comment_id ) {
270
		wp_spam_comment( $comment_id );
271
	}
272
273
	public function trashed_post_comments( $post_id, $statuses ) {
274
		wp_trash_post_comments( $post_id );
275
	}
276
277
	public function untrashed_post_comments( $post_id ) {
278
		wp_untrash_post_comments( $post_id );
279
	}
280
281
	public function comments_checksum( $min_id = null, $max_id = null ) {
282
		global $wpdb;
283
		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...
284
	}
285
286
	public function comment_meta_checksum( $min_id = null, $max_id = null ) {
287
		global $wpdb;
288
		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...
289
	}
290
291
	public function options_checksum() {
292
		global $wpdb;
293
294
		$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...
295
		$where_sql         = "option_name IN ( $options_whitelist )";
296
297
		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...
298
	}
299
300
301
	public function update_option( $option, $value ) {
302
		return update_option( $option, $value );
303
	}
304
305
	public function get_option( $option, $default = false ) {
306
		return get_option( $option, $default );
307
	}
308
309
	public function delete_option( $option ) {
310
		return delete_option( $option );
311
	}
312
313
	public function set_theme_support( $theme_support ) {
314
		// noop
315
	}
316
317
	public function current_theme_supports( $feature ) {
318
		return current_theme_supports( $feature );
319
	}
320
321
	public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
322
		return get_metadata( $type, $object_id, $meta_key, $single );
323
	}
324
325
	/**
326
	 *
327
	 * Stores remote meta key/values alongside an ID mapping key
328
	 *
329
	 * @param $type
330
	 * @param $object_id
331
	 * @param $meta_key
332
	 * @param $meta_value
333
	 * @param $meta_id
334
	 *
335
	 * @return bool
336
	 */
337
	public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
338
339
		$table = _get_meta_table( $type );
340
		if ( ! $table ) {
341
			return false;
342
		}
343
344
		global $wpdb;
345
346
		$exists = $wpdb->get_var(
347
			$wpdb->prepare(
348
				"SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
349
				$meta_id
350
			)
351
		);
352
353
		if ( $exists ) {
354
			$wpdb->update(
355
				$table,
356
				array(
357
					'meta_key'   => $meta_key,
358
					'meta_value' => maybe_serialize( $meta_value ),
359
				),
360
				array( 'meta_id' => $meta_id )
361
			);
362
		} else {
363
			$object_id_field = $type . '_id';
364
			$wpdb->insert(
365
				$table,
366
				array(
367
					'meta_id'        => $meta_id,
368
					$object_id_field => $object_id,
369
					'meta_key'       => $meta_key,
370
					'meta_value'     => maybe_serialize( $meta_value ),
371
				)
372
			);
373
		}
374
375
		wp_cache_delete( $object_id, $type . '_meta' );
376
377
		return true;
378
	}
379
380
	public function delete_metadata( $type, $object_id, $meta_ids ) {
381
		global $wpdb;
382
383
		$table = _get_meta_table( $type );
384
		if ( ! $table ) {
385
			return false;
386
		}
387
388
		foreach ( $meta_ids as $meta_id ) {
389
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
390
		}
391
392
		// if we don't have an object ID what do we do - invalidate ALL meta?
393
		if ( $object_id ) {
394
			wp_cache_delete( $object_id, $type . '_meta' );
395
		}
396
	}
397
398
	// todo: test this out to make sure it works as expected.
399
	public function delete_batch_metadata( $type, $object_ids, $meta_key ) {
400
		global $wpdb;
401
402
		$table = _get_meta_table( $type );
403
		if ( ! $table ) {
404
			return false;
405
		}
406
		$column = sanitize_key( $type . '_id' );
407
		$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE $column IN (%s) && meta_key = %s", implode( ',', $object_ids ), $meta_key ) );
408
409
		// if we don't have an object ID what do we do - invalidate ALL meta?
410
		foreach ( $object_ids as $object_id ) {
411
			wp_cache_delete( $object_id, $type . '_meta' );
412
		}
413
	}
414
415
	// constants
416
	public function get_constant( $constant ) {
417
		$value = get_option( 'jetpack_constant_' . $constant );
418
419
		if ( $value ) {
420
			return $value;
421
		}
422
423
		return null;
424
	}
425
426
	public function set_constant( $constant, $value ) {
427
		update_option( 'jetpack_constant_' . $constant, $value );
428
	}
429
430
	public function get_updates( $type ) {
431
		$all_updates = get_option( 'jetpack_updates', array() );
432
433
		if ( isset( $all_updates[ $type ] ) ) {
434
			return $all_updates[ $type ];
435
		} else {
436
			return null;
437
		}
438
	}
439
440
	public function set_updates( $type, $updates ) {
441
		$all_updates          = get_option( 'jetpack_updates', array() );
442
		$all_updates[ $type ] = $updates;
443
		update_option( 'jetpack_updates', $all_updates );
444
	}
445
446
	// functions
447
	public function get_callable( $name ) {
448
		$value = get_option( 'jetpack_' . $name );
449
450
		if ( $value ) {
451
			return $value;
452
		}
453
454
		return null;
455
	}
456
457
	public function set_callable( $name, $value ) {
458
		update_option( 'jetpack_' . $name, $value );
459
	}
460
461
	// network options
462
	public function get_site_option( $option ) {
463
		return get_option( 'jetpack_network_' . $option );
464
	}
465
466
	public function update_site_option( $option, $value ) {
467
		return update_option( 'jetpack_network_' . $option, $value );
468
	}
469
470
	public function delete_site_option( $option ) {
471
		return delete_option( 'jetpack_network_' . $option );
472
	}
473
474
	// terms
475
	// terms
476
	public function get_terms( $taxonomy ) {
477
		return get_terms( $taxonomy );
478
	}
479
480
	public function get_term( $taxonomy, $term_id, $is_term_id = true ) {
481
		$t = $this->ensure_taxonomy( $taxonomy );
482
		if ( ! $t || is_wp_error( $t ) ) {
483
			return $t;
484
		}
485
486
		return get_term( $term_id, $taxonomy );
487
	}
488
489
	private function ensure_taxonomy( $taxonomy ) {
490
		if ( ! taxonomy_exists( $taxonomy ) ) {
491
			// try re-registering synced taxonomies
492
			$taxonomies = $this->get_callable( 'taxonomies' );
493
			if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
494
				// doesn't exist, or somehow hasn't been synced
495
				return new WP_Error( 'invalid_taxonomy', "The taxonomy '$taxonomy' doesn't exist" );
496
			}
497
			$t = $taxonomies[ $taxonomy ];
498
499
			return register_taxonomy(
500
				$taxonomy,
501
				$t->object_type,
502
				(array) $t
503
			);
504
		}
505
506
		return true;
507
	}
508
509
	public function get_the_terms( $object_id, $taxonomy ) {
510
		return get_the_terms( $object_id, $taxonomy );
511
	}
512
513
	public function update_term( $term_object ) {
514
		$taxonomy = $term_object->taxonomy;
515
		global $wpdb;
516
		$exists = $wpdb->get_var(
517
			$wpdb->prepare(
518
				"SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
519
				$term_object->term_id
520
			)
521
		);
522
		if ( ! $exists ) {
523
			$term_object   = sanitize_term( clone( $term_object ), $taxonomy, 'db' );
524
			$term          = array(
525
				'term_id'    => $term_object->term_id,
526
				'name'       => $term_object->name,
527
				'slug'       => $term_object->slug,
528
				'term_group' => $term_object->term_group,
529
			);
530
			$term_taxonomy = array(
531
				'term_taxonomy_id' => $term_object->term_taxonomy_id,
532
				'term_id'          => $term_object->term_id,
533
				'taxonomy'         => $term_object->taxonomy,
534
				'description'      => $term_object->description,
535
				'parent'           => (int) $term_object->parent,
536
				'count'            => (int) $term_object->count,
537
			);
538
			$wpdb->insert( $wpdb->terms, $term );
539
			$wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
540
541
			return true;
542
		}
543
544
		return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
545
	}
546
547
	public function delete_term( $term_id, $taxonomy ) {
548
		return wp_delete_term( $term_id, $taxonomy );
549
	}
550
551
	public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
552
		wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
553
	}
554
555
	public function delete_object_terms( $object_id, $tt_ids ) {
556
		global $wpdb;
557
558
		if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
559
			// escape
560
			$tt_ids_sanitized = array_map( 'intval', $tt_ids );
561
562
			$taxonomies = array();
563
			foreach ( $tt_ids_sanitized as $tt_id ) {
564
				$term                            = get_term_by( 'term_taxonomy_id', $tt_id );
565
				$taxonomies[ $term->taxonomy ][] = $tt_id;
566
			}
567
			$in_tt_ids = implode( ', ', $tt_ids_sanitized );
568
569
			/**
570
			 * Fires immediately before an object-term relationship is deleted.
571
			 *
572
			 * @since 2.9.0
573
			 *
574
			 * @param int $object_id Object ID.
575
			 * @param array $tt_ids An array of term taxonomy IDs.
576
			 */
577
			do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized );
578
			$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
579
			foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
580
				$this->ensure_taxonomy( $taxonomy );
581
				wp_cache_delete( $object_id, $taxonomy . '_relationships' );
582
				/**
583
				 * Fires immediately after an object-term relationship is deleted.
584
				 *
585
				 * @since 2.9.0
586
				 *
587
				 * @param int $object_id Object ID.
588
				 * @param array $tt_ids An array of term taxonomy IDs.
589
				 */
590
				do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
591
				wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
592
			}
593
594
			return (bool) $deleted;
595
		}
596
597
		return false;
598
	}
599
600
	// users
601
	public function user_count() {
602
603
	}
604
605
	public function get_user( $user_id ) {
606
		return WP_User::get_instance( $user_id );
607
	}
608
609
	public function upsert_user( $user ) {
610
		$this->invalid_call();
611
	}
612
613
	public function delete_user( $user_id ) {
614
		$this->invalid_call();
615
	}
616
617
	public function upsert_user_locale( $user_id, $local ) {
618
		$this->invalid_call();
619
	}
620
621
	public function delete_user_locale( $user_id ) {
622
		$this->invalid_call();
623
	}
624
625
	public function get_user_locale( $user_id ) {
626
		return get_user_locale( $user_id );
627
	}
628
629
	public function get_allowed_mime_types( $user_id ) {
630
631
	}
632
633
	public function checksum_all() {
634
		$post_meta_checksum    = $this->checksum_histogram( 'post_meta', 1 );
635
		$comment_meta_checksum = $this->checksum_histogram( 'comment_meta', 1 );
636
637
		return array(
638
			'posts'        => $this->posts_checksum(),
639
			'comments'     => $this->comments_checksum(),
640
			'post_meta'    => reset( $post_meta_checksum ),
641
			'comment_meta' => reset( $comment_meta_checksum ),
642
		);
643
	}
644
645
	function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '' ) {
646
		global $wpdb;
647
648
		$wpdb->queries = array();
649
650
		switch ( $object_type ) {
651
			case 'posts':
652
				$object_count = $this->post_count( null, $start_id, $end_id );
653
				$object_table = $wpdb->posts;
654
				$id_field     = 'ID';
655
				$where_sql    = Settings::get_blacklisted_post_types_sql();
656
				if ( empty( $columns ) ) {
657
					$columns = 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...
658
				}
659
				break;
660 View Code Duplication
			case 'post_meta':
661
				$object_table = $wpdb->postmeta;
662
				$where_sql    = Settings::get_whitelisted_post_meta_sql();
663
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
664
				$id_field     = 'meta_id';
665
666
				if ( empty( $columns ) ) {
667
					$columns = 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...
668
				}
669
				break;
670
			case 'comments':
671
				$object_count = $this->comment_count( null, $start_id, $end_id );
672
				$object_table = $wpdb->comments;
673
				$id_field     = 'comment_ID';
674
				$where_sql    = Settings::get_comments_filter_sql();
675
				if ( empty( $columns ) ) {
676
					$columns = 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...
677
				}
678
				break;
679 View Code Duplication
			case 'comment_meta':
680
				$object_table = $wpdb->commentmeta;
681
				$where_sql    = Settings::get_whitelisted_comment_meta_sql();
682
				$object_count = $this->meta_count( $object_table, $where_sql, $start_id, $end_id );
683
				$id_field     = 'meta_id';
684
				if ( empty( $columns ) ) {
685
					$columns = 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...
686
				}
687
				break;
688
			default:
689
				return false;
690
		}
691
692
		$bucket_size     = intval( ceil( $object_count / $buckets ) );
693
		$previous_max_id = 0;
694
		$histogram       = array();
695
696
		$where = '1=1';
697
698
		if ( $start_id ) {
699
			$where .= " AND $id_field >= " . intval( $start_id );
700
		}
701
702
		if ( $end_id ) {
703
			$where .= " AND $id_field <= " . intval( $end_id );
704
		}
705
706
		do {
707
			list( $first_id, $last_id ) = $wpdb->get_row(
708
				"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",
709
				ARRAY_N
710
			);
711
712
			if ( null === $first_id || null === $last_id  ) {
713
				// Nothing to checksum here...
714
				break;
715
			}
716
717
			// get the checksum value
718
			$value = $this->table_checksum( $object_table, $columns, $id_field, $where_sql, $first_id, $last_id, $strip_non_ascii, $salt );
719
720
			if ( is_wp_error( $value ) ) {
721
				return $value;
722
			}
723
724
			if ( null === $first_id || null === $last_id  ) {
725
				break;
726
			} elseif ( $first_id === $last_id ) {
727
				$histogram[ $first_id ] = $value;
728
			} else {
729
				$histogram[ "{$first_id}-{$last_id}" ] = $value;
730
			}
731
732
			$previous_max_id = $last_id;
733
		} while ( true );
734
735
		return $histogram;
736
	}
737
738
	private function table_checksum( $table, $columns, $id_column, $where_sql = '1=1', $min_id = null, $max_id = null, $strip_non_ascii = true, $salt = '' ) {
739
		global $wpdb;
740
741
		// sanitize to just valid MySQL column names
742
		$sanitized_columns = preg_grep( '/^[0-9,a-z,A-Z$_]+$/i', $columns );
743
744
		if ( $strip_non_ascii ) {
745
			$columns_sql = implode( ',', array_map( array( $this, 'strip_non_ascii_sql' ), $sanitized_columns ) );
746
		} else {
747
			$columns_sql = implode( ',', $sanitized_columns );
748
		}
749
750
		if ( null !== $min_id && null !== $max_id ) {
751
			if ( $min_id === $max_id ) {
752
				$min_id = intval( $min_id );
753
				$where_sql .= " AND $id_column = $min_id LIMIT 1";
754
			} else {
755
				$min_id = intval( $min_id );
756
				$max_id = intval( $max_id );
757
				$size = $max_id - $min_id;
758
				$where_sql .= " AND $id_column >= $min_id AND $id_column <= $max_id LIMIT $size";
759
			}
760
		} else {
761
			if ( null !== $min_id ) {
762
				$min_id = intval( $min_id );
763
				$where_sql .= " AND $id_column >= $min_id";
764
			}
765
766
			if ( null !== $max_id ) {
767
				$max_id = intval( $max_id );
768
				$where_sql .= " AND $id_column <= $max_id";
769
			}
770
		}
771
772
		$query = <<<ENDSQL
773
			SELECT CAST( SUM( CRC32( CONCAT_WS( '#', '%s', {$columns_sql} ) ) ) AS UNSIGNED INT )
774
			FROM $table
775
			WHERE $where_sql;
776
ENDSQL;
777
		$result = $wpdb->get_var( $wpdb->prepare( $query, $salt ) );
778
		if ( $wpdb->last_error ) {
779
			return new WP_Error( 'database_error', $wpdb->last_error );
780
		}
781
782
		return $result;
783
	}
784
785
	public function get_checksum_type() {
786
		return 'sum';
787
	}
788
789
	private function meta_count( $table, $where_sql, $min_id, $max_id ) {
790
		global $wpdb;
791
792
		if ( $min_id != null ) {
793
			$where_sql .= ' AND meta_id >= ' . intval( $min_id );
794
		}
795
796
		if ( $max_id != null ) {
797
			$where_sql .= ' AND meta_id <= ' . intval( $max_id );
798
		}
799
800
		return $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE $where_sql" );
801
	}
802
803
	/**
804
	 * Wraps a column name in SQL which strips non-ASCII chars.
805
	 * This helps normalize data to avoid checksum differences caused by
806
	 * badly encoded data in the DB
807
	 */
808
	function strip_non_ascii_sql( $column_name ) {
809
		return "REPLACE( CONVERT( $column_name USING ascii ), '?', '' )";
810
	}
811
812
	private function invalid_call() {
813
		$backtrace = debug_backtrace();
814
		$caller    = $backtrace[1]['function'];
815
		throw new Exception( "This function $caller is not supported on the WP Replicastore" );
816
	}
817
}
818