Completed
Push — add/select-histogram-columns ( a94a20 )
by
unknown
10:07
created

Jetpack_Sync_WP_Replicastore::table_checksum()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 15
c 1
b 0
f 0
nc 4
nop 6
dl 0
loc 24
rs 8.9713
1
<?php
2
3
require_once 'interface.jetpack-sync-replicastore.php';
4
5
/**
6
 * An implementation of iJetpack_Sync_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 Jetpack_Sync_WP_Replicastore implements iJetpack_Sync_Replicastore {
10
11
12
	public function reset() {
13
		global $wpdb;
14
15
		$wpdb->query( "DELETE FROM $wpdb->posts" );
16
		$wpdb->query( "DELETE FROM $wpdb->comments" );
17
18
		// also need to delete terms from cache
19
		$term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" );
20
		foreach ( $term_ids as $term_id ) {
21
			wp_cache_delete( $term_id, 'terms' );
22
		}
23
24
		$wpdb->query( "DELETE FROM $wpdb->terms" );
25
26
		$wpdb->query( "DELETE FROM $wpdb->term_taxonomy" );
27
		$wpdb->query( "DELETE FROM $wpdb->term_relationships" );
28
29
		// callables and constants
30
		$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" );
31
		$wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" );
32
	}
33
34
	function full_sync_start() {
35
		$this->reset();
36
	}
37
38
	function full_sync_end( $checksum ) {
39
		// noop right now
40
	}
41
42 View Code Duplication
	public function post_count( $status = null, $min_id = null, $max_id = null ) {
43
		global $wpdb;
44
45
		$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...
46
47
		if ( $status ) {
48
			$where = "post_status = '" . esc_sql( $status ) . "'";
49
		} else {
50
			$where = '1=1';
51
		}
52
53
		if ( null != $min_id ) {
54
			$where .= ' AND ID >= ' . intval( $min_id );
55
		}
56
57
		if ( null != $max_id ) {
58
			$where .= ' AND ID <= ' . intval( $max_id );
59
		}
60
61
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
62
	}
63
64
	// TODO: actually use max_id/min_id
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
65
	public function get_posts( $status = null, $min_id = null, $max_id = null ) {
66
		$args = array( 'orderby' => 'ID', 'posts_per_page' => -1 );
67
68
		if ( $status ) {
69
			$args['post_status'] = $status;
70
		} else {
71
			$args['post_status'] = 'any';
72
		}
73
74
		return get_posts( $args );
75
	}
76
77
	public function get_post( $id ) {
78
		return get_post( $id );
79
	}
80
81
	public function upsert_post( $post, $silent = false ) {
82
		global $wpdb;
83
84
		// reject the post if it's not a WP_Post
85
		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...
86
			return;
87
		}
88
89
		$post = $post->to_array();
90
91
		// reject posts without an ID
92
		if ( ! isset( $post['ID'] ) ) {
93
			return;
94
		}
95
96
		$now     = current_time( 'mysql' );
97
		$now_gmt = get_gmt_from_date( $now );
98
99
		$defaults = array(
100
			'ID'                    => 0,
101
			'post_author'           => '0',
102
			'post_content'          => '',
103
			'post_content_filtered' => '',
104
			'post_title'            => '',
105
			'post_name'             => '',
106
			'post_excerpt'          => '',
107
			'post_status'           => 'draft',
108
			'post_type'             => 'post',
109
			'comment_status'        => 'closed',
110
			'comment_count'         => '0',
111
			'ping_status'           => '',
112
			'post_password'         => '',
113
			'to_ping'               => '',
114
			'pinged'                => '',
115
			'post_parent'           => 0,
116
			'menu_order'            => 0,
117
			'guid'                  => '',
118
			'post_date'             => $now,
119
			'post_date_gmt'         => $now_gmt,
120
			'post_modified'         => $now,
121
			'post_modified_gmt'     => $now_gmt,
122
		);
123
124
		$post = array_intersect_key( $post, $defaults );
125
126
		$post = sanitize_post( $post, 'db' );
127
128
		unset( $post['filter'] );
129
130
		$exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
131
132
		if ( $exists ) {
133
			$wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
134
		} else {
135
			$wpdb->insert( $wpdb->posts, $post );
136
		}
137
138
		clean_post_cache( $post['ID'] );
139
	}
140
141
	public function delete_post( $post_id ) {
142
		wp_delete_post( $post_id, true );
143
	}
144
145
	public function posts_checksum( $min_id = null, $max_id = null ) {
146
		global $wpdb;
147
		return $this->table_checksum( $wpdb->posts, Jetpack_Sync_Defaults::$default_post_checksum_columns , 'ID', Jetpack_Sync_Defaults::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...
148
	}
149
150 View Code Duplication
	public function comment_count( $status = null, $min_id = null, $max_id = null ) {
151
		global $wpdb;
152
153
		$comment_approved = $this->comment_status_to_approval_value( $status );
154
155
		if ( $comment_approved !== false ) {
156
			$where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
157
		} else {
158
			$where = '1=1';
159
		}
160
161
		if ( $min_id != null ) {
162
			$where .= ' AND comment_ID >= ' . intval( $min_id );
163
		}
164
165
		if ( $max_id != null ) {
166
			$where .= ' AND comment_ID <= ' . intval( $max_id );
167
		}
168
169
		return $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
170
	}
171
172
	private function comment_status_to_approval_value( $status ) {
173
		switch ( $status ) {
174
			case 'approve':
175
				return '1';
176
			case 'hold':
177
				return '0';
178
			case 'spam':
179
				return 'spam';
180
			case 'trash':
181
				return 'trash';
182
			case 'any':
183
				return false;
184
			case 'all':
185
				return false;
186
			default:
187
				return false;
188
		}
189
	}
190
191
	// TODO: actually use max_id/min_id
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
192
	public function get_comments( $status = null, $min_id = null, $max_id = null ) {
193
		$args = array( 'orderby' => 'ID', 'status' => 'all' );
194
195
		if ( $status ) {
196
			$args['status'] = $status;
197
		}
198
199
		return get_comments( $args );
200
	}
201
202
	public function get_comment( $id ) {
203
		return WP_Comment::get_instance( $id );
204
	}
205
206
	public function upsert_comment( $comment ) {
207
		global $wpdb, $wp_version;
208
209
		if ( version_compare( $wp_version, '4.4', '<' ) ) {
210
			$comment = (array) $comment;
211
		} else {
212
			// WP 4.4 introduced the WP_Comment Class
213
			$comment = $comment->to_array();
214
		}
215
216
		// filter by fields on comment table
217
		$comment_fields_whitelist = array(
218
			'comment_ID',
219
			'comment_post_ID',
220
			'comment_author',
221
			'comment_author_email',
222
			'comment_author_url',
223
			'comment_author_IP',
224
			'comment_date',
225
			'comment_date_gmt',
226
			'comment_content',
227
			'comment_karma',
228
			'comment_approved',
229
			'comment_agent',
230
			'comment_type',
231
			'comment_parent',
232
			'user_id',
233
		);
234
235
		foreach ( $comment as $key => $value ) {
236
			if ( ! in_array( $key, $comment_fields_whitelist ) ) {
237
				unset( $comment[ $key ] );
238
			}
239
		}
240
241
		$exists = $wpdb->get_var(
242
			$wpdb->prepare(
243
				"SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
244
				$comment['comment_ID']
245
			)
246
		);
247
248
		if ( $exists ) {
249
			$wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
250
		} else {
251
			$wpdb->insert( $wpdb->comments, $comment );
252
		}
253
254
		wp_update_comment_count( $comment['comment_post_ID'] );
255
	}
256
257
	public function trash_comment( $comment_id ) {
258
		wp_delete_comment( $comment_id );
259
	}
260
261
	public function delete_comment( $comment_id ) {
262
		wp_delete_comment( $comment_id, true );
263
	}
264
265
	public function spam_comment( $comment_id ) {
266
		wp_spam_comment( $comment_id );
267
	}
268
269
	public function comments_checksum( $min_id = null, $max_id = null ) {
270
		global $wpdb;
271
		return $this->table_checksum( $wpdb->comments, Jetpack_Sync_Defaults::$default_comment_checksum_columns, 'comment_ID', '1=1', $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...
272
	}
273
274
	public function options_checksum() {
275
		global $wpdb;
276
277
		$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...
278
		$where_sql = "option_name IN ( $options_whitelist )";
279
280
		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...
281
	}
282
283
284
	public function update_option( $option, $value ) {
285
		return update_option( $option, $value );
286
	}
287
288
	public function get_option( $option, $default = false ) {
289
		return get_option( $option, $default );
290
	}
291
292
	public function delete_option( $option ) {
293
		return delete_option( $option );
294
	}
295
296
	public function set_theme_support( $theme_support ) {
297
		// noop
298
	}
299
300
	public function current_theme_supports( $feature ) {
301
		return current_theme_supports( $feature );
302
	}
303
304
	public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
305
		return get_metadata( $type, $object_id, $meta_key, $single );
306
	}
307
308
	/**
309
	 *
310
	 * Stores remote meta key/values alongside an ID mapping key
311
	 *
312
	 * @param $type
313
	 * @param $object_id
314
	 * @param $meta_key
315
	 * @param $meta_value
316
	 * @param $meta_id
317
	 *
318
	 * @return bool
319
	 */
320
	public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
321
322
		$table = _get_meta_table( $type );
323
		if ( ! $table ) {
324
			return false;
325
		}
326
327
		global $wpdb;
328
329
		$exists = $wpdb->get_var( $wpdb->prepare(
330
			"SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
331
			$meta_id
332
		) );
333
334
		if ( $exists ) {
335
			$wpdb->update( $table, array(
336
				'meta_key'   => $meta_key,
337
				'meta_value' => serialize( $meta_value ),
338
			), array( 'meta_id' => $meta_id ) );
339
		} else {
340
			$object_id_field = $type . '_id';
341
			$wpdb->insert( $table, array(
342
				'meta_id'        => $meta_id,
343
				$object_id_field => $object_id,
344
				'meta_key'       => $meta_key,
345
				'meta_value'     => serialize( $meta_value ),
346
			) );
347
		}
348
349
		wp_cache_delete( $object_id, $type . '_meta' );
350
351
		return true;
352
	}
353
354
	public function delete_metadata( $type, $object_id, $meta_ids ) {
355
		global $wpdb;
356
357
		$table = _get_meta_table( $type );
358
		if ( ! $table ) {
359
			return false;
360
		}
361
362
		foreach ( $meta_ids as $meta_id ) {
363
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
364
		}
365
366
		// if we don't have an object ID what do we do - invalidate ALL meta?
367
		if ( $object_id ) {
368
			wp_cache_delete( $object_id, $type . '_meta' );
369
		}
370
	}
371
372
	// constants
373
	public function get_constant( $constant ) {
374
		$value = get_option( 'jetpack_constant_' . $constant );
375
376
		if ( $value ) {
377
			return $value;
378
		}
379
380
		return null;
381
	}
382
383
	public function set_constant( $constant, $value ) {
384
		update_option( 'jetpack_constant_' . $constant, $value );
385
	}
386
387
	public function get_updates( $type ) {
388
		$all_updates = get_option( 'jetpack_updates', array() );
389
390
		if ( isset( $all_updates[ $type ] ) ) {
391
			return $all_updates[ $type ];
392
		} else {
393
			return null;
394
		}
395
	}
396
397
	public function set_updates( $type, $updates ) {
398
		$all_updates          = get_option( 'jetpack_updates', array() );
399
		$all_updates[ $type ] = $updates;
400
		update_option( 'jetpack_updates', $all_updates );
401
	}
402
403
	// functions
404
	public function get_callable( $name ) {
405
		$value = get_option( 'jetpack_' . $name );
406
407
		if ( $value ) {
408
			return $value;
409
		}
410
411
		return null;
412
	}
413
414
	public function set_callable( $name, $value ) {
415
		update_option( 'jetpack_' . $name, $value );
416
	}
417
418
	// network options
419
	public function get_site_option( $option ) {
420
		return get_option( 'jetpack_network_' . $option );
421
	}
422
423
	public function update_site_option( $option, $value ) {
424
		return update_option( 'jetpack_network_' . $option, $value );
425
	}
426
427
	public function delete_site_option( $option ) {
428
		return delete_option( 'jetpack_network_' . $option );
429
	}
430
431
	// terms
432
	// terms
433
	public function get_terms( $taxonomy ) {
434
		return get_terms( $taxonomy );
435
	}
436
437
	public function get_term( $taxonomy, $term_id, $is_term_id = true ) {
438
		$t = $this->ensure_taxonomy( $taxonomy );
439
		if ( ! $t || is_wp_error( $t ) ) {
440
			return $t;
441
		}
442
443
		return get_term( $term_id, $taxonomy );
444
	}
445
446
	private function ensure_taxonomy( $taxonomy ) {
447
		if ( ! taxonomy_exists( $taxonomy ) ) {
448
			// try re-registering synced taxonomies
449
			$taxonomies = $this->get_callable( 'taxonomies' );
450
			if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
451
				// doesn't exist, or somehow hasn't been synced
452
				return new WP_Error( 'invalid_taxonomy', "The taxonomy '$taxonomy' doesn't exist" );
453
			}
454
			$t = $taxonomies[ $taxonomy ];
455
456
			return register_taxonomy(
457
				$taxonomy,
458
				$t->object_type,
459
				(array) $t
460
			);
461
		}
462
463
		return true;
464
	}
465
466
	public function get_the_terms( $object_id, $taxonomy ) {
467
		return get_the_terms( $object_id, $taxonomy );
468
	}
469
470
	public function update_term( $term_object ) {
471
		$taxonomy = $term_object->taxonomy;
472
		global $wpdb;
473
		$exists = $wpdb->get_var( $wpdb->prepare(
474
			"SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
475
			$term_object->term_id
476
		) );
477
		if ( ! $exists ) {
478
			$term_object   = sanitize_term( clone( $term_object ), $taxonomy, 'db' );
479
			$term          = array(
480
				'term_id'    => $term_object->term_id,
481
				'name'       => $term_object->name,
482
				'slug'       => $term_object->slug,
483
				'term_group' => $term_object->term_group,
484
			);
485
			$term_taxonomy = array(
486
				'term_taxonomy_id' => $term_object->term_taxonomy_id,
487
				'term_id'          => $term_object->term_id,
488
				'taxonomy'         => $term_object->taxonomy,
489
				'description'      => $term_object->description,
490
				'parent'           => (int) $term_object->parent,
491
				'count'            => (int) $term_object->count,
492
			);
493
			$wpdb->insert( $wpdb->terms, $term );
494
			$wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
495
496
			return true;
497
		}
498
499
		return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
500
	}
501
502
	public function delete_term( $term_id, $taxonomy ) {
503
		return wp_delete_term( $term_id, $taxonomy );
504
	}
505
506
	public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
507
		wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
508
	}
509
510
	public function delete_object_terms( $object_id, $tt_ids ) {
511
		global $wpdb;
512
513
		if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
514
			$taxonomies = array();
515
			foreach ( $tt_ids as $tt_id ) {
516
				$term                            = get_term_by( 'term_taxonomy_id', $tt_id );
517
				$taxonomies[ $term->taxonomy ][] = $tt_id;
518
			}
519
			$in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
520
521
			/**
522
			 * Fires immediately before an object-term relationship is deleted.
523
			 *
524
			 * @since 2.9.0
525
			 *
526
			 * @param int $object_id Object ID.
527
			 * @param array $tt_ids An array of term taxonomy IDs.
528
			 */
529
			do_action( 'delete_term_relationships', $object_id, $tt_ids );
530
			$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
531
			foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
532
				wp_cache_delete( $object_id, $taxonomy . '_relationships' );
533
				/**
534
				 * Fires immediately after an object-term relationship is deleted.
535
				 *
536
				 * @since 2.9.0
537
				 *
538
				 * @param int $object_id Object ID.
539
				 * @param array $tt_ids An array of term taxonomy IDs.
540
				 */
541
				do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
542
				wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
543
			}
544
545
			return (bool) $deleted;
546
		}
547
548
		return false;
549
	}
550
551
	// users
552
	public function user_count() {
553
554
	}
555
556
	public function get_user( $user_id ) {
557
		return WP_User::get_instance( $user_id );
558
	}
559
560
	public function upsert_user( $user ) {
561
		$this->invalid_call();
562
	}
563
564
	public function delete_user( $user_id ) {
565
		$this->invalid_call();
566
	}
567
568
	public function get_allowed_mime_types( $user_id ) {
569
570
	}
571
572
	public function checksum_all() {
573
		return array(
574
			'posts'    => $this->posts_checksum(),
575
			'comments' => $this->comments_checksum(),
576
			'options'  => $this->options_checksum(),
577
		);
578
	}
579
580
	function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null, $columns = null ) {
581
		global $wpdb;
582
583
		$wpdb->queries = array();
584
585
		switch( $object_type ) {
586
			case "posts":
587
				$object_count = $this->post_count( null, $start_id, $end_id );
588
				$object_table = $wpdb->posts;
589
				$id_field     = 'ID';
590
				if ( empty( $columns ) ) {
591
					$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...
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
592
				}
593
				break;
594
			case "comments":
595
				$object_count = $this->comment_count( null, $start_id, $end_id );
596
				$object_table = $wpdb->comments;
597
				$id_field     = 'comment_ID';
598
				if ( empty( $columns ) ) {
599
					$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...
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
600
				}
601
				break;
602
			default:
603
				return false;
604
		}
605
606
		$bucket_size  = intval( ceil( $object_count / $buckets ) );
607
		$query_offset = 0;
608
		$histogram    = array();
609
610
		$where = '1=1';
611
612
		if ( $start_id ) {
613
			$where .= " AND $id_field >= " . intval( $start_id );
614
		}
615
616
		if ( $end_id ) {
617
			$where .= " AND $id_field <= " . intval( $end_id );
618
		}
619
620
		do {
621
			list( $first_id, $last_id ) = $wpdb->get_row(
622
				"SELECT MIN($id_field) as min_id, MAX($id_field) as max_id FROM ( SELECT $id_field FROM $object_table WHERE $where ORDER BY $id_field ASC LIMIT $query_offset, $bucket_size ) as ids",
623
				ARRAY_N
624
			);
625
626
			// get the checksum value
627
			$value = $this->table_checksum( $object_table, $columns, $id_field, '1=1', $first_id, $last_id );
628
629
			if ( $first_id === null || $last_id === null ) {
630
				break;
631
			} elseif ( $first_id === $last_id ) {
632
				$histogram[ $first_id ] = $value;
633
			} else {
634
				$histogram[ "{$first_id}-{$last_id}" ] = $value;
635
			}
636
637
			$query_offset += $bucket_size;
638
		} while ( true );
639
640
		return $histogram;
641
	}
642
643
	private function table_checksum( $table, $columns, $id_column, $where_sql = '1=1', $min_id = null, $max_id = null ) {
644
		global $wpdb;
645
646
		// sanitize to just valid MySQL column names
647
		$columns_sql = implode( ',', preg_grep ( '/^[0-9,a-z,A-Z$_]+$/i', $columns ) );
648
649
		if ( $min_id !== null ) {
650
			$min_id = intval( $min_id );
651
			$where_sql .= " AND $id_column >= $min_id";
652
		}
653
654
		if ( $max_id !== null ) {
655
			$max_id = intval( $max_id );
656
			$where_sql .= " AND $id_column <= $max_id";
657
		}
658
659
		$query = <<<ENDSQL
660
			SELECT CONV(BIT_XOR(CRC32(CONCAT({$columns_sql}))), 10, 16)
661
				FROM $table
662
				WHERE $where_sql
663
ENDSQL;
664
665
		return $wpdb->get_var( $query );
666
	}
667
668
	private function invalid_call() {
669
		$backtrace = debug_backtrace();
670
		$caller    = $backtrace[1]['function'];
671
		throw new Exception( "This function $caller is not supported on the WP Replicastore" );
672
	}
673
}
674