Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.26% |
1 / 378 |
|
0.00% |
0 / 67 |
CRAP | |
0.00% |
0 / 1 |
| Replicastore | |
0.00% |
0 / 376 |
|
0.00% |
0 / 67 |
20306 | |
0.00% |
0 / 1 |
| reset | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
| full_sync_start | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| full_sync_end | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| term_count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| term_taxonomy_count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| term_relationship_count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| post_count | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
| get_posts | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| get_post | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| upsert_post | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
20 | |||
| delete_post | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| posts_checksum | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| post_meta_checksum | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| comment_count | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
| comment_status_to_approval_value | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
132 | |||
| get_comments | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
| get_comment | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| upsert_comment | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
20 | |||
| trash_comment | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| delete_comment | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| spam_comment | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| trashed_post_comments | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| untrashed_post_comments | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| comments_checksum | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| comment_meta_checksum | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| update_option | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_option | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| delete_option | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| set_theme_info | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| current_theme_supports | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_metadata | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| upsert_metadata | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
12 | |||
| delete_metadata | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
| delete_batch_metadata | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| get_constant | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| set_constant | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_updates | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| set_updates | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| get_callable | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| set_callable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_site_option | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| update_site_option | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| delete_site_option | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_terms | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| get_term | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
| ensure_taxonomy | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
| get_the_terms | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| update_term | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
6 | |||
| delete_term | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| update_object_terms | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| delete_object_terms | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
| user_count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_user | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| upsert_user | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| delete_user | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| upsert_user_locale | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| delete_user_locale | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_user_locale | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_allowed_mime_types | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| checksum_all | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
| summarize_checksum_histogram | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| get_min_max_object_id | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| checksum_histogram | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
210 | |||
| get_checksum_type | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| invalid_call | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| calculate_buckets | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
| get_table_checksum_instance | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| 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 | * Retrieve value of a constant based on the constant name. |
| 731 | * |
| 732 | * We explicitly return null instead of false if the constant doesn't exist. |
| 733 | * |
| 734 | * @access public |
| 735 | * |
| 736 | * @param string $constant Name of constant to retrieve. |
| 737 | * @return mixed Value set for the constant. |
| 738 | */ |
| 739 | public function get_constant( $constant ) { |
| 740 | $value = get_option( 'jetpack_constant_' . $constant ); |
| 741 | |
| 742 | if ( $value ) { |
| 743 | return $value; |
| 744 | } |
| 745 | |
| 746 | return null; |
| 747 | } |
| 748 | |
| 749 | /** |
| 750 | * Set the value of a constant. |
| 751 | * |
| 752 | * @access public |
| 753 | * |
| 754 | * @param string $constant Name of constant to retrieve. |
| 755 | * @param mixed $value Value set for the constant. |
| 756 | */ |
| 757 | public function set_constant( $constant, $value ) { |
| 758 | update_option( 'jetpack_constant_' . $constant, $value ); |
| 759 | } |
| 760 | |
| 761 | /** |
| 762 | * Retrieve the number of the available updates of a certain type. |
| 763 | * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`. |
| 764 | * |
| 765 | * @access public |
| 766 | * |
| 767 | * @param string $type Type of updates to retrieve. |
| 768 | * @return int|null Number of updates available, `null` if type is invalid or missing. |
| 769 | */ |
| 770 | public function get_updates( $type ) { |
| 771 | $all_updates = get_option( 'jetpack_updates', array() ); |
| 772 | |
| 773 | if ( isset( $all_updates[ $type ] ) ) { |
| 774 | return $all_updates[ $type ]; |
| 775 | } else { |
| 776 | return null; |
| 777 | } |
| 778 | } |
| 779 | |
| 780 | /** |
| 781 | * Set the available updates of a certain type. |
| 782 | * Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`. |
| 783 | * |
| 784 | * @access public |
| 785 | * |
| 786 | * @param string $type Type of updates to set. |
| 787 | * @param int $updates Total number of updates. |
| 788 | */ |
| 789 | public function set_updates( $type, $updates ) { |
| 790 | $all_updates = get_option( 'jetpack_updates', array() ); |
| 791 | $all_updates[ $type ] = $updates; |
| 792 | update_option( 'jetpack_updates', $all_updates ); |
| 793 | } |
| 794 | |
| 795 | /** |
| 796 | * Retrieve a callable value based on its name. |
| 797 | * |
| 798 | * @access public |
| 799 | * |
| 800 | * @param string $name Name of the callable to retrieve. |
| 801 | * @return mixed Value of the callable. |
| 802 | */ |
| 803 | public function get_callable( $name ) { |
| 804 | $value = get_option( 'jetpack_' . $name ); |
| 805 | |
| 806 | if ( $value ) { |
| 807 | return $value; |
| 808 | } |
| 809 | |
| 810 | return null; |
| 811 | } |
| 812 | |
| 813 | /** |
| 814 | * Update the value of a callable. |
| 815 | * |
| 816 | * @access public |
| 817 | * |
| 818 | * @param string $name Callable name. |
| 819 | * @param mixed $value Callable value. |
| 820 | */ |
| 821 | public function set_callable( $name, $value ) { |
| 822 | update_option( 'jetpack_' . $name, $value ); |
| 823 | } |
| 824 | |
| 825 | /** |
| 826 | * Retrieve a network option value based on a network option name. |
| 827 | * |
| 828 | * @access public |
| 829 | * |
| 830 | * @param string $option Name of network option to retrieve. |
| 831 | * @return mixed Value set for the network option. |
| 832 | */ |
| 833 | public function get_site_option( $option ) { |
| 834 | return get_option( 'jetpack_network_' . $option ); |
| 835 | } |
| 836 | |
| 837 | /** |
| 838 | * Update the value of a network option. |
| 839 | * |
| 840 | * @access public |
| 841 | * |
| 842 | * @param string $option Network option name. |
| 843 | * @param mixed $value Network option value. |
| 844 | * @return bool False if value was not updated and true if value was updated. |
| 845 | */ |
| 846 | public function update_site_option( $option, $value ) { |
| 847 | return update_option( 'jetpack_network_' . $option, $value ); |
| 848 | } |
| 849 | |
| 850 | /** |
| 851 | * Remove a network option by name. |
| 852 | * |
| 853 | * @access public |
| 854 | * |
| 855 | * @param string $option Name of option to remove. |
| 856 | * @return bool True, if option is successfully deleted. False on failure. |
| 857 | */ |
| 858 | public function delete_site_option( $option ) { |
| 859 | return delete_option( 'jetpack_network_' . $option ); |
| 860 | } |
| 861 | |
| 862 | /** |
| 863 | * Retrieve the terms from a particular taxonomy. |
| 864 | * |
| 865 | * @access public |
| 866 | * |
| 867 | * @param string $taxonomy Taxonomy slug. |
| 868 | * |
| 869 | * @return array|WP_Error Array of terms or WP_Error object on failure. |
| 870 | */ |
| 871 | public function get_terms( $taxonomy ) { |
| 872 | $t = $this->ensure_taxonomy( $taxonomy ); |
| 873 | if ( ! $t || is_wp_error( $t ) ) { |
| 874 | return $t; |
| 875 | } |
| 876 | // @phan-suppress-next-line PhanAccessMethodInternal @phan-suppress-current-line UnusedSuppression -- Fixed in WP 6.9, but then we need a suppression for the WP 6.8 compat run. @todo Remove this suppression when we drop WP <6.9. |
| 877 | return get_terms( $taxonomy ); |
| 878 | } |
| 879 | |
| 880 | /** |
| 881 | * Retrieve a particular term. |
| 882 | * |
| 883 | * @access public |
| 884 | * |
| 885 | * @param string|false $taxonomy Taxonomy slug. |
| 886 | * @param int $term_id ID of the term. |
| 887 | * @param string $term_key ID Field `term_id` or `term_taxonomy_id`. |
| 888 | * |
| 889 | * @return \WP_Term|WP_Error Term object on success, \WP_Error object on failure. |
| 890 | */ |
| 891 | public function get_term( $taxonomy, $term_id, $term_key = 'term_id' ) { |
| 892 | |
| 893 | // Full Sync will pass false for the $taxonomy so a check for term_taxonomy_id is needed before ensure_taxonomy. |
| 894 | if ( 'term_taxonomy_id' === $term_key ) { |
| 895 | return get_term_by( 'term_taxonomy_id', $term_id ); |
| 896 | } |
| 897 | |
| 898 | $t = $this->ensure_taxonomy( $taxonomy ); |
| 899 | if ( ! $t || is_wp_error( $t ) ) { |
| 900 | return $t; |
| 901 | } |
| 902 | |
| 903 | return get_term( $term_id, $taxonomy ); |
| 904 | } |
| 905 | |
| 906 | /** |
| 907 | * Verify a taxonomy is legitimate and register it if necessary. |
| 908 | * |
| 909 | * @access private |
| 910 | * |
| 911 | * @param string $taxonomy Taxonomy slug. |
| 912 | * |
| 913 | * @return bool|void|WP_Error True if already exists; void if it was registered; \WP_Error on error. |
| 914 | */ |
| 915 | private function ensure_taxonomy( $taxonomy ) { |
| 916 | if ( ! taxonomy_exists( $taxonomy ) ) { |
| 917 | // Try re-registering synced taxonomies. |
| 918 | $taxonomies = $this->get_callable( 'taxonomies' ); |
| 919 | if ( ! isset( $taxonomies[ $taxonomy ] ) ) { |
| 920 | // Doesn't exist, or somehow hasn't been synced. |
| 921 | return new WP_Error( 'invalid_taxonomy', "The taxonomy '$taxonomy' doesn't exist" ); |
| 922 | } |
| 923 | $t = $taxonomies[ $taxonomy ]; |
| 924 | |
| 925 | return register_taxonomy( |
| 926 | $taxonomy, |
| 927 | $t->object_type, |
| 928 | (array) $t |
| 929 | ); |
| 930 | } |
| 931 | |
| 932 | return true; |
| 933 | } |
| 934 | |
| 935 | /** |
| 936 | * Retrieve all terms from a taxonomy that are related to an object with a particular ID. |
| 937 | * |
| 938 | * @access public |
| 939 | * |
| 940 | * @param int $object_id Object ID. |
| 941 | * @param string $taxonomy Taxonomy slug. |
| 942 | * |
| 943 | * @return array|bool|WP_Error Array of terms on success, `false` if no terms or post doesn't exist, \WP_Error on failure. |
| 944 | */ |
| 945 | public function get_the_terms( $object_id, $taxonomy ) { |
| 946 | return get_the_terms( $object_id, $taxonomy ); |
| 947 | } |
| 948 | |
| 949 | /** |
| 950 | * Insert or update a term. |
| 951 | * |
| 952 | * @access public |
| 953 | * |
| 954 | * @param \WP_Term $term_object Term object. |
| 955 | * |
| 956 | * @return array|bool|WP_Error Array of term_id and term_taxonomy_id if updated, true if inserted, \WP_Error on failure. |
| 957 | */ |
| 958 | public function update_term( $term_object ) { |
| 959 | $taxonomy = $term_object->taxonomy; |
| 960 | global $wpdb; |
| 961 | $exists = $wpdb->get_var( |
| 962 | $wpdb->prepare( |
| 963 | "SELECT EXISTS( SELECT 1 FROM $wpdb->terms WHERE term_id = %d )", |
| 964 | $term_object->term_id |
| 965 | ) |
| 966 | ); |
| 967 | if ( ! $exists ) { |
| 968 | $term_object = sanitize_term( clone $term_object, $taxonomy, 'db' ); |
| 969 | $term = array( |
| 970 | 'term_id' => $term_object->term_id, |
| 971 | 'name' => $term_object->name, |
| 972 | 'slug' => $term_object->slug, |
| 973 | 'term_group' => $term_object->term_group, |
| 974 | ); |
| 975 | $term_taxonomy = array( |
| 976 | 'term_taxonomy_id' => $term_object->term_taxonomy_id, |
| 977 | 'term_id' => $term_object->term_id, |
| 978 | 'taxonomy' => $term_object->taxonomy, |
| 979 | 'description' => $term_object->description, |
| 980 | 'parent' => (int) $term_object->parent, |
| 981 | 'count' => (int) $term_object->count, |
| 982 | ); |
| 983 | $wpdb->insert( $wpdb->terms, $term ); |
| 984 | $wpdb->insert( $wpdb->term_taxonomy, $term_taxonomy ); |
| 985 | |
| 986 | return true; |
| 987 | } |
| 988 | |
| 989 | return wp_update_term( $term_object->term_id, $taxonomy, (array) $term_object ); |
| 990 | } |
| 991 | |
| 992 | /** |
| 993 | * Delete a term by the term ID and its corresponding taxonomy. |
| 994 | * |
| 995 | * @access public |
| 996 | * |
| 997 | * @param int $term_id Term ID. |
| 998 | * @param string $taxonomy Taxonomy slug. |
| 999 | * |
| 1000 | * @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. |
| 1001 | */ |
| 1002 | public function delete_term( $term_id, $taxonomy ) { |
| 1003 | $this->ensure_taxonomy( $taxonomy ); |
| 1004 | return wp_delete_term( $term_id, $taxonomy ); |
| 1005 | } |
| 1006 | |
| 1007 | /** |
| 1008 | * Add/update terms of a particular taxonomy of an object with the specified ID. |
| 1009 | * |
| 1010 | * @access public |
| 1011 | * |
| 1012 | * @param int $object_id The object to relate to. |
| 1013 | * @param string $taxonomy The context in which to relate the term to the object. |
| 1014 | * @param string|int|array $terms A single term slug, single term id, or array of either term slugs or ids. |
| 1015 | * @param bool $append Optional. If false will delete difference of terms. Default false. |
| 1016 | */ |
| 1017 | public function update_object_terms( $object_id, $taxonomy, $terms, $append ) { |
| 1018 | $this->ensure_taxonomy( $taxonomy ); |
| 1019 | wp_set_object_terms( $object_id, $terms, $taxonomy, $append ); |
| 1020 | } |
| 1021 | |
| 1022 | /** |
| 1023 | * Remove certain term relationships from the specified object. |
| 1024 | * |
| 1025 | * @access public |
| 1026 | * |
| 1027 | * @todo Refactor to not use interpolated values when preparing the SQL query. |
| 1028 | * |
| 1029 | * @param int $object_id ID of the object. |
| 1030 | * @param array $tt_ids Term taxonomy IDs. |
| 1031 | * @return bool True on success, false on failure. |
| 1032 | */ |
| 1033 | public function delete_object_terms( $object_id, $tt_ids ) { |
| 1034 | global $wpdb; |
| 1035 | |
| 1036 | if ( is_array( $tt_ids ) && ! empty( $tt_ids ) ) { |
| 1037 | // Escape. |
| 1038 | $tt_ids_sanitized = array_map( 'intval', $tt_ids ); |
| 1039 | |
| 1040 | $taxonomies = array(); |
| 1041 | foreach ( $tt_ids_sanitized as $tt_id ) { |
| 1042 | $term = get_term_by( 'term_taxonomy_id', $tt_id ); |
| 1043 | $taxonomies[ $term->taxonomy ][] = $tt_id; |
| 1044 | } |
| 1045 | $in_tt_ids = implode( ', ', $tt_ids_sanitized ); |
| 1046 | |
| 1047 | /** |
| 1048 | * Fires immediately before an object-term relationship is deleted. |
| 1049 | * |
| 1050 | * @since 1.6.3 |
| 1051 | * @since-jetpack 2.9.0 |
| 1052 | * |
| 1053 | * @param int $object_id Object ID. |
| 1054 | * @param array $tt_ids An array of term taxonomy IDs. |
| 1055 | */ |
| 1056 | do_action( 'delete_term_relationships', $object_id, $tt_ids_sanitized ); |
| 1057 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 1058 | $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) ); |
| 1059 | foreach ( $taxonomies as $taxonomy => $taxonomy_tt_ids ) { |
| 1060 | $this->ensure_taxonomy( $taxonomy ); |
| 1061 | wp_cache_delete( $object_id, $taxonomy . '_relationships' ); |
| 1062 | /** |
| 1063 | * Fires immediately after an object-term relationship is deleted. |
| 1064 | * |
| 1065 | * @since 1.6.3 |
| 1066 | * @since-jetpack 2.9.0 |
| 1067 | * |
| 1068 | * @param int $object_id Object ID. |
| 1069 | * @param array $tt_ids An array of term taxonomy IDs. |
| 1070 | */ |
| 1071 | do_action( 'deleted_term_relationships', $object_id, $taxonomy_tt_ids ); |
| 1072 | wp_update_term_count( $taxonomy_tt_ids, $taxonomy ); |
| 1073 | } |
| 1074 | |
| 1075 | return (bool) $deleted; |
| 1076 | } |
| 1077 | |
| 1078 | return false; |
| 1079 | } |
| 1080 | |
| 1081 | /** |
| 1082 | * Retrieve the number of users. |
| 1083 | * Not supported in this replicastore. |
| 1084 | * |
| 1085 | * @access public |
| 1086 | */ |
| 1087 | public function user_count() { |
| 1088 | // Noop. |
| 1089 | } |
| 1090 | |
| 1091 | /** |
| 1092 | * Retrieve a user object by the user ID. |
| 1093 | * |
| 1094 | * @access public |
| 1095 | * |
| 1096 | * @param int $user_id User ID. |
| 1097 | * @return \WP_User|null User object, or `null` if user invalid/not found. |
| 1098 | */ |
| 1099 | public function get_user( $user_id ) { |
| 1100 | $user = get_user_by( 'id', $user_id ); |
| 1101 | return $user instanceof \WP_User ? $user : null; |
| 1102 | } |
| 1103 | |
| 1104 | /** |
| 1105 | * Insert or update a user. |
| 1106 | * Not supported in this replicastore. |
| 1107 | * |
| 1108 | * @access public |
| 1109 | * @throws Exception If this method is invoked. |
| 1110 | * |
| 1111 | * @param \WP_User $user User object. |
| 1112 | */ |
| 1113 | public function upsert_user( $user ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 1114 | $this->invalid_call(); |
| 1115 | } |
| 1116 | |
| 1117 | /** |
| 1118 | * Delete a user. |
| 1119 | * Not supported in this replicastore. |
| 1120 | * |
| 1121 | * @access public |
| 1122 | * @throws Exception If this method is invoked. |
| 1123 | * |
| 1124 | * @param int $user_id User ID. |
| 1125 | */ |
| 1126 | public function delete_user( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 1127 | $this->invalid_call(); |
| 1128 | } |
| 1129 | |
| 1130 | /** |
| 1131 | * Update/insert user locale. |
| 1132 | * Not supported in this replicastore. |
| 1133 | * |
| 1134 | * @access public |
| 1135 | * @throws Exception If this method is invoked. |
| 1136 | * |
| 1137 | * @param int $user_id User ID. |
| 1138 | * @param string $local The user locale. |
| 1139 | */ |
| 1140 | public function upsert_user_locale( $user_id, $local ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 1141 | $this->invalid_call(); |
| 1142 | } |
| 1143 | |
| 1144 | /** |
| 1145 | * Delete user locale. |
| 1146 | * Not supported in this replicastore. |
| 1147 | * |
| 1148 | * @access public |
| 1149 | * @throws Exception If this method is invoked. |
| 1150 | * |
| 1151 | * @param int $user_id User ID. |
| 1152 | */ |
| 1153 | public function delete_user_locale( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 1154 | $this->invalid_call(); |
| 1155 | } |
| 1156 | |
| 1157 | /** |
| 1158 | * Retrieve the user locale. |
| 1159 | * |
| 1160 | * @access public |
| 1161 | * |
| 1162 | * @param int $user_id User ID. |
| 1163 | * @return string The user locale. |
| 1164 | */ |
| 1165 | public function get_user_locale( $user_id ) { |
| 1166 | return get_user_locale( $user_id ); |
| 1167 | } |
| 1168 | |
| 1169 | /** |
| 1170 | * Retrieve the allowed mime types for the user. |
| 1171 | * Not supported in this replicastore. |
| 1172 | * |
| 1173 | * @access public |
| 1174 | * |
| 1175 | * @param int $user_id User ID. |
| 1176 | */ |
| 1177 | public function get_allowed_mime_types( $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 1178 | // Noop. |
| 1179 | } |
| 1180 | |
| 1181 | /** |
| 1182 | * Retrieve all the checksums we are interested in. |
| 1183 | * |
| 1184 | * @access public |
| 1185 | * |
| 1186 | * @param boolean $perform_text_conversion If text fields should be latin1 converted. |
| 1187 | * |
| 1188 | * @return array Checksums. |
| 1189 | */ |
| 1190 | public function checksum_all( $perform_text_conversion = false ) { |
| 1191 | $all_checksum_tables = Table_Checksum::get_allowed_tables(); |
| 1192 | |
| 1193 | unset( $all_checksum_tables['users'] ); // Handled separately - TODO. |
| 1194 | unset( $all_checksum_tables['usermeta'] ); // Handled separately - TODO. |
| 1195 | unset( $all_checksum_tables['termmeta'] ); // Handled separately - TODO. |
| 1196 | unset( $all_checksum_tables['links'] ); // Not supported yet. Consider removing from default config. |
| 1197 | unset( $all_checksum_tables['options'] ); // Not supported yet. Consider removing from default config. |
| 1198 | |
| 1199 | $all_checksum_tables = array_unique( array_keys( $all_checksum_tables ) ); |
| 1200 | |
| 1201 | $result = array(); |
| 1202 | |
| 1203 | foreach ( $all_checksum_tables as $table ) { |
| 1204 | $result_key = in_array( $table, array( 'postmeta', 'commentmeta' ), true ) ? str_replace( 'meta', '_meta', $table ) : $table; |
| 1205 | try { |
| 1206 | $checksum = $this->checksum_histogram( $table, null, null, null, null, true, '', false, false, $perform_text_conversion ); |
| 1207 | $result[ $result_key ] = $this->summarize_checksum_histogram( $checksum ); |
| 1208 | } catch ( Exception $ex ) { |
| 1209 | $result[ $result_key ] = null; |
| 1210 | } |
| 1211 | } |
| 1212 | |
| 1213 | return $result; |
| 1214 | } |
| 1215 | |
| 1216 | /** |
| 1217 | * Return the summarized checksum from buckets or the WP_Error. |
| 1218 | * |
| 1219 | * @param array $histogram checksum_histogram result. |
| 1220 | * |
| 1221 | * @return int|WP_Error checksum or Error. |
| 1222 | */ |
| 1223 | protected function summarize_checksum_histogram( $histogram ) { |
| 1224 | if ( is_wp_error( $histogram ) ) { |
| 1225 | return $histogram; |
| 1226 | } else { |
| 1227 | return array_sum( $histogram ); |
| 1228 | } |
| 1229 | } |
| 1230 | |
| 1231 | /** |
| 1232 | * Grabs the minimum and maximum object ids for the given parameters. |
| 1233 | * |
| 1234 | * @access public |
| 1235 | * |
| 1236 | * @param string $id_field The id column in the table to query. |
| 1237 | * @param string $object_table The table to query. |
| 1238 | * @param string $where A sql where clause without 'WHERE'. |
| 1239 | * @param int $bucket_size The maximum amount of objects to include in the query. |
| 1240 | * For `term_relationships` table, the bucket size will refer to the amount |
| 1241 | * of distinct object ids. This will likely include more database rows than |
| 1242 | * the bucket size implies. |
| 1243 | * |
| 1244 | * @return object An object with min_id and max_id properties. |
| 1245 | */ |
| 1246 | public function get_min_max_object_id( $id_field, $object_table, $where, $bucket_size ) { |
| 1247 | global $wpdb; |
| 1248 | |
| 1249 | // The term relationship table's unique key is a combination of 2 columns. `DISTINCT` helps us get a more acurate query. |
| 1250 | $distinct_sql = ( $wpdb->term_relationships === $object_table ) ? 'DISTINCT' : ''; |
| 1251 | $where_sql = $where ? "WHERE $where" : ''; |
| 1252 | |
| 1253 | // Since MIN() and MAX() do not work with LIMIT, we'll need to adjust the dataset we query if a limit is present. |
| 1254 | // With a limit present, we'll look at a dataset consisting of object_ids that meet the constructs of the $where clause. |
| 1255 | // Without a limit, we can use the actual table as a dataset. |
| 1256 | $from = $bucket_size ? |
| 1257 | "( SELECT $distinct_sql $id_field FROM $object_table $where_sql ORDER BY $id_field ASC LIMIT " . ( (int) $bucket_size ) . ' ) as ids' : |
| 1258 | "$object_table $where_sql ORDER BY $id_field ASC"; |
| 1259 | |
| 1260 | return $wpdb->get_row( |
| 1261 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 1262 | "SELECT MIN($id_field) as min, MAX($id_field) as max FROM $from" |
| 1263 | ); |
| 1264 | } |
| 1265 | |
| 1266 | /** |
| 1267 | * Retrieve the checksum histogram for a specific object type. |
| 1268 | * |
| 1269 | * @access public |
| 1270 | * |
| 1271 | * @param string $table Object type. |
| 1272 | * @param null $buckets Number of buckets to split the objects to. |
| 1273 | * @param null $start_id Minimum object ID. |
| 1274 | * @param null $end_id Maximum object ID. |
| 1275 | * @param null $columns Table columns to calculate the checksum from. |
| 1276 | * @param bool $strip_non_ascii Whether to strip non-ASCII characters. |
| 1277 | * @param string $salt Salt, used for $wpdb->prepare()'s args. |
| 1278 | * @param bool $only_range_edges Only return the range edges and not the actual checksums. |
| 1279 | * @param bool $detailed_drilldown If the call should return a detailed drilldown for the checksum or only the checksum. |
| 1280 | * @param bool $perform_text_conversion If text fields should be converted to latin1 during the checksum calculation. |
| 1281 | * |
| 1282 | * @return array|WP_Error The checksum histogram. |
| 1283 | */ |
| 1284 | 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 ) { |
| 1285 | global $wpdb; |
| 1286 | |
| 1287 | $wpdb->queries = array(); |
| 1288 | try { |
| 1289 | $checksum_table = $this->get_table_checksum_instance( $table, $salt, $perform_text_conversion, $columns ); |
| 1290 | } catch ( Exception $ex ) { |
| 1291 | return new WP_Error( 'checksum_disabled', $ex->getMessage() ); |
| 1292 | } |
| 1293 | |
| 1294 | try { |
| 1295 | $range_edges = $checksum_table->get_range_edges( $start_id, $end_id ); |
| 1296 | } catch ( Exception $ex ) { |
| 1297 | return new WP_Error( 'invalid_range_edges', '[' . ( $start_id ?? 'null' ) . '-' . ( $end_id ?? 'null' ) . ']: ' . $ex->getMessage() ); |
| 1298 | } |
| 1299 | |
| 1300 | if ( $only_range_edges ) { |
| 1301 | return $range_edges; |
| 1302 | } |
| 1303 | |
| 1304 | $object_count = (int) $range_edges['item_count']; |
| 1305 | |
| 1306 | if ( 0 === $object_count ) { |
| 1307 | return array(); |
| 1308 | } |
| 1309 | |
| 1310 | // Validate / Determine Buckets. |
| 1311 | if ( $buckets === null || $buckets < 1 ) { |
| 1312 | $buckets = $this->calculate_buckets( $table, $object_count ); |
| 1313 | } |
| 1314 | |
| 1315 | $bucket_size = (int) ceil( $object_count / $buckets ); |
| 1316 | $previous_max_id = max( 0, $range_edges['min_range'] ); |
| 1317 | $histogram = array(); |
| 1318 | |
| 1319 | do { |
| 1320 | try { |
| 1321 | $ids_range = $checksum_table->get_range_edges( $previous_max_id, null, $bucket_size ); |
| 1322 | } catch ( Exception $ex ) { |
| 1323 | return new WP_Error( 'invalid_range_edges', '[' . $previous_max_id . '- ]: ' . $ex->getMessage() ); |
| 1324 | } |
| 1325 | |
| 1326 | if ( empty( $ids_range['min_range'] ) || empty( $ids_range['max_range'] ) ) { |
| 1327 | // Nothing to checksum here... |
| 1328 | break; |
| 1329 | } |
| 1330 | |
| 1331 | // Get the checksum value. |
| 1332 | $batch_checksum = $checksum_table->calculate_checksum( $ids_range['min_range'], $ids_range['max_range'], null, $detailed_drilldown ); |
| 1333 | |
| 1334 | if ( is_wp_error( $batch_checksum ) ) { |
| 1335 | return $batch_checksum; |
| 1336 | } |
| 1337 | |
| 1338 | if ( $ids_range['min_range'] === $ids_range['max_range'] ) { |
| 1339 | $histogram[ $ids_range['min_range'] ] = $batch_checksum; |
| 1340 | } else { |
| 1341 | $histogram[ "{$ids_range[ 'min_range' ]}-{$ids_range[ 'max_range' ]}" ] = $batch_checksum; |
| 1342 | } |
| 1343 | // 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. |
| 1344 | if ( PHP_INT_MAX === (int) $ids_range['max_range'] ) { |
| 1345 | break; |
| 1346 | } |
| 1347 | $previous_max_id = $ids_range['max_range'] + 1; |
| 1348 | // If we've reached the max_range lets bail out. |
| 1349 | if ( $previous_max_id > $range_edges['max_range'] ) { |
| 1350 | break; |
| 1351 | } |
| 1352 | } while ( true ); |
| 1353 | |
| 1354 | return $histogram; |
| 1355 | } |
| 1356 | |
| 1357 | /** |
| 1358 | * Retrieve the type of the checksum. |
| 1359 | * |
| 1360 | * @access public |
| 1361 | * |
| 1362 | * @return string Type of the checksum. |
| 1363 | */ |
| 1364 | public function get_checksum_type() { |
| 1365 | return 'sum'; |
| 1366 | } |
| 1367 | |
| 1368 | /** |
| 1369 | * Used in methods that are not implemented and shouldn't be invoked. |
| 1370 | * |
| 1371 | * @access private |
| 1372 | * @return never |
| 1373 | * @throws Exception If this method is invoked. |
| 1374 | */ |
| 1375 | private function invalid_call() { |
| 1376 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace |
| 1377 | $backtrace = debug_backtrace(); |
| 1378 | $caller = $backtrace[1]['function']; |
| 1379 | throw new Exception( "This function $caller is not supported on the WP Replicastore" ); |
| 1380 | } |
| 1381 | |
| 1382 | /** |
| 1383 | * Determine number of buckets to use in full table checksum. |
| 1384 | * |
| 1385 | * @param string $table Object Type. |
| 1386 | * @param int $object_count Object count. |
| 1387 | * @return int Number of Buckets to use. |
| 1388 | */ |
| 1389 | private function calculate_buckets( $table, $object_count ) { |
| 1390 | // Ensure no division by 0. |
| 1391 | if ( 0 === (int) $object_count ) { |
| 1392 | return 1; |
| 1393 | } |
| 1394 | |
| 1395 | // Default Bucket sizes. |
| 1396 | $bucket_size = 10000; // Default bucket size is 10,000 items. |
| 1397 | switch ( $table ) { |
| 1398 | case 'postmeta': |
| 1399 | case 'commentmeta': |
| 1400 | case 'order_itemmeta': |
| 1401 | $bucket_size = 1000; // Meta bucket size is restricted to 1000 items. |
| 1402 | } |
| 1403 | |
| 1404 | return (int) ceil( $object_count / $bucket_size ); |
| 1405 | } |
| 1406 | |
| 1407 | /** |
| 1408 | * Return an instance for `Table_Checksum`, depending on the table. |
| 1409 | * |
| 1410 | * Some tables require custom instances, due to different checksum logic. |
| 1411 | * |
| 1412 | * @param string $table The table that we want to get the instance for. |
| 1413 | * @param string $salt Salt to be used when generating the checksums. |
| 1414 | * @param bool $perform_text_conversion Should we perform text encoding conversion when calculating the checksum. |
| 1415 | * @param array $additional_columns Additional columns to add to the checksum calculation. |
| 1416 | * |
| 1417 | * @return Table_Checksum|Table_Checksum_Usermeta |
| 1418 | * @throws Exception Might throw an exception if any of the input parameters were invalid. |
| 1419 | */ |
| 1420 | public function get_table_checksum_instance( $table, $salt = null, $perform_text_conversion = false, $additional_columns = null ) { |
| 1421 | if ( 'users' === $table ) { |
| 1422 | return new Table_Checksum_Users( $table, $salt, $perform_text_conversion, $additional_columns ); |
| 1423 | } |
| 1424 | if ( 'usermeta' === $table ) { |
| 1425 | return new Table_Checksum_Usermeta( $table, $salt, $perform_text_conversion, $additional_columns ); |
| 1426 | } |
| 1427 | |
| 1428 | return new Table_Checksum( $table, $salt, $perform_text_conversion, $additional_columns ); |
| 1429 | } |
| 1430 | } |