PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
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 13.7.2 13.8.3 All 506 releases
← All changes | jetpack_vendor/automattic/jetpack-sync/src/modules/class-posts.php +423 -55 12.8.3 → 16.3-a.5 View file →
@@ -8,10 +8,16 @@
8 8 namespace Automattic\Jetpack\Sync\Modules;
9 9
10 10 use Automattic\Jetpack\Constants as Jetpack_Constants;
11 11 use Automattic\Jetpack\Roles;
12 +use Automattic\Jetpack\Sync\Activity_Log_Event;
13 +use Automattic\Jetpack\Sync\Modules;
12 14 use Automattic\Jetpack\Sync\Settings;
13 15
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
14 20 /**
15 21 * Class to handle sync for posts.
16 22 */
17 23 class Posts extends Module {
@@ -42,8 +48,17 @@
42 48 */
43 49 private $action_handler;
44 50
45 51 /**
52 + * Mark posts that are deleted in the current request.
53 + *
54 + * @access private
55 + *
56 + * @var array
57 + */
58 + private static $deleted_posts_in_request = array();
59 +
60 + /**
46 61 * Import end.
47 62 *
48 63 * @access private
49 64 *
@@ -63,18 +78,8 @@
63 78 */
64 79 const MAX_POST_CONTENT_LENGTH = 5000000;
65 80
66 81 /**
67 - * Max bytes allowed for post meta_value => length.
68 - * Current Setting : 2MB.
69 - *
70 - * @access public
71 - *
72 - * @var int
73 - */
74 - const MAX_POST_META_LENGTH = 2000000;
75 -
76 - /**
77 82 * Default previous post state.
78 83 * Used for default previous post status.
79 84 *
80 85 * @access public
@@ -94,19 +99,33 @@
94 99 return 'posts';
95 100 }
96 101
97 102 /**
98 - * The table in the database.
103 + * The table name.
99 104 *
100 105 * @access public
101 106 *
102 107 * @return string
108 + * @deprecated since 3.11.0 Use table() instead.
103 109 */
104 110 public function table_name() {
111 + _deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Posts->table' );
105 112 return 'posts';
106 113 }
107 114
108 115 /**
116 + * The table in the database with the prefix.
117 + *
118 + * @access public
119 + *
120 + * @return string|bool
121 + */
122 + public function table() {
123 + global $wpdb;
124 + return $wpdb->posts;
125 + }
126 +
127 + /**
109 128 * Retrieve a post by its ID.
110 129 *
111 130 * @access public
112 131 *
@@ -134,14 +153,15 @@
134 153 */
135 154 public function init_listeners( $callable ) {
136 155 $this->action_handler = $callable;
137 156
157 + add_action( 'before_delete_post', array( $this, 'mark_post_is_being_deleted' ), 0, 1 );
138 158 add_action( 'wp_insert_post', array( $this, 'wp_insert_post' ), 11, 3 );
139 159 add_action( 'wp_after_insert_post', array( $this, 'wp_after_insert_post' ), 11, 2 );
140 160 add_action( 'jetpack_sync_save_post', $callable, 10, 4 );
141 161
142 162 add_action( 'deleted_post', $callable, 10 );
143 - add_action( 'jetpack_published_post', $callable, 10, 2 );
163 + add_action( 'jetpack_published_post', $callable, 10, 3 );
144 164 add_filter( 'jetpack_sync_before_enqueue_deleted_post', array( $this, 'filter_blacklisted_post_types_deleted' ) );
145 165
146 166 add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
147 167
@@ -146,15 +166,20 @@
146 166 add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
147 167
148 168 // Listen for meta changes.
149 169 $this->init_listeners_for_meta_type( 'post', $callable );
150 - $this->init_meta_whitelist_handler( 'post', array( $this, 'filter_meta' ) );
170 + add_filter( 'jetpack_sync_before_enqueue_added_post_meta', array( $this, 'filter_meta' ) );
171 + add_filter( 'jetpack_sync_before_enqueue_updated_post_meta', array( $this, 'filter_updated_post_meta' ) );
172 + add_filter( 'jetpack_sync_before_enqueue_deleted_post_meta', array( $this, 'filter_deleted_post_meta' ) );
151 173
152 174 add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_post', array( $this, 'filter_jetpack_sync_before_enqueue_jetpack_sync_save_post' ) );
175 + add_filter( 'jetpack_sync_before_enqueue_jetpack_published_post', array( $this, 'filter_jetpack_sync_before_enqueue_jetpack_published_post' ) );
153 176
154 177 add_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
155 178 add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
156 179 add_action( 'jetpack_post_meta_batch_delete', $callable, 10, 2 );
180 +
181 + add_action( 'deleted_post', array( $this, 'unmark_post_being_deleted' ), 11, 1 );
157 182 }
158 183
159 184 /**
160 185 * Before Akismet's daily cleanup of spam detection metadata.
@@ -215,13 +240,18 @@
215 240 * @access public
216 241 */
217 242 public function init_before_send() {
218 243 // meta.
219 - add_filter( 'jetpack_sync_before_send_added_post_meta', array( $this, 'trim_post_meta' ) );
220 - add_filter( 'jetpack_sync_before_send_updated_post_meta', array( $this, 'trim_post_meta' ) );
244 + add_filter( 'jetpack_sync_before_send_added_post_meta', array( $this, 'filter_added_post_meta_before_send' ), 5 ); // Incase this filter is used elsewhere, we run early.
245 + add_filter( 'jetpack_sync_before_send_updated_post_meta', array( $this, 'filter_updated_post_meta_before_send' ), 5 ); // Incase this filter is used elsewhere, we run early.
221 246 add_filter( 'jetpack_sync_before_send_deleted_post_meta', array( $this, 'trim_post_meta' ) );
222 247 // Full sync.
223 - add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_post_ids' ) );
248 + $sync_module = Modules::get_module( 'full-sync' );
249 + if ( $sync_module instanceof Full_Sync_Immediately ) {
250 + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'build_full_sync_action_array' ) );
251 + } else {
252 + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_posts_with_metadata_and_terms' ) );
253 + }
224 254 }
225 255
226 256 /**
227 257 * Enqueue the posts actions for full sync.
@@ -246,16 +276,16 @@
246 276 *
247 277 * @todo Use $wpdb->prepare for the SQL query.
248 278 *
249 279 * @param array $config Full sync configuration for this sync module.
250 - * @return array Number of items yet to be enqueued.
280 + * @return int Number of items yet to be enqueued.
251 281 */
252 282 public function estimate_full_sync_actions( $config ) {
253 283 global $wpdb;
254 284
255 285 $query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config );
256 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
257 - $count = $wpdb->get_var( $query );
286 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
287 + $count = (int) $wpdb->get_var( $query );
258 288
259 289 return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
260 290 }
261 291
@@ -270,9 +300,9 @@
270 300 public function get_where_sql( $config ) {
271 301 $where_sql = Settings::get_blacklisted_post_types_sql();
272 302
273 303 // Config is a list of post IDs to sync.
274 - if ( is_array( $config ) ) {
304 + if ( is_array( $config ) && ! empty( $config ) ) {
275 305 $where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
276 306 }
277 307
278 308 return $where_sql;
@@ -289,9 +319,9 @@
289 319 return array( 'jetpack_full_sync_posts' );
290 320 }
291 321
292 322 /**
293 - * Filter meta arguments so that we don't sync meta_values over MAX_POST_META_LENGTH.
323 + * Filter meta arguments so that we don't sync meta_values over MAX_META_LENGTH.
294 324 *
295 325 * @param array $args action arguments.
296 326 *
297 327 * @return array filtered action arguments.
@@ -300,9 +330,9 @@
300 330 list( $meta_id, $object_id, $meta_key, $meta_value ) = $args;
301 331 // Explicitly truncate meta_value when it exceeds limit.
302 332 // Large content will cause OOM issues and break Sync.
303 333 $serialized_value = maybe_serialize( $meta_value );
304 - if ( strlen( $serialized_value ) >= self::MAX_POST_META_LENGTH ) {
334 + if ( $serialized_value === null || strlen( $serialized_value ) >= self::MAX_META_LENGTH ) {
305 335 $meta_value = '';
306 336 }
307 337 return array( $meta_id, $object_id, $meta_key, $meta_value );
308 338 }
@@ -307,8 +337,112 @@
307 337 return array( $meta_id, $object_id, $meta_key, $meta_value );
308 338 }
309 339
310 340 /**
341 + * Updated post meta send-time filter: refreshes _wp_attachment_metadata to the latest DB value, then trims.
342 + *
343 + * @param array $args [ $meta_id, $object_id, $meta_key, $meta_value ].
344 + * @return array Filtered args.
345 + */
346 + public function filter_updated_post_meta_before_send( $args ) {
347 + if ( ! is_array( $args ) || count( $args ) < 4 ) {
348 + return $args;
349 + }
350 + list( $meta_id, $object_id, $meta_key, $meta_value ) = $args;
351 + if ( '_wp_attachment_metadata' !== $meta_key || 'attachment' !== get_post_type( (int) $object_id ) ) {
352 + return $this->trim_post_meta( $args );
353 + }
354 + $current_value = wp_get_attachment_metadata( (int) $object_id );
355 + if ( is_array( $current_value ) && ! empty( $current_value ) ) {
356 + $meta_value = $current_value;
357 + }
358 + return $this->trim_post_meta( array( $meta_id, $object_id, $meta_key, $meta_value ) );
359 + }
360 +
361 + /**
362 + * Added post meta send-time filter: refreshes _wp_attachment_metadata to the latest DB value, then trims.
363 + *
364 + * @param array $args [ $meta_id, $object_id, $meta_key, $meta_value ].
365 + * @return array|false Filtered args, or false to skip sending when the snapshot is clearly incomplete.
366 + */
367 + public function filter_added_post_meta_before_send( $args ) {
368 + if ( ! is_array( $args ) || count( $args ) < 4 ) {
369 + return $args;
370 + }
371 + list( $meta_id, $object_id, $meta_key, $meta_value ) = $args;
372 + if ( '_wp_attachment_metadata' !== $meta_key || 'attachment' !== get_post_type( (int) $object_id ) ) {
373 + return $this->trim_post_meta( $args );
374 + }
375 + $current_value = wp_get_attachment_metadata( (int) $object_id );
376 + // For added_post_meta, skip clearly incomplete snapshots (e.g., missing or empty sizes).
377 + if ( ! is_array( $current_value ) || empty( $current_value ) ) {
378 + return false;
379 + }
380 + if ( isset( $current_value['sizes'] ) && is_array( $current_value['sizes'] ) && count( $current_value['sizes'] ) === 0 ) {
381 + return false;
382 + }
383 + $meta_value = $current_value;
384 + return $this->trim_post_meta( array( $meta_id, $object_id, $meta_key, $meta_value ) );
385 + }
386 +
387 + /**
388 + * Mark a post as being deleted in the current request.
389 + *
390 + * @param int $post_id ID of the post being deleted.
391 + */
392 + public function mark_post_is_being_deleted( $post_id ) {
393 + self::$deleted_posts_in_request[ (int) $post_id ] = true;
394 + }
395 +
396 + /**
397 + * Enqueue-time per-request dedupe for deleted post metadata, if the post itself is being deleted.
398 + *
399 + * @param array $args [ $meta_id, $post_id, $meta_key, $meta_value ].
400 + * @return array|false
401 + */
402 + public function maybe_skip_deleted_post_meta( $args ) {
403 + if ( is_array( $args ) && isset( $args[1] ) && is_numeric( $args[1] ) ) {
404 + $post_id = (int) $args[1];
405 + if ( isset( self::$deleted_posts_in_request[ $post_id ] ) ) {
406 + return false;
407 + }
408 + }
409 + return $args;
410 + }
411 +
412 + /**
413 + * Unmark a post as being deleted in the current request, to clean up.
414 + *
415 + * @param int $post_id ID of the post.
416 + */
417 + public function unmark_post_being_deleted( $post_id ) {
418 + unset( self::$deleted_posts_in_request[ (int) $post_id ] );
419 + }
420 +
421 + /**
422 + * Enqueue-time per-request dedupe for updated attachment metadata.
423 + *
424 + * @param array $args [ $meta_id, $object_id, $meta_key, $meta_value ].
425 + * @return array|false
426 + */
427 + public function on_before_enqueue_updated_attachment_metadata( $args ) {
428 + if ( ! is_array( $args ) || count( $args ) < 3 ) {
429 + return $args;
430 + }
431 + $post_id = (int) $args[1];
432 + $meta_key = $args[2];
433 + if ( '_wp_attachment_metadata' !== $meta_key || 'attachment' !== get_post_type( $post_id ) ) {
434 + return $args;
435 + }
436 + static $seen_updated_meta_for_post = array();
437 + if ( isset( $seen_updated_meta_for_post[ $post_id ] ) ) {
438 + return false;
439 + }
440 + $seen_updated_meta_for_post[ $post_id ] = true;
441 + return $args;
442 + }
443 +
444 + /**
311 445 * Process content before send.
312 446 *
313 447 * @param array $args Arguments of the `wp_insert_post` hook.
314 448 *
@@ -325,18 +459,61 @@
325 459 * @param array $args Hook arguments.
326 460 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
327 461 */
328 462 public function filter_jetpack_sync_before_enqueue_jetpack_sync_save_post( $args ) {
329 - list( $post_id, $post, $update, $previous_state ) = $args;
463 + if (
464 + ! is_array( $args )
465 + || ! array_key_exists( 0, $args ) || ! is_numeric( $args[0] )
466 + || ! array_key_exists( 1, $args ) || ! ( $args[1] instanceof \WP_Post )
467 + ) {
468 + return false;
469 + }
330 470
471 + list( $post_id, $post, $update, $previous_state ) = array_pad( $args, 4, null );
472 +
331 473 if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
332 474 return false;
333 475 }
334 476
335 - return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
477 + // During incremental sync, skip posts whose type is not registered (e.g. CPT unregistered before sync).
478 + // Full sync may have already sent them; we simply don't enqueue incremental updates for them.
479 + if ( ! get_post_type_object( $post->post_type ) ) {
480 + return false;
481 + }
482 +
483 + if ( Activity_Log_Event::POST_TYPE === $post->post_type && ! Activity_Log_Event::is_valid_post( $post ) ) {
484 + return false;
485 + }
486 +
487 + return array( (int) $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
336 488 }
337 489
338 490 /**
491 + * Add filtered post content.
492 + *
493 + * @param array $args Hook arguments.
494 + * @return array|false Hook arguments, or false if the arguments are invalid.
495 + */
496 + public function filter_jetpack_sync_before_enqueue_jetpack_published_post( $args ) {
497 + if (
498 + ! is_array( $args )
499 + || ! array_key_exists( 0, $args ) || ! is_numeric( $args[0] )
500 + || ! array_key_exists( 1, $args ) || ! is_array( $args[1] )
501 + || ! array_key_exists( 2, $args ) || ! ( $args[2] instanceof \WP_Post )
502 + ) {
503 + return false;
504 + }
505 +
506 + list( $post_id, $flags, $post ) = $args;
507 +
508 + if ( Activity_Log_Event::POST_TYPE === $post->post_type && ! Activity_Log_Event::is_valid_post( $post ) ) {
509 + return false;
510 + }
511 +
512 + return array( (int) $post_id, $flags, $this->filter_post_content_and_add_links( $post ) );
513 + }
514 +
515 + /**
339 516 * Filter all blacklisted post types.
340 517 *
341 518 * @param array $args Hook arguments.
342 519 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
@@ -341,9 +518,11 @@
341 518 * @param array $args Hook arguments.
342 519 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
343 520 */
344 521 public function filter_blacklisted_post_types_deleted( $args ) {
345 -
522 + if ( ! is_array( $args ) || ! array_key_exists( 0, $args ) || ! is_numeric( $args[0] ) ) {
523 + return false;
524 + }
346 525 // deleted_post is called after the SQL delete but before cache cleanup.
347 526 // There is the potential we can't detect post_type at this point.
348 527 if ( ! $this->is_post_type_allowed( $args[0] ) ) {
349 528 return false;
@@ -354,20 +533,84 @@
354 533
355 534 /**
356 535 * Filter all meta that is not blacklisted, or is stored for a disallowed post type.
357 536 *
358 - * @param array $args Hook arguments.
537 + * @param array|false $args Hook arguments.
359 538 * @return array|false Hook arguments, or false if meta was filtered.
360 539 */
361 540 public function filter_meta( $args ) {
362 - if ( $this->is_post_type_allowed( $args[1] ) && $this->is_whitelisted_post_meta( $args[2] ) ) {
363 - return $args;
541 + if ( ! $this->has_valid_meta_args( $args ) || ! is_numeric( $args[1] ) ) {
542 + return false;
364 543 }
365 544
366 - return false;
545 + return $this->is_allowed_post_meta( $args[1], $args[2] ) ? $args : false;
367 546 }
368 547
369 548 /**
549 + * Filter updated post meta that is not whitelisted, is stored for a disallowed post type,
550 + * or is duplicate attachment metadata.
551 + *
552 + * @param array|false $args Hook arguments.
553 + * @return array|false Hook arguments, or false if meta was filtered.
554 + */
555 + public function filter_updated_post_meta( $args ) {
556 + $args = $this->on_before_enqueue_updated_attachment_metadata( $args );
557 + if ( false === $args ) {
558 + return false;
559 + }
560 +
561 + return $this->filter_meta( $args );
562 + }
563 +
564 + /**
565 + * Filter deleted post meta that is not whitelisted, or is stored for a disallowed post type.
566 + *
567 + * @param array|false $args Hook arguments.
568 + * @return array|false Hook arguments, or false if meta was filtered.
569 + */
570 + public function filter_deleted_post_meta( $args ) {
571 + if ( ! $this->has_valid_meta_args( $args ) ) {
572 + return false;
573 + }
574 + // Core uses post ID 0 on this hook for delete-all metadata operations. Only mirror value-constrained deletes.
575 + if ( 0 === $args[1] ) {
576 + if ( ! array_key_exists( 3, $args ) || '' === $args[3] || null === $args[3] || false === $args[3] ) {
577 + return false;
578 + }
579 +
580 + return $this->is_whitelisted_post_meta( $args[2] ) ? $args : false;
581 + }
582 +
583 + $args = $this->filter_meta( $args );
584 + if ( false === $args ) {
585 + return false;
586 + }
587 +
588 + return $this->maybe_skip_deleted_post_meta( $args );
589 + }
590 +
591 + /**
592 + * Whether metadata hook arguments include a meta key.
593 + *
594 + * @param array|false $args Hook arguments.
595 + * @return bool Whether metadata hook arguments include a meta key.
596 + */
597 + private function has_valid_meta_args( $args ) {
598 + return is_array( $args ) && array_key_exists( 1, $args ) && array_key_exists( 2, $args ) && is_string( $args[2] );
599 + }
600 +
601 + /**
602 + * Whether post metadata is allowed to sync.
603 + *
604 + * @param int|string $object_id Post ID.
605 + * @param string $meta_key Meta key.
606 + * @return bool Whether post metadata is allowed to sync.
607 + */
608 + private function is_allowed_post_meta( $object_id, $meta_key ) {
609 + return $this->is_post_type_allowed( $object_id ) && $this->is_whitelisted_post_meta( $meta_key );
610 + }
611 +
612 + /**
370 613 * Whether a post meta key is whitelisted.
371 614 *
372 615 * @param string $meta_key Meta key.
373 616 * @return boolean Whether the post meta key is whitelisted.
@@ -372,10 +615,13 @@
372 615 * @param string $meta_key Meta key.
373 616 * @return boolean Whether the post meta key is whitelisted.
374 617 */
375 618 public function is_whitelisted_post_meta( $meta_key ) {
376 - // The _wpas_skip_ meta key is used by Publicize.
377 - return in_array( $meta_key, Settings::get_setting( 'post_meta_whitelist' ), true ) || ( 0 === strpos( $meta_key, '_wpas_skip_' ) );
619 + if ( ! is_string( $meta_key ) ) {
620 + return false;
621 + }
622 + // The '_wpas_skip_' meta key prefix is used by Publicize to mark posts that should be skipped.
623 + return str_starts_with( $meta_key, '_wpas_skip_' ) || in_array( $meta_key, Settings::get_setting( 'post_meta_whitelist' ), true );
378 624 }
379 625
380 626 /**
381 627 * Whether a post type is allowed.
@@ -427,8 +673,12 @@
427 673 * @param \WP_Post $post_object Post object.
428 674 */
429 675 public function filter_post_content_and_add_links( $post_object ) {
430 676 global $post;
677 +
678 + // Used to restore the post global.
679 + $current_post = $post;
680 +
431 681 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
432 682 $post = $post_object;
433 683
434 684 // Return non existant post.
@@ -439,8 +689,11 @@
439 689 $non_existant_post->post_modified = $post->post_modified;
440 690 $non_existant_post->post_modified_gmt = $post->post_modified_gmt;
441 691 $non_existant_post->post_status = 'jetpack_sync_non_registered_post_type';
442 692 $non_existant_post->post_type = $post->post_type;
693 + // Restore global post.
694 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
695 + $post = $current_post;
443 696
444 697 return $non_existant_post;
445 698 }
446 699 /**
@@ -466,8 +719,12 @@
466 719 $blocked_post->post_modified_gmt = $post->post_modified_gmt;
467 720 $blocked_post->post_status = 'jetpack_sync_blocked';
468 721 $blocked_post->post_type = $post->post_type;
469 722
723 + // Restore global post.
724 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
725 + $post = $current_post;
726 +
470 727 return $blocked_post;
471 728 }
472 729
473 730 // lets not do oembed just yet.
@@ -545,9 +802,15 @@
545 802 if ( function_exists( 'amp_get_permalink' ) ) {
546 803 $post->amp_permalink = amp_get_permalink( $post->ID );
547 804 }
548 805
549 - return $post;
806 + $filtered_post = $post;
807 +
808 + // Restore global post.
809 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
810 + $post = $current_post;
811 +
812 + return $filtered_post;
550 813 }
551 814
552 815 /**
553 816 * Handle transition from another post status to a published one.
@@ -556,8 +819,11 @@
556 819 * @param string $old_status Old post status.
557 820 * @param \WP_Post $post Post object.
558 821 */
559 822 public function save_published( $new_status, $old_status, $post ) {
823 + if ( ! $post instanceof \WP_Post ) {
824 + return;
825 + }
560 826 if ( 'publish' === $new_status && 'publish' !== $old_status ) {
561 827 $this->just_published[ $post->ID ] = true;
562 828 }
563 829
@@ -571,14 +837,12 @@
571 837 *
572 838 * The 2nd request is to update post meta, which is not supported on WP REST API.
573 839 * When syncing post data, we will include if this was a meta box update.
574 840 *
575 - * @todo Implement nonce verification.
576 - *
577 841 * @return boolean Whether this is a Gutenberg meta box update.
578 842 */
579 - public function is_gutenberg_meta_box_update() {
580 - // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
843 + private function is_gutenberg_meta_box_update() {
844 + // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- We only check the request to determine if this is a Gutenberg meta box update, and we only use the result to set a boolean logged in the sync event. If anyone anywhere else gets the flag and does something CSRF-able with it, they should ensure that a nonce has been checked.
581 845 return (
582 846 isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
583 847 'editpost' === $_POST['action'] &&
584 848 '1' === $_GET['classic-editor'] &&
@@ -595,20 +859,20 @@
595 859 * @param \WP_Post $post Post object.
596 860 * @param boolean $update Whether this is an existing post being updated or not.
597 861 */
598 862 public function wp_insert_post( $post_ID, $post = null, $update = null ) {
599 - if ( ! is_numeric( $post_ID ) || $post === null ) {
863 + if ( ! is_numeric( $post_ID ) || ! $post instanceof \WP_Post ) {
600 864 return;
601 865 }
602 866
603 867 // Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
604 - if ( $post && 'shop_order' === $post->post_type ) {
868 + if ( 'shop_order' === $post->post_type ) {
605 869 $post = get_post( $post_ID );
606 870 }
607 871
608 - $previous_status = isset( $this->previous_status[ $post_ID ] ) ? $this->previous_status[ $post_ID ] : self::DEFAULT_PREVIOUS_STATE;
872 + $previous_status = $this->previous_status[ $post_ID ] ?? self::DEFAULT_PREVIOUS_STATE;
609 873
610 - $just_published = isset( $this->just_published[ $post_ID ] ) ? $this->just_published[ $post_ID ] : false;
874 + $just_published = $this->just_published[ $post_ID ] ?? false;
611 875
612 876 $state = array(
613 877 'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
614 878 'previous_status' => $previous_status,
@@ -639,14 +903,14 @@
639 903 * @param int $post_ID Post ID.
640 904 * @param \WP_Post $post Post object.
641 905 **/
642 906 public function wp_after_insert_post( $post_ID, $post ) {
643 - if ( ! is_numeric( $post_ID ) || $post === null ) {
907 + if ( ! is_numeric( $post_ID ) || ! $post instanceof \WP_Post ) {
644 908 return;
645 909 }
646 910
647 911 // Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
648 - if ( $post && 'shop_order' === $post->post_type ) {
912 + if ( 'shop_order' === $post->post_type ) {
649 913 $post = get_post( $post_ID );
650 914 }
651 915
652 916 $this->send_published( $post_ID, $post );
@@ -698,12 +962,8 @@
698 962
699 963 // Only Send Pulished Post event if post_type is not blacklisted.
700 964 if ( ! in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
701 965
702 - // Refreshing the post in the cache site before triggering the publish event.
703 - // The true parameter means that it's an update action, not create action.
704 - $this->wp_insert_post( $post_ID, $post, true );
705 -
706 966 /**
707 967 * Action that gets synced when a post type gets published.
708 968 *
709 969 * @since 1.6.3
@@ -710,10 +970,11 @@
710 970 * @since-jetpack 4.4.0
711 971 *
712 972 * @param int $post_ID
713 973 * @param mixed array $flags post flags that are added to the post
974 + * @param WP_Post $post The post object
714 975 */
715 - do_action( 'jetpack_published_post', $post_ID, $flags );
976 + do_action( 'jetpack_published_post', $post_ID, $flags, $post );
716 977 }
717 978 unset( $this->just_published[ $post_ID ] );
718 979
719 980 /**
@@ -720,8 +981,11 @@
720 981 * Send additional sync action for Activity Log when post is a Customizer publish
721 982 */
722 983 if ( 'customize_changeset' === $post->post_type ) {
723 984 $post_content = json_decode( $post->post_content, true );
985 + if ( ! is_iterable( $post_content ) ) {
986 + return;
987 + }
724 988 foreach ( $post_content as $key => $value ) {
725 989 // Skip if it isn't a widget.
726 990 if ( 'widget_' !== substr( $key, 0, strlen( 'widget_' ) ) ) {
727 991 continue;
@@ -735,9 +999,9 @@
735 999 if ( isset( $wp_registered_widgets[ $key ] ) ) {
736 1000 $widget_data = array(
737 1001 'name' => $wp_registered_widgets[ $key ]['name'],
738 1002 'id' => $key,
739 - 'title' => $value['value']['title'],
1003 + 'title' => $value['value']['title'] ?? '',
740 1004 );
741 1005 do_action( 'jetpack_widget_edited', $widget_data );
742 1006 }
743 1007 }
@@ -744,9 +1008,51 @@
744 1008 }
745 1009 }
746 1010
747 1011 /**
1012 + * Build the full sync action object for Posts.
1013 + *
1014 + * @access public
1015 + *
1016 + * @param array $args An array with the posts and the previous end.
1017 + *
1018 + * @return array An array with the posts, postmeta and the previous end.
1019 + */
1020 + public function build_full_sync_action_array( $args ) {
1021 + list( $filtered_posts, $previous_end ) = $args;
1022 + return array(
1023 + $filtered_posts['objects'],
1024 + $filtered_posts['meta'],
1025 + array(), // WPCOM does not process term relationships in full sync posts actions for a while now, let's skip them.
1026 + $previous_end,
1027 + );
1028 + }
1029 +
1030 + /**
1031 + * Add term relationships to post objects within a hook before they are serialized and sent to the server.
1032 + * This is used in Full Sync Immediately
1033 + *
1034 + * @access public
1035 + *
1036 + * @param array $args The hook parameters.
1037 + * @return array $args The expanded hook parameters.
1038 + * @deprecated since 4.7.0
1039 + */
1040 + public function add_term_relationships( $args ) {
1041 + _deprecated_function( __METHOD__, '4.7.0' );
1042 + list( $filtered_posts, $previous_interval_end ) = $args;
1043 +
1044 + return array(
1045 + $filtered_posts['objects'],
1046 + $filtered_posts['meta'],
1047 + $this->get_term_relationships( $filtered_posts['object_ids'] ),
1048 + $previous_interval_end,
1049 + );
1050 + }
1051 +
1052 + /**
748 1053 * Expand post IDs to post objects within a hook before they are serialized and sent to the server.
1054 + * This is used in Legacy Full Sync
749 1055 *
750 1056 * @access public
751 1057 *
752 1058 * @param array $args The hook parameters.
@@ -751,19 +1057,19 @@
751 1057 *
752 1058 * @param array $args The hook parameters.
753 1059 * @return array $args The expanded hook parameters.
754 1060 */
755 - public function expand_post_ids( $args ) {
756 - list( $post_ids, $previous_interval_end) = $args;
1061 + public function expand_posts_with_metadata_and_terms( $args ) {
1062 + list( $post_ids, $previous_interval_end ) = $args;
757 1063
758 - $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
759 - $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
760 - $posts = array_values( $posts ); // Reindex in case posts were deleted.
1064 + $posts = $this->expand_posts( $post_ids );
1065 + $posts_metadata = $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
1066 + $term_relationships = $this->get_term_relationships( $post_ids );
761 1067
762 1068 return array(
763 1069 $posts,
764 - $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) ),
765 - $this->get_term_relationships( $post_ids ),
1070 + $posts_metadata,
1071 + $term_relationships,
766 1072 $previous_interval_end,
767 1073 );
768 1074 }
769 1075
@@ -778,6 +1084,68 @@
778 1084 * @return array|bool An array of min and max ids for each batch. FALSE if no table can be found.
779 1085 */
780 1086 public function get_min_max_object_ids_for_batches( $batch_size, $where_sql = false ) {
781 1087 return parent::get_min_max_object_ids_for_batches( $batch_size, $this->get_where_sql( $where_sql ) );
1088 + }
1089 +
1090 + /**
1091 + * Given the Module Configuration and Status return the next chunk of items to send.
1092 + * This function also expands the posts and metadata and filters them based on the maximum size constraints.
1093 + *
1094 + * @param array $config This module Full Sync configuration.
1095 + * @param array $status This module Full Sync status.
1096 + * @param int $chunk_size Chunk size.
1097 + *
1098 + * @return array
1099 + */
1100 + public function get_next_chunk( $config, $status, $chunk_size ) {
1101 +
1102 + $post_ids = parent::get_next_chunk( $config, $status, $chunk_size );
1103 +
1104 + if ( empty( $post_ids ) ) {
1105 + return array();
1106 + }
1107 +
1108 + $posts = $this->expand_posts( $post_ids );
1109 +
1110 + // If no posts were fetched, make sure to return the expected structure so that status is updated correctly.
1111 + if ( empty( $posts ) ) {
1112 + return array(
1113 + 'object_ids' => $post_ids,
1114 + 'objects' => array(),
1115 + 'meta' => array(),
1116 + );
1117 + }
1118 + // Get the post IDs from the posts that were fetched.
1119 + $fetched_post_ids = wp_list_pluck( $posts, 'ID' );
1120 + $metadata = $this->get_metadata( $fetched_post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
1121 +
1122 + // Filter the posts and metadata based on the maximum size constraints.
1123 + list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $this->filter_objects_and_metadata_by_size(
1124 + 'post',
1125 + $posts,
1126 + $metadata,
1127 + self::MAX_META_LENGTH,
1128 + self::MAX_SIZE_FULL_SYNC
1129 + );
1130 +
1131 + return array(
1132 + 'object_ids' => $filtered_post_ids,
1133 + 'objects' => $filtered_posts,
1134 + 'meta' => $filtered_posts_metadata,
1135 + );
1136 + }
1137 +
1138 + /**
1139 + * Expand posts.
1140 + *
1141 + * @param array $post_ids Post IDs.
1142 + *
1143 + * @return array Expanded posts.
1144 + */
1145 + private function expand_posts( $post_ids ) {
1146 + $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
1147 + $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
1148 + $posts = array_values( $posts ); // Reindex in case posts were deleted.
1149 + return $posts;
782 1150 }
783 1151 }