PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-sync/src/class-replicastore.php +1468 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,1468 @@
1 +<?php
2 +/**
3 + * Sync replicastore.
4 + *
5 + * @package automattic/jetpack-sync
6 + */
7 +
8 +namespace Automattic\Jetpack\Sync;
9 +
10 +use Automattic\Jetpack\Sync\Replicastore\Table_Checksum;
11 +use Automattic\Jetpack\Sync\Replicastore\Table_Checksum_Usermeta;
12 +use Automattic\Jetpack\Sync\Replicastore\Table_Checksum_Users;
13 +use Exception;
14 +use WP_Error;
15 +
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
20 +/**
21 + * An implementation of Replicastore Interface which returns data stored in a WordPress.org DB.
22 + * This is useful to compare values in the local WP DB to values in the synced replica store
23 + */
24 +class Replicastore implements Replicastore_Interface {
25 + /**
26 + * Empty and reset the replicastore.
27 + *
28 + * @access public
29 + */
30 + public function reset() {
31 + global $wpdb;
32 +
33 + $wpdb->query( "DELETE FROM $wpdb->posts" );
34 +
35 + // Delete comments from cache.
36 + $comment_ids = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments" );
37 + if ( ! empty( $comment_ids ) ) {
38 + clean_comment_cache( $comment_ids );
39 + }
40 + $wpdb->query( "DELETE FROM $wpdb->comments" );
41 +
42 + // Also need to delete terms from cache.
43 + $term_ids = $wpdb->get_col( "SELECT term_id FROM $wpdb->terms" );
44 + foreach ( $term_ids as $term_id ) {
45 + wp_cache_delete( $term_id, 'terms' );
46 + }
47 +
48 + $wpdb->query( "DELETE FROM $wpdb->terms" );
49 +
50 + $wpdb->query( "DELETE FROM $wpdb->term_taxonomy" );
51 + $wpdb->query( "DELETE FROM $wpdb->term_relationships" );
52 +
53 + // Callables and constants.
54 + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'jetpack_%'" );
55 + $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key NOT LIKE '\_%'" );
56 + }
57 +
58 + /**
59 + * Ran when full sync has just started.
60 + *
61 + * @access public
62 + *
63 + * @param array $config Full sync configuration for this sync module.
64 + */
65 + public function full_sync_start( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
66 + $this->reset();
67 + }
68 +
69 + /**
70 + * Ran when full sync has just finished.
71 + *
72 + * @access public
73 + *
74 + * @param string $checksum Deprecated since 7.3.0.
75 + */
76 + public function full_sync_end( $checksum ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
77 + // Noop right now.
78 + }
79 +
80 + /**
81 + * Retrieve the number of terms.
82 + *
83 + * @access public
84 + *
85 + * @return int Number of terms.
86 + */
87 + public function term_count() {
88 + global $wpdb;
89 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
90 + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->terms" );
91 + }
92 +
93 + /**
94 + * Retrieve the number of rows in the `term_taxonomy` table.
95 + *
96 + * @access public
97 + *
98 + * @return int Number of terms.
99 + */
100 + public function term_taxonomy_count() {
101 + global $wpdb;
102 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
103 + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_taxonomy" );
104 + }
105 +
106 + /**
107 + * Retrieve the number of term relationships.
108 + *
109 + * @access public
110 + *
111 + * @return int Number of rows in the term relationships table.
112 + */
113 + public function term_relationship_count() {
114 + global $wpdb;
115 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
116 + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->term_relationships" );
117 + }
118 +
119 + /**
120 + * Retrieve the number of posts with a particular post status within a certain range.
121 + *
122 + * @access public
123 + *
124 + * @todo Prepare the SQL query before executing it.
125 + *
126 + * @param string $status Post status.
127 + * @param int $min_id Minimum post ID.
128 + * @param int $max_id Maximum post ID.
129 + * @return int Number of posts.
130 + */
131 + public function post_count( $status = null, $min_id = null, $max_id = null ) {
132 + global $wpdb;
133 +
134 + $where = '';
135 +
136 + if ( $status ) {
137 + $where = "post_status = '" . esc_sql( $status ) . "'";
138 + } else {
139 + $where = '1=1';
140 + }
141 +
142 + if ( ! empty( $min_id ) ) {
143 + $where .= ' AND ID >= ' . (int) $min_id;
144 + }
145 +
146 + if ( ! empty( $max_id ) ) {
147 + $where .= ' AND ID <= ' . (int) $max_id;
148 + }
149 +
150 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
151 + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts WHERE $where" );
152 + }
153 +
154 + /**
155 + * Retrieve the posts with a particular post status.
156 + *
157 + * @access public
158 + *
159 + * @todo Implement range and actually use max_id/min_id arguments.
160 + *
161 + * @param string $status Post status.
162 + * @param int $min_id Minimum post ID.
163 + * @param int $max_id Maximum post ID.
164 + * @return array Array of posts.
165 + */
166 + public function get_posts( $status = null, $min_id = null, $max_id = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
167 + $args = array(
168 + 'orderby' => 'ID',
169 + 'posts_per_page' => -1,
170 + );
171 +
172 + if ( $status ) {
173 + $args['post_status'] = $status;
174 + } else {
175 + $args['post_status'] = 'any';
176 + }
177 +
178 + return get_posts( $args );
179 + }
180 +
181 + /**
182 + * Retrieve a post object by the post ID.
183 + *
184 + * @access public
185 + *
186 + * @param int $id Post ID.
187 + * @return \WP_Post Post object.
188 + */
189 + public function get_post( $id ) {
190 + return get_post( $id );
191 + }
192 +
193 + /**
194 + * Update or insert a post.
195 + *
196 + * @access public
197 + *
198 + * @param \WP_Post $post Post object.
199 + * @param bool $silent Whether to perform a silent action. Not used in this implementation.
200 + */
201 + public function upsert_post( $post, $silent = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
202 + global $wpdb;
203 +
204 + // Reject the post if it's not a \WP_Post.
205 + if ( ! $post instanceof \WP_Post ) {
206 + return;
207 + }
208 +
209 + $post = $post->to_array();
210 +
211 + // Reject posts without an ID.
212 + if ( ! isset( $post['ID'] ) ) {
213 + return;
214 + }
215 +
216 + $now = current_time( 'mysql' );
217 + $now_gmt = get_gmt_from_date( $now );
218 +
219 + $defaults = array(
220 + 'ID' => 0,
221 + 'post_author' => '0',
222 + 'post_content' => '',
223 + 'post_content_filtered' => '',
224 + 'post_title' => '',
225 + 'post_name' => '',
226 + 'post_excerpt' => '',
227 + 'post_status' => 'draft',
228 + 'post_type' => 'post',
229 + 'comment_status' => 'closed',
230 + 'comment_count' => '0',
231 + 'ping_status' => '',
232 + 'post_password' => '',
233 + 'to_ping' => '',
234 + 'pinged' => '',
235 + 'post_parent' => 0,
236 + 'menu_order' => 0,
237 + 'guid' => '',
238 + 'post_date' => $now,
239 + 'post_date_gmt' => $now_gmt,
240 + 'post_modified' => $now,
241 + 'post_modified_gmt' => $now_gmt,
242 + );
243 +
244 + $post = array_intersect_key( $post, $defaults );
245 +
246 + $post = sanitize_post( $post, 'db' );
247 +
248 + unset( $post['filter'] );
249 +
250 + $exists = $wpdb->get_var( $wpdb->prepare( "SELECT EXISTS( SELECT 1 FROM $wpdb->posts WHERE ID = %d )", $post['ID'] ) );
251 +
252 + if ( $exists ) {
253 + $wpdb->update( $wpdb->posts, $post, array( 'ID' => $post['ID'] ) );
254 + } else {
255 + $wpdb->insert( $wpdb->posts, $post );
256 + }
257 +
258 + clean_post_cache( $post['ID'] );
259 + }
260 +
261 + /**
262 + * Delete a post by the post ID.
263 + *
264 + * @access public
265 + *
266 + * @param int $post_id Post ID.
267 + */
268 + public function delete_post( $post_id ) {
269 + wp_delete_post( $post_id, true );
270 + }
271 +
272 + /**
273 + * Retrieve the checksum for posts within a range.
274 + *
275 + * @access public
276 + *
277 + * @param int $min_id Minimum post ID.
278 + * @param int $max_id Maximum post ID.
279 + * @return int The checksum.
280 + */
281 + public function posts_checksum( $min_id = null, $max_id = null ) {
282 + return $this->summarize_checksum_histogram( $this->checksum_histogram( 'posts', null, $min_id, $max_id ) );
283 + }
284 +
285 + /**
286 + * Retrieve the checksum for post meta within a range.
287 + *
288 + * @access public
289 + *
290 + * @param int $min_id Minimum post meta ID.
291 + * @param int $max_id Maximum post meta ID.
292 + * @return int The checksum.
293 + */
294 + public function post_meta_checksum( $min_id = null, $max_id = null ) {
295 + return $this->summarize_checksum_histogram( $this->checksum_histogram( 'postmeta', null, $min_id, $max_id ) );
296 + }
297 +
298 + /**
299 + * Retrieve the number of comments with a particular comment status within a certain range.
300 + *
301 + * @access public
302 + *
303 + * @todo Prepare the SQL query before executing it.
304 + *
305 + * @param string $status Comment status.
306 + * @param int $min_id Minimum comment ID.
307 + * @param int $max_id Maximum comment ID.
308 + * @return int Number of comments.
309 + */
310 + public function comment_count( $status = null, $min_id = null, $max_id = null ) {
311 + global $wpdb;
312 +
313 + $comment_approved = $this->comment_status_to_approval_value( $status );
314 +
315 + if ( false !== $comment_approved ) {
316 + $where = "comment_approved = '" . esc_sql( $comment_approved ) . "'";
317 + } else {
318 + $where = '1=1';
319 + }
320 +
321 + if ( ! empty( $min_id ) ) {
322 + $where .= ' AND comment_ID >= ' . (int) $min_id;
323 + }
324 +
325 + if ( ! empty( $max_id ) ) {
326 + $where .= ' AND comment_ID <= ' . (int) $max_id;
327 + }
328 +
329 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
330 + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE $where" );
331 + }
332 +
333 + /**
334 + * Translate a comment status to a value of the comment_approved field.
335 + *
336 + * @access protected
337 + *
338 + * @param string $status Comment status.
339 + * @return string|bool New comment_approved value, false if the status doesn't affect it.
340 + */
341 + protected function comment_status_to_approval_value( $status ) {
342 + switch ( (string) $status ) {
343 + case 'approve':
344 + case '1':
345 + return '1';
346 + case 'hold':
347 + case '0':
348 + return '0';
349 + case 'spam':
350 + return 'spam';
351 + case 'trash':
352 + return 'trash';
353 + case 'post-trashed':
354 + return 'post-trashed';
355 + case 'any':
356 + case 'all':
357 + default:
358 + return false;
359 + }
360 + }
361 +
362 + /**
363 + * Retrieve the comments with a particular comment status.
364 + *
365 + * @access public
366 + *
367 + * @todo Implement range and actually use max_id/min_id arguments.
368 + *
369 + * @param string $status Comment status.
370 + * @param int $min_id Minimum comment ID.
371 + * @param int $max_id Maximum comment ID.
372 + * @return array Array of comments.
373 + */
374 + public function get_comments( $status = null, $min_id = null, $max_id = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
375 + $args = array(
376 + 'orderby' => 'ID',
377 + 'status' => 'all',
378 + );
379 +
380 + if ( $status ) {
381 + $args['status'] = $status;
382 + }
383 +
384 + return get_comments( $args );
385 + }
386 +
387 + /**
388 + * Retrieve a comment object by the comment ID.
389 + *
390 + * @access public
391 + *
392 + * @param int $id Comment ID.
393 + * @return \WP_Comment Comment object.
394 + */
395 + public function get_comment( $id ) {
396 + return \WP_Comment::get_instance( $id );
397 + }
398 +
399 + /**
400 + * Update or insert a comment.
401 + *
402 + * @access public
403 + *
404 + * @param \WP_Comment $comment Comment object.
405 + */
406 + public function upsert_comment( $comment ) {
407 + global $wpdb;
408 +
409 + $comment = $comment->to_array();
410 +
411 + // Filter by fields on comment table.
412 + $comment_fields_whitelist = array(
413 + 'comment_ID',
414 + 'comment_post_ID',
415 + 'comment_author',
416 + 'comment_author_email',
417 + 'comment_author_url',
418 + 'comment_author_IP',
419 + 'comment_date',
420 + 'comment_date_gmt',
421 + 'comment_content',
422 + 'comment_karma',
423 + 'comment_approved',
424 + 'comment_agent',
425 + 'comment_type',
426 + 'comment_parent',
427 + 'user_id',
428 + );
429 +
430 + foreach ( $comment as $key => $value ) {
431 + if ( ! in_array( $key, $comment_fields_whitelist, true ) ) {
432 + unset( $comment[ $key ] );
433 + }
434 + }
435 +
436 + $exists = $wpdb->get_var(
437 + $wpdb->prepare(
438 + "SELECT EXISTS( SELECT 1 FROM $wpdb->comments WHERE comment_ID = %d )",
439 + $comment['comment_ID']
440 + )
441 + );
442 +
443 + if ( $exists ) {
444 + $wpdb->update( $wpdb->comments, $comment, array( 'comment_ID' => $comment['comment_ID'] ) );
445 + } else {
446 + $wpdb->insert( $wpdb->comments, $comment );
447 + }
448 + // Remove comment from cache.
449 + clean_comment_cache( $comment['comment_ID'] );
450 +
451 + wp_update_comment_count( $comment['comment_post_ID'] );
452 + }
453 +
454 + /**
455 + * Trash a comment by the comment ID.
456 + *
457 + * @access public
458 + *
459 + * @param int $comment_id Comment ID.
460 + */
461 + public function trash_comment( $comment_id ) {
462 + wp_delete_comment( $comment_id );
463 + }
464 +
465 + /**
466 + * Delete a comment by the comment ID.
467 + *
468 + * @access public
469 + *
470 + * @param int $comment_id Comment ID.
471 + */
472 + public function delete_comment( $comment_id ) {
473 + wp_delete_comment( $comment_id, true );
474 + }
475 +
476 + /**
477 + * Mark a comment by the comment ID as spam.
478 + *
479 + * @access public
480 + *
481 + * @param int $comment_id Comment ID.
482 + */
483 + public function spam_comment( $comment_id ) {
484 + wp_spam_comment( $comment_id );
485 + }
486 +
487 + /**
488 + * Trash the comments of a post.
489 + *
490 + * @access public
491 + *
492 + * @param int $post_id Post ID.
493 + * @param array $statuses Post statuses. Not used in this implementation.
494 + */
495 + public function trashed_post_comments( $post_id, $statuses ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
496 + wp_trash_post_comments( $post_id );
497 + }
498 +
499 + /**
500 + * Untrash the comments of a post.
501 + *
502 + * @access public
503 + *
504 + * @param int $post_id Post ID.
505 + */
506 + public function untrashed_post_comments( $post_id ) {
507 + wp_untrash_post_comments( $post_id );
508 + }
509 +
510 + /**
511 + * Retrieve the checksum for comments within a range.
512 + *
513 + * @access public
514 + *
515 + * @param int $min_id Minimum comment ID.
516 + * @param int $max_id Maximum comment ID.
517 + * @return int The checksum.
518 + */
519 + public function comments_checksum( $min_id = null, $max_id = null ) {
520 + return $this->summarize_checksum_histogram( $this->checksum_histogram( 'comments', null, $min_id, $max_id ) );
521 + }
522 +
523 + /**
524 + * Retrieve the checksum for comment meta within a range.
525 + *
526 + * @access public
527 + *
528 + * @param int $min_id Minimum comment meta ID.
529 + * @param int $max_id Maximum comment meta ID.
530 + * @return int The checksum.
531 + */
532 + public function comment_meta_checksum( $min_id = null, $max_id = null ) {
533 + return $this->summarize_checksum_histogram( $this->checksum_histogram( 'commentmeta', null, $min_id, $max_id ) );
534 + }
535 +
536 + /**
537 + * Update the value of an option.
538 + *
539 + * @access public
540 + *
541 + * @param string $option Option name.
542 + * @param mixed $value Option value.
543 + * @return bool False if value was not updated and true if value was updated.
544 + */
545 + public function update_option( $option, $value ) {
546 + return update_option( $option, $value );
547 + }
548 +
549 + /**
550 + * Retrieve an option value based on an option name.
551 + *
552 + * @access public
553 + *
554 + * @param string $option Name of option to retrieve.
555 + * @param mixed $default Optional. Default value to return if the option does not exist.
556 + * @return mixed Value set for the option.
557 + */
558 + public function get_option( $option, $default = false ) {
559 + return get_option( $option, $default );
560 + }
561 +
562 + /**
563 + * Remove an option by name.
564 + *
565 + * @access public
566 + *
567 + * @param string $option Name of option to remove.
568 + * @return bool True, if option is successfully deleted. False on failure.
569 + */
570 + public function delete_option( $option ) {
571 + return delete_option( $option );
572 + }
573 +
574 + /**
575 + * Change the info of the current theme.
576 + *
577 + * @access public
578 + *
579 + * @param array $theme_info Theme info array.
580 + */
581 + public function set_theme_info( $theme_info ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
582 + // Noop.
583 + }
584 +
585 + /**
586 + * Whether the current theme supports a certain feature.
587 + *
588 + * @access public
589 + *
590 + * @param string $feature Name of the feature.
591 + */
592 + public function current_theme_supports( $feature ) {
593 + return current_theme_supports( $feature );
594 + }
595 +
596 + /**
597 + * Retrieve metadata for the specified object.
598 + *
599 + * @access public
600 + *
601 + * @param string $type Meta type.
602 + * @param int $object_id ID of the object.
603 + * @param string $meta_key Meta key.
604 + * @param bool $single If true, return only the first value of the specified meta_key.
605 + *
606 + * @return mixed Single metadata value, or array of values.
607 + */
608 + public function get_metadata( $type, $object_id, $meta_key = '', $single = false ) {
609 + return get_metadata( $type, $object_id, $meta_key, $single );
610 + }
611 +
612 + /**
613 + * Stores remote meta key/values alongside an ID mapping key.
614 + *
615 + * @access public
616 + *
617 + * @todo Refactor to not use interpolated values when preparing the SQL query.
618 + *
619 + * @param string $type Meta type.
620 + * @param int $object_id ID of the object.
621 + * @param string $meta_key Meta key.
622 + * @param mixed $meta_value Meta value.
623 + * @param int $meta_id ID of the meta.
624 + *
625 + * @return bool False if meta table does not exist, true otherwise.
626 + */
627 + public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id ) {
628 + $table = _get_meta_table( $type );
629 + if ( ! $table ) {
630 + return false;
631 + }
632 +
633 + global $wpdb;
634 +
635 + $exists = $wpdb->get_var(
636 + $wpdb->prepare(
637 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
638 + "SELECT EXISTS( SELECT 1 FROM $table WHERE meta_id = %d )",
639 + $meta_id
640 + )
641 + );
642 +
643 + if ( $exists ) {
644 + $wpdb->update(
645 + $table,
646 + array(
647 + 'meta_key' => $meta_key,
648 + 'meta_value' => maybe_serialize( $meta_value ),
649 + ),
650 + array( 'meta_id' => $meta_id )
651 + );
652 + } else {
653 + $object_id_field = $type . '_id';
654 + $wpdb->insert(
655 + $table,
656 + array(
657 + 'meta_id' => $meta_id,
658 + $object_id_field => $object_id,
659 + 'meta_key' => $meta_key,
660 + 'meta_value' => maybe_serialize( $meta_value ),
661 + )
662 + );
663 + }
664 +
665 + wp_cache_delete( $object_id, $type . '_meta' );
666 +
667 + return true;
668 + }
669 +
670 + /**
671 + * Delete metadata for the specified object.
672 + *
673 + * @access public
674 + *
675 + * @todo Refactor to not use interpolated values when preparing the SQL query.
676 + *
677 + * @param string $type Meta type.
678 + * @param int $object_id ID of the object.
679 + * @param array $meta_ids IDs of the meta objects to delete.
680 + */
681 + public function delete_metadata( $type, $object_id, $meta_ids ) {
682 + global $wpdb;
683 +
684 + $table = _get_meta_table( $type );
685 + if ( ! $table ) {
686 + return false;
687 + }
688 +
689 + foreach ( $meta_ids as $meta_id ) {
690 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
691 + $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE meta_id = %d", $meta_id ) );
692 + }
693 +
694 + // If we don't have an object ID what do we do - invalidate ALL meta?
695 + if ( $object_id ) {
696 + wp_cache_delete( $object_id, $type . '_meta' );
697 + }
698 + }
699 +
700 + /**
701 + * Delete metadata with a certain key for the specified objects.
702 + *
703 + * @access public
704 + *
705 + * @todo Test this out to make sure it works as expected.
706 + * @todo Refactor to not use interpolated values when preparing the SQL query.
707 + *
708 + * @param string $type Meta type.
709 + * @param array $object_ids IDs of the objects.
710 + * @param string $meta_key Meta key.
711 + */
712 + public function delete_batch_metadata( $type, $object_ids, $meta_key ) {
713 + global $wpdb;
714 +
715 + $table = _get_meta_table( $type );
716 + if ( ! $table ) {
717 + return false;
718 + }
719 + $column = sanitize_key( $type . '_id' );
720 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
721 + $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE $column IN (%s) && meta_key = %s", implode( ',', $object_ids ), $meta_key ) );
722 +
723 + // If we don't have an object ID what do we do - invalidate ALL meta?
724 + foreach ( $object_ids as $object_id ) {
725 + wp_cache_delete( $object_id, $type . '_meta' );
726 + }
727 + }
728 +
729 + /**
730 + * Delete metadata with a certain key and value for all objects.
731 + *
732 + * @access public
733 + *
734 + * @param string $type Meta type.
735 + * @param string $meta_key Meta key.
736 + * @param mixed $meta_value Meta value to match.
737 + */
738 + public function delete_metadata_by_key_value( $type, $meta_key, $meta_value ) {
739 + global $wpdb;
740 +
741 + $table = _get_meta_table( $type );
742 + if ( ! $table ) {
743 + return false;
744 + }
745 +
746 + if ( '' === $meta_value || null === $meta_value || false === $meta_value ) {
747 + return false;
748 + }
749 +
750 + $column = sanitize_key( $type . '_id' );
751 + $meta_value = maybe_serialize( $meta_value );
752 +
753 + $object_ids = $wpdb->get_col( $wpdb->prepare( 'SELECT DISTINCT %i FROM %i WHERE meta_key = %s AND meta_value = %s', $column, $table, $meta_key, $meta_value ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct replica query is needed to identify caches invalidated below.
754 + $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct replica mutation.
755 + $table,
756 + array(
757 + 'meta_key' => $meta_key,
758 + 'meta_value' => $meta_value,
759 + ),
760 + array( '%s', '%s' )
761 + );
762 +
763 + foreach ( $object_ids as $object_id ) {
764 + wp_cache_delete( $object_id, $type . '_meta' );
765 + }
766 + }
767 +
768 + /**
769 + * Retrieve value of a constant based on the constant name.
770 + *
771 + * We explicitly return null instead of false if the constant doesn't exist.
772 + *
773 + * @access public
774 + *
775 + * @param string $constant Name of constant to retrieve.
776 + * @return mixed Value set for the constant.
777 + */
778 + public function get_constant( $constant ) {
779 + $value = get_option( 'jetpack_constant_' . $constant );
780 +
781 + if ( $value ) {
782 + return $value;
783 + }
784 +
785 + return null;
786 + }
787 +
788 + /**
789 + * Set the value of a constant.
790 + *
791 + * @access public
792 + *
793 + * @param string $constant Name of constant to retrieve.
794 + * @param mixed $value Value set for the constant.
795 + */
796 + public function set_constant( $constant, $value ) {
797 + update_option( 'jetpack_constant_' . $constant, $value );
798 + }
799 +
800 + /**
801 + * Retrieve the number of the available updates of a certain type.
802 + * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
803 + *
804 + * @access public
805 + *
806 + * @param string $type Type of updates to retrieve.
807 + * @return int|null Number of updates available, `null` if type is invalid or missing.
808 + */
809 + public function get_updates( $type ) {
810 + $all_updates = get_option( 'jetpack_updates', array() );
811 +
812 + if ( isset( $all_updates[ $type ] ) ) {
813 + return $all_updates[ $type ];
814 + } else {
815 + return null;
816 + }
817 + }
818 +
819 + /**
820 + * Set the available updates of a certain type.
821 + * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
822 + *
823 + * @access public
824 + *
825 + * @param string $type Type of updates to set.
826 + * @param int $updates Total number of updates.
827 + */
828 + public function set_updates( $type, $updates ) {
829 + $all_updates = get_option( 'jetpack_updates', array() );
830 + $all_updates[ $type ] = $updates;
831 + update_option( 'jetpack_updates', $all_updates );
832 + }
833 +
834 + /**
835 + * Retrieve a callable value based on its name.
836 + *
837 + * @access public
838 + *
839 + * @param string $name Name of the callable to retrieve.
840 + * @return mixed Value of the callable.
841 + */
842 + public function get_callable( $name ) {
843 + $value = get_option( 'jetpack_' . $name );
844 +
845 + if ( $value ) {
846 + return $value;
847 + }
848 +
849 + return null;
850 + }
851 +
852 + /**
853 + * Update the value of a callable.
854 + *
855 + * @access public
856 + *
857 + * @param string $name Callable name.
858 + * @param mixed $value Callable value.
859 + */
860 + public function set_callable( $name, $value ) {
861 + update_option( 'jetpack_' . $name, $value );
862 + }
863 +
864 + /**
865 + * Retrieve a network option value based on a network option name.
866 + *
867 + * @access public
868 + *
869 + * @param string $option Name of network option to retrieve.
870 + * @return mixed Value set for the network option.
871 + */
872 + public function get_site_option( $option ) {
873 + return get_option( 'jetpack_network_' . $option );
874 + }
875 +
876 + /**
877 + * Update the value of a network option.
878 + *
879 + * @access public
880 + *
881 + * @param string $option Network option name.
882 + * @param mixed $value Network option value.
883 + * @return bool False if value was not updated and true if value was updated.
884 + */
885 + public function update_site_option( $option, $value ) {
886 + return update_option( 'jetpack_network_' . $option, $value );
887 + }
888 +
889 + /**
890 + * Remove a network option by name.
891 + *
892 + * @access public
893 + *
894 + * @param string $option Name of option to remove.
895 + * @return bool True, if option is successfully deleted. False on failure.
896 + */
897 + public function delete_site_option( $option ) {
898 + return delete_option( 'jetpack_network_' . $option );
899 + }
900 +
901 + /**
902 + * Retrieve the terms from a particular taxonomy.
903 + *
904 + * @access public
905 + *
906 + * @param string $taxonomy Taxonomy slug.
907 + *
908 + * @return array|WP_Error Array of terms or WP_Error object on failure.
909 + */
910 + public function get_terms( $taxonomy ) {
911 + $t = $this->ensure_taxonomy( $taxonomy );
912 + if ( ! $t || is_wp_error( $t ) ) {
913 + return $t;
914 + }
915 + return get_terms( $taxonomy );
916 + }
917 +
918 + /**
919 + * Retrieve a particular term.
920 + *
921 + * @access public
922 + *
923 + * @param string|false $taxonomy Taxonomy slug.
924 + * @param int $term_id ID of the term.
925 + * @param string $term_key ID Field `term_id` or `term_taxonomy_id`.
926 + *
927 + * @return \WP_Term|WP_Error Term object on success, \WP_Error object on failure.
928 + */
929 + public function get_term( $taxonomy, $term_id, $term_key = 'term_id' ) {
930 +
931 + // Full Sync will pass false for the $taxonomy so a check for term_taxonomy_id is needed before ensure_taxonomy.
932 + if ( 'term_taxonomy_id' === $term_key ) {
933 + return get_term_by( 'term_taxonomy_id', $term_id );
934 + }
935 +
936 + $t = $this->ensure_taxonomy( $taxonomy );
937 + if ( ! $t || is_wp_error( $t ) ) {
938 + return $t;
939 + }
940 +
941 + return get_term( $term_id, $taxonomy );
942 + }
943 +
944 + /**
945 + * Verify a taxonomy is legitimate and register it if necessary.
946 + *
947 + * @access private
948 + *
949 + * @param string $taxonomy Taxonomy slug.
950 + *
951 + * @return bool|void|WP_Error True if already exists; void if it was registered; \WP_Error on error.
952 + */
953 + private function ensure_taxonomy( $taxonomy ) {
954 + if ( ! taxonomy_exists( $taxonomy ) ) {
955 + // Try re-registering synced taxonomies.
956 + $taxonomies = $this->get_callable( 'taxonomies' );
957 + if ( ! isset( $taxonomies[ $taxonomy ] ) ) {
958 + // Doesn't exist, or somehow hasn't been synced.
959 + return new WP_Error( 'invalid_taxonomy', "The taxonomy '$taxonomy' doesn't exist" );
960 + }
961 + $t = $taxonomies[ $taxonomy ];
962 +
963 + return register_taxonomy(
964 + $taxonomy,
965 + $t->object_type,
966 + (array) $t
967 + );
968 + }
969 +
970 + return true;
971 + }
972 +
973 + /**
974 + * Retrieve all terms from a taxonomy that are related to an object with a particular ID.
975 + *
976 + * @access public
977 + *
978 + * @param int $object_id Object ID.
979 + * @param string $taxonomy Taxonomy slug.
980 + *
981 + * @return array|bool|WP_Error Array of terms on success, `false` if no terms or post doesn't exist, \WP_Error on failure.
982 + */
983 + public function get_the_terms( $object_id, $taxonomy ) {
984 + return get_the_terms( $object_id, $taxonomy );
985 + }
986 +
987 + /**
988 + * Insert or update a term.
989 + *
990 + * @access public
991 + *
992 + * @param \WP_Term $term_object Term object.
993 + *
994 + * @return array|bool|WP_Error Array of term_id and term_taxonomy_id if updated, true if inserted, \WP_Error on failure.
995 + */
996 + public function update_term( $term_object ) {
997 + $taxonomy = $term_object->taxonomy;
998 + global $wpdb;
999 + $exists = $wpdb->get_var(
1000 + $wpdb->prepare(
1001 + "SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )",
1002 + $term_object->term_id
1003 + )
1004 + );
1005 + if ( ! $exists ) {
1006 + $term_object = sanitize_term( clone $term_object, $taxonomy, 'db' );
1007 + $term = array(
1008 + 'term_id' => $term_object->term_id,
1009 + 'name' => $term_object->name,
1010 + 'slug' => $term_object->slug,
1011 + 'term_group' => $term_object->term_group,
1012 + );
1013 + $term_taxonomy = array(
1014 + 'term_taxonomy_id' => $term_object->term_taxonomy_id,
1015 + 'term_id' => $term_object->term_id,
1016 + 'taxonomy' => $term_object->taxonomy,
1017 + 'description' => $term_object->description,
1018 + 'parent' => (int) $term_object->parent,
1019 + 'count' => (int) $term_object->count,
1020 + );
1021 + $wpdb->insert( $wpdb->terms, $term );
1022 + $wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy );
1023 +
1024 + return true;
1025 + }
1026 +
1027 + return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object );
1028 + }
1029 +
1030 + /**
1031 + * Delete a term by the term ID and its corresponding taxonomy.
1032 + *
1033 + * @access public
1034 + *
1035 + * @param int $term_id Term ID.
1036 + * @param string $taxonomy Taxonomy slug.
1037 + *
1038 + * @return bool|int|WP_Error True on success, false if term doesn't exist. Zero if trying with default category. \WP_Error on invalid taxonomy.
1039 + */
1040 + public function delete_term( $term_id, $taxonomy ) {
1041 + $this->ensure_taxonomy( $taxonomy );
1042 + return wp_delete_term( $term_id, $taxonomy );
1043 + }
1044 +
1045 + /**
1046 + * Add/update terms of a particular taxonomy of an object with the specified ID.
1047 + *
1048 + * @access public
1049 + *
1050 + * @param int $object_id The object to relate to.
1051 + * @param string $taxonomy The context in which to relate the term to the object.
1052 + * @param string|int|array $terms A single term slug, single term id, or array of either term slugs or ids.
1053 + * @param bool $append Optional. If false will delete difference of terms. Default false.
1054 + */
1055 + public function update_object_terms( $object_id, $taxonomy, $terms, $append ) {
1056 + $this->ensure_taxonomy( $taxonomy );
1057 + wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
1058 + }
1059 +
1060 + /**
1061 + * Remove certain term relationships from the specified object.
1062 + *
1063 + * @access public
1064 + *
1065 + * @todo Refactor to not use interpolated values when preparing the SQL query.
1066 + *
1067 + * @param int $object_id ID of the object.
1068 + * @param array $tt_ids Term taxonomy IDs.
1069 + * @return bool True on success, false on failure.
1070 + */
1071 + public function delete_object_terms( $object_id, $tt_ids ) {
1072 + global $wpdb;
1073 +
1074 + if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) {
1075 + // Escape.
1076 + $tt_ids_sanitized = array_map( 'intval', $tt_ids );
1077 +
1078 + $taxonomies = array();
1079 + foreach ( $tt_ids_sanitized as $tt_id ) {
1080 + $term = get_term_by( 'term_taxonomy_id', $tt_id );
1081 + $taxonomies[ $term->taxonomy ][] = $tt_id;
1082 + }
1083 + $in_tt_ids = implode( ', ', $tt_ids_sanitized );
1084 +
1085 + /**
1086 + * Fires immediately before an object-term relationship is deleted.
1087 + *
1088 + * @since 1.6.3
1089 + * @since-jetpack 2.9.0
1090 + *
1091 + * @param int $object_id Object ID.
1092 + * @param array $tt_ids An array of term taxonomy IDs.
1093 + */
1094 + do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized );
1095 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1096 + $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
1097 + foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) {
1098 + $this->ensure_taxonomy( $taxonomy );
1099 + wp_cache_delete( $object_id, $taxonomy . '_relationships' );
1100 + /**
1101 + * Fires immediately after an object-term relationship is deleted.
1102 + *
1103 + * @since 1.6.3
1104 + * @since-jetpack 2.9.0
1105 + *
1106 + * @param int $object_id Object ID.
1107 + * @param array $tt_ids An array of term taxonomy IDs.
1108 + */
1109 + do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids );
1110 + wp_update_term_count( $taxonomy_tt_ids, $taxonomy );
1111 + }
1112 +
1113 + return (bool) $deleted;
1114 + }
1115 +
1116 + return false;
1117 + }
1118 +
1119 + /**
1120 + * Retrieve the number of users.
1121 + * Not supported in this replicastore.
1122 + *
1123 + * @access public
1124 + */
1125 + public function user_count() {
1126 + // Noop.
1127 + }
1128 +
1129 + /**
1130 + * Retrieve a user object by the user ID.
1131 + *
1132 + * @access public
1133 + *
1134 + * @param int $user_id User ID.
1135 + * @return \WP_User|null User object, or `null` if user invalid/not found.
1136 + */
1137 + public function get_user( $user_id ) {
1138 + $user = get_user_by( 'id', $user_id );
1139 + return $user instanceof \WP_User ? $user : null;
1140 + }
1141 +
1142 + /**
1143 + * Insert or update a user.
1144 + * Not supported in this replicastore.
1145 + *
1146 + * @access public
1147 + * @throws Exception If this method is invoked.
1148 + *
1149 + * @param \WP_User $user User object.
1150 + */
1151 + public function upsert_user( $user ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1152 + $this->invalid_call();
1153 + }
1154 +
1155 + /**
1156 + * Delete a user.
1157 + * Not supported in this replicastore.
1158 + *
1159 + * @access public
1160 + * @throws Exception If this method is invoked.
1161 + *
1162 + * @param int $user_id User ID.
1163 + */
1164 + public function delete_user( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1165 + $this->invalid_call();
1166 + }
1167 +
1168 + /**
1169 + * Update/insert user locale.
1170 + * Not supported in this replicastore.
1171 + *
1172 + * @access public
1173 + * @throws Exception If this method is invoked.
1174 + *
1175 + * @param int $user_id User ID.
1176 + * @param string $local The user locale.
1177 + */
1178 + public function upsert_user_locale( $user_id, $local ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1179 + $this->invalid_call();
1180 + }
1181 +
1182 + /**
1183 + * Delete user locale.
1184 + * Not supported in this replicastore.
1185 + *
1186 + * @access public
1187 + * @throws Exception If this method is invoked.
1188 + *
1189 + * @param int $user_id User ID.
1190 + */
1191 + public function delete_user_locale( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1192 + $this->invalid_call();
1193 + }
1194 +
1195 + /**
1196 + * Retrieve the user locale.
1197 + *
1198 + * @access public
1199 + *
1200 + * @param int $user_id User ID.
1201 + * @return string The user locale.
1202 + */
1203 + public function get_user_locale( $user_id ) {
1204 + return get_user_locale( $user_id );
1205 + }
1206 +
1207 + /**
1208 + * Retrieve the allowed mime types for the user.
1209 + * Not supported in this replicastore.
1210 + *
1211 + * @access public
1212 + *
1213 + * @param int $user_id User ID.
1214 + */
1215 + public function get_allowed_mime_types( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1216 + // Noop.
1217 + }
1218 +
1219 + /**
1220 + * Retrieve all the checksums we are interested in.
1221 + *
1222 + * @access public
1223 + *
1224 + * @param boolean $perform_text_conversion If text fields should be latin1 converted.
1225 + *
1226 + * @return array Checksums.
1227 + */
1228 + public function checksum_all( $perform_text_conversion = false ) {
1229 + $all_checksum_tables = Table_Checksum::get_allowed_tables();
1230 +
1231 + unset( $all_checksum_tables['users'] ); // Handled separately - TODO.
1232 + unset( $all_checksum_tables['usermeta'] ); // Handled separately - TODO.
1233 + unset( $all_checksum_tables['termmeta'] ); // Handled separately - TODO.
1234 + unset( $all_checksum_tables['links'] ); // Not supported yet. Consider removing from default config.
1235 + unset( $all_checksum_tables['options'] ); // Not supported yet. Consider removing from default config.
1236 +
1237 + $all_checksum_tables = array_unique( array_keys( $all_checksum_tables ) );
1238 +
1239 + $result = array();
1240 +
1241 + foreach ( $all_checksum_tables as $table ) {
1242 + $result_key = in_array( $table, array( 'postmeta', 'commentmeta' ), true ) ? str_replace( 'meta', '_meta', $table ) : $table;
1243 + try {
1244 + $checksum = $this->checksum_histogram( $table, null, null, null, null, true, '', false, false, $perform_text_conversion );
1245 + $result[ $result_key ] = $this->summarize_checksum_histogram( $checksum );
1246 + } catch ( Exception $ex ) {
1247 + $result[ $result_key ] = null;
1248 + }
1249 + }
1250 +
1251 + return $result;
1252 + }
1253 +
1254 + /**
1255 + * Return the summarized checksum from buckets or the WP_Error.
1256 + *
1257 + * @param array $histogram checksum_histogram result.
1258 + *
1259 + * @return int|WP_Error checksum or Error.
1260 + */
1261 + protected function summarize_checksum_histogram( $histogram ) {
1262 + if ( is_wp_error( $histogram ) ) {
1263 + return $histogram;
1264 + } else {
1265 + return array_sum( $histogram );
1266 + }
1267 + }
1268 +
1269 + /**
1270 + * Grabs the minimum and maximum object ids for the given parameters.
1271 + *
1272 + * @access public
1273 + *
1274 + * @param string $id_field The id column in the table to query.
1275 + * @param string $object_table The table to query.
1276 + * @param string $where A sql where clause without 'WHERE'.
1277 + * @param int $bucket_size The maximum amount of objects to include in the query.
1278 + * For `term_relationships` table, the bucket size will refer to the amount
1279 + * of distinct object ids. This will likely include more database rows than
1280 + * the bucket size implies.
1281 + *
1282 + * @return object An object with min_id and max_id properties.
1283 + */
1284 + public function get_min_max_object_id( $id_field, $object_table, $where, $bucket_size ) {
1285 + global $wpdb;
1286 +
1287 + // The term relationship table's unique key is a combination of 2 columns. `DISTINCT` helps us get a more acurate query.
1288 + $distinct_sql = ( $wpdb->term_relationships === $object_table ) ? 'DISTINCT' : '';
1289 + $where_sql = $where ? "WHERE $where" : '';
1290 +
1291 + // Since MIN() and MAX() do not work with LIMIT, we'll need to adjust the dataset we query if a limit is present.
1292 + // With a limit present, we'll look at a dataset consisting of object_ids that meet the constructs of the $where clause.
1293 + // Without a limit, we can use the actual table as a dataset.
1294 + $from = $bucket_size ?
1295 + "( SELECT $distinct_sql $id_field FROM $object_table $where_sql ORDER BY $id_field ASC LIMIT " . ( (int) $bucket_size ) . ' ) as ids' :
1296 + "$object_table $where_sql ORDER BY $id_field ASC";
1297 +
1298 + return $wpdb->get_row(
1299 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1300 + "SELECT MIN($id_field) as min, MAX($id_field) as max FROM $from"
1301 + );
1302 + }
1303 +
1304 + /**
1305 + * Retrieve the checksum histogram for a specific object type.
1306 + *
1307 + * @access public
1308 + *
1309 + * @param string $table Object type.
1310 + * @param null $buckets Number of buckets to split the objects to.
1311 + * @param null $start_id Minimum object ID.
1312 + * @param null $end_id Maximum object ID.
1313 + * @param null $columns Table columns to calculate the checksum from.
1314 + * @param bool $strip_non_ascii Whether to strip non-ASCII characters.
1315 + * @param string $salt Salt, used for $wpdb->prepare()'s args.
1316 + * @param bool $only_range_edges Only return the range edges and not the actual checksums.
1317 + * @param bool $detailed_drilldown If the call should return a detailed drilldown for the checksum or only the checksum.
1318 + * @param bool $perform_text_conversion If text fields should be converted to latin1 during the checksum calculation.
1319 + *
1320 + * @return array|WP_Error The checksum histogram.
1321 + */
1322 + public function checksum_histogram( $table, $buckets = null, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '', $only_range_edges = false, $detailed_drilldown = false, $perform_text_conversion = false ) {
1323 + global $wpdb;
1324 +
1325 + $wpdb->queries = array();
1326 + try {
1327 + $checksum_table = $this->get_table_checksum_instance( $table, $salt, $perform_text_conversion, $columns );
1328 + } catch ( Exception $ex ) {
1329 + return new WP_Error( 'checksum_disabled', $ex->getMessage() );
1330 + }
1331 +
1332 + try {
1333 + $range_edges = $checksum_table->get_range_edges( $start_id, $end_id );
1334 + } catch ( Exception $ex ) {
1335 + return new WP_Error( 'invalid_range_edges', '[' . ( $start_id ?? 'null' ) . '-' . ( $end_id ?? 'null' ) . ']: ' . $ex->getMessage() );
1336 + }
1337 +
1338 + if ( $only_range_edges ) {
1339 + return $range_edges;
1340 + }
1341 +
1342 + $object_count = (int) $range_edges['item_count'];
1343 +
1344 + if ( 0 === $object_count ) {
1345 + return array();
1346 + }
1347 +
1348 + // Validate / Determine Buckets.
1349 + if ( $buckets === null || $buckets < 1 ) {
1350 + $buckets = $this->calculate_buckets( $table, $object_count );
1351 + }
1352 +
1353 + $bucket_size = (int) ceil( $object_count / $buckets );
1354 + $previous_max_id = max( 0, $range_edges['min_range'] );
1355 + $histogram = array();
1356 +
1357 + do {
1358 + try {
1359 + $ids_range = $checksum_table->get_range_edges( $previous_max_id, null, $bucket_size );
1360 + } catch ( Exception $ex ) {
1361 + return new WP_Error( 'invalid_range_edges', '[' . $previous_max_id . '- ]: ' . $ex->getMessage() );
1362 + }
1363 +
1364 + if ( empty( $ids_range['min_range'] ) || empty( $ids_range['max_range'] ) ) {
1365 + // Nothing to checksum here...
1366 + break;
1367 + }
1368 +
1369 + // Get the checksum value.
1370 + $batch_checksum = $checksum_table->calculate_checksum( $ids_range['min_range'], $ids_range['max_range'], null, $detailed_drilldown );
1371 +
1372 + if ( is_wp_error( $batch_checksum ) ) {
1373 + return $batch_checksum;
1374 + }
1375 +
1376 + if ( $ids_range['min_range'] === $ids_range['max_range'] ) {
1377 + $histogram[ $ids_range['min_range'] ] = $batch_checksum;
1378 + } else {
1379 + $histogram[ "{$ids_range[ 'min_range' ]}-{$ids_range[ 'max_range' ]}" ] = $batch_checksum;
1380 + }
1381 + // If ids_range['max_range'] is PHP_INT_MAX, we've reached the end of the table. Edge case causing the loop to never end.
1382 + if ( PHP_INT_MAX === (int) $ids_range['max_range'] ) {
1383 + break;
1384 + }
1385 + $previous_max_id = $ids_range['max_range'] + 1;
1386 + // If we've reached the max_range lets bail out.
1387 + if ( $previous_max_id > $range_edges['max_range'] ) {
1388 + break;
1389 + }
1390 + } while ( true );
1391 +
1392 + return $histogram;
1393 + }
1394 +
1395 + /**
1396 + * Retrieve the type of the checksum.
1397 + *
1398 + * @access public
1399 + *
1400 + * @return string Type of the checksum.
1401 + */
1402 + public function get_checksum_type() {
1403 + return 'sum';
1404 + }
1405 +
1406 + /**
1407 + * Used in methods that are not implemented and shouldn't be invoked.
1408 + *
1409 + * @access private
1410 + * @return never
1411 + * @throws Exception If this method is invoked.
1412 + */
1413 + private function invalid_call() {
1414 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
1415 + $backtrace = debug_backtrace();
1416 + $caller = $backtrace[1]['function'];
1417 + throw new Exception( "This function $caller is not supported on the WP Replicastore" );
1418 + }
1419 +
1420 + /**
1421 + * Determine number of buckets to use in full table checksum.
1422 + *
1423 + * @param string $table Object Type.
1424 + * @param int $object_count Object count.
1425 + * @return int Number of Buckets to use.
1426 + */
1427 + private function calculate_buckets( $table, $object_count ) {
1428 + // Ensure no division by 0.
1429 + if ( 0 === (int) $object_count ) {
1430 + return 1;
1431 + }
1432 +
1433 + // Default Bucket sizes.
1434 + $bucket_size = 10000; // Default bucket size is 10,000 items.
1435 + switch ( $table ) {
1436 + case 'postmeta':
1437 + case 'commentmeta':
1438 + case 'order_itemmeta':
1439 + $bucket_size = 1000; // Meta bucket size is restricted to 1000 items.
1440 + }
1441 +
1442 + return (int) ceil( $object_count / $bucket_size );
1443 + }
1444 +
1445 + /**
1446 + * Return an instance for `Table_Checksum`, depending on the table.
1447 + *
1448 + * Some tables require custom instances, due to different checksum logic.
1449 + *
1450 + * @param string $table The table that we want to get the instance for.
1451 + * @param string $salt Salt to be used when generating the checksums.
1452 + * @param bool $perform_text_conversion Should we perform text encoding conversion when calculating the checksum.
1453 + * @param array $additional_columns Additional columns to add to the checksum calculation.
1454 + *
1455 + * @return Table_Checksum|Table_Checksum_Usermeta
1456 + * @throws Exception Might throw an exception if any of the input parameters were invalid.
1457 + */
1458 + public function get_table_checksum_instance( $table, $salt = null, $perform_text_conversion = false, $additional_columns = null ) {
1459 + if ( 'users' === $table ) {
1460 + return new Table_Checksum_Users( $table, $salt, $perform_text_conversion, $additional_columns );
1461 + }
1462 + if ( 'usermeta' === $table ) {
1463 + return new Table_Checksum_Usermeta( $table, $salt, $perform_text_conversion, $additional_columns );
1464 + }
1465 +
1466 + return new Table_Checksum( $table, $salt, $perform_text_conversion, $additional_columns );
1467 + }
1468 +}