Completed
Push — update/sync-histogram-term-rel... ( 2a6044 )
by
unknown
07:02
created

Replicastore::term_relationship_count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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