Completed
Push — renovate/webpack-cli-3.x ( 961c1b...02322b )
by
unknown
06:55
created

Jetpack_Sync_WP_Replicastore::post_count()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21

Duplication

Lines 21
Ratio 100 %

Importance

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