PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
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 +357 -138 13.0.2 → 16.3-a.7 View file →
@@ -8,11 +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;
12 13 use Automattic\Jetpack\Sync\Modules;
13 14 use Automattic\Jetpack\Sync\Settings;
14 15
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
15 20 /**
16 21 * Class to handle sync for posts.
17 22 */
18 23 class Posts extends Module {
@@ -43,8 +48,17 @@
43 48 */
44 49 private $action_handler;
45 50
46 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 + /**
47 61 * Import end.
48 62 *
49 63 * @access private
50 64 *
@@ -64,28 +78,8 @@
64 78 */
65 79 const MAX_POST_CONTENT_LENGTH = 5000000;
66 80
67 81 /**
68 - * Max bytes allowed for post meta_value => length.
69 - * Current Setting : 2MB.
70 - *
71 - * @access public
72 - *
73 - * @var int
74 - */
75 - const MAX_POST_META_LENGTH = 2000000;
76 -
77 - /**
78 - * Max bytes allowed for full sync upload.
79 - * Current Setting : 7MB.
80 - *
81 - * @access public
82 - *
83 - * @var int
84 - */
85 - const MAX_SIZE_FULL_SYNC = 7000000;
86 -
87 - /**
88 82 * Default previous post state.
89 83 * Used for default previous post status.
90 84 *
91 85 * @access public
@@ -105,19 +99,33 @@
105 99 return 'posts';
106 100 }
107 101
108 102 /**
109 - * The table in the database.
103 + * The table name.
110 104 *
111 105 * @access public
112 106 *
113 107 * @return string
108 + * @deprecated since 3.11.0 Use table() instead.
114 109 */
115 110 public function table_name() {
111 + _deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Posts->table' );
116 112 return 'posts';
117 113 }
118 114
119 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 + /**
120 128 * Retrieve a post by its ID.
121 129 *
122 130 * @access public
123 131 *
@@ -145,14 +153,15 @@
145 153 */
146 154 public function init_listeners( $callable ) {
147 155 $this->action_handler = $callable;
148 156
157 + add_action( 'before_delete_post', array( $this, 'mark_post_is_being_deleted' ), 0, 1 );
149 158 add_action( 'wp_insert_post', array( $this, 'wp_insert_post' ), 11, 3 );
150 159 add_action( 'wp_after_insert_post', array( $this, 'wp_after_insert_post' ), 11, 2 );
151 160 add_action( 'jetpack_sync_save_post', $callable, 10, 4 );
152 161
153 162 add_action( 'deleted_post', $callable, 10 );
154 - add_action( 'jetpack_published_post', $callable, 10, 2 );
163 + add_action( 'jetpack_published_post', $callable, 10, 3 );
155 164 add_filter( 'jetpack_sync_before_enqueue_deleted_post', array( $this, 'filter_blacklisted_post_types_deleted' ) );
156 165
157 166 add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
158 167
@@ -157,15 +166,20 @@
157 166 add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
158 167
159 168 // Listen for meta changes.
160 169 $this->init_listeners_for_meta_type( 'post', $callable );
161 - $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' ) );
162 173
163 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' ) );
164 176
165 177 add_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
166 178 add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
167 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 );
168 182 }
169 183
170 184 /**
171 185 * Before Akismet's daily cleanup of spam detection metadata.
@@ -226,15 +240,15 @@
226 240 * @access public
227 241 */
228 242 public function init_before_send() {
229 243 // meta.
230 - add_filter( 'jetpack_sync_before_send_added_post_meta', array( $this, 'trim_post_meta' ) );
231 - 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.
232 246 add_filter( 'jetpack_sync_before_send_deleted_post_meta', array( $this, 'trim_post_meta' ) );
233 247 // Full sync.
234 248 $sync_module = Modules::get_module( 'full-sync' );
235 - if ( $sync_module && str_contains( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
236 - add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'add_term_relationships' ) );
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' ) );
237 251 } else {
238 252 add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_posts_with_metadata_and_terms' ) );
239 253 }
240 254 }
@@ -262,16 +276,16 @@
262 276 *
263 277 * @todo Use $wpdb->prepare for the SQL query.
264 278 *
265 279 * @param array $config Full sync configuration for this sync module.
266 - * @return array Number of items yet to be enqueued.
280 + * @return int Number of items yet to be enqueued.
267 281 */
268 282 public function estimate_full_sync_actions( $config ) {
269 283 global $wpdb;
270 284
271 285 $query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config );
272 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
273 - $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 );
274 288
275 289 return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
276 290 }
277 291
@@ -286,9 +300,9 @@
286 300 public function get_where_sql( $config ) {
287 301 $where_sql = Settings::get_blacklisted_post_types_sql();
288 302
289 303 // Config is a list of post IDs to sync.
290 - if ( is_array( $config ) ) {
304 + if ( is_array( $config ) && ! empty( $config ) ) {
291 305 $where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
292 306 }
293 307
294 308 return $where_sql;
@@ -305,9 +319,9 @@
305 319 return array( 'jetpack_full_sync_posts' );
306 320 }
307 321
308 322 /**
309 - * 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.
310 324 *
311 325 * @param array $args action arguments.
312 326 *
313 327 * @return array filtered action arguments.
@@ -316,9 +330,9 @@
316 330 list( $meta_id, $object_id, $meta_key, $meta_value ) = $args;
317 331 // Explicitly truncate meta_value when it exceeds limit.
318 332 // Large content will cause OOM issues and break Sync.
319 333 $serialized_value = maybe_serialize( $meta_value );
320 - if ( strlen( $serialized_value ) >= self::MAX_POST_META_LENGTH ) {
334 + if ( $serialized_value === null || strlen( $serialized_value ) >= self::MAX_META_LENGTH ) {
321 335 $meta_value = '';
322 336 }
323 337 return array( $meta_id, $object_id, $meta_key, $meta_value );
324 338 }
@@ -323,8 +337,112 @@
323 337 return array( $meta_id, $object_id, $meta_key, $meta_value );
324 338 }
325 339
326 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 + /**
327 445 * Process content before send.
328 446 *
329 447 * @param array $args Arguments of the `wp_insert_post` hook.
330 448 *
@@ -341,18 +459,61 @@
341 459 * @param array $args Hook arguments.
342 460 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
343 461 */
344 462 public function filter_jetpack_sync_before_enqueue_jetpack_sync_save_post( $args ) {
345 - 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 + }
346 470
471 + list( $post_id, $post, $update, $previous_state ) = array_pad( $args, 4, null );
472 +
347 473 if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
348 474 return false;
349 475 }
350 476
351 - 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 );
352 488 }
353 489
354 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 + /**
355 516 * Filter all blacklisted post types.
356 517 *
357 518 * @param array $args Hook arguments.
358 519 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
@@ -357,9 +518,11 @@
357 518 * @param array $args Hook arguments.
358 519 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
359 520 */
360 521 public function filter_blacklisted_post_types_deleted( $args ) {
361 -
522 + if ( ! is_array( $args ) || ! array_key_exists( 0, $args ) || ! is_numeric( $args[0] ) ) {
523 + return false;
524 + }
362 525 // deleted_post is called after the SQL delete but before cache cleanup.
363 526 // There is the potential we can't detect post_type at this point.
364 527 if ( ! $this->is_post_type_allowed( $args[0] ) ) {
365 528 return false;
@@ -370,20 +533,84 @@
370 533
371 534 /**
372 535 * Filter all meta that is not blacklisted, or is stored for a disallowed post type.
373 536 *
374 - * @param array $args Hook arguments.
537 + * @param array|false $args Hook arguments.
375 538 * @return array|false Hook arguments, or false if meta was filtered.
376 539 */
377 540 public function filter_meta( $args ) {
378 - if ( $this->is_post_type_allowed( $args[1] ) && $this->is_whitelisted_post_meta( $args[2] ) ) {
379 - return $args;
541 + if ( ! $this->has_valid_meta_args( $args ) || ! is_numeric( $args[1] ) ) {
542 + return false;
380 543 }
381 544
382 - return false;
545 + return $this->is_allowed_post_meta( $args[1], $args[2] ) ? $args : false;
383 546 }
384 547
385 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 + /**
386 613 * Whether a post meta key is whitelisted.
387 614 *
388 615 * @param string $meta_key Meta key.
389 616 * @return boolean Whether the post meta key is whitelisted.
@@ -388,10 +615,13 @@
388 615 * @param string $meta_key Meta key.
389 616 * @return boolean Whether the post meta key is whitelisted.
390 617 */
391 618 public function is_whitelisted_post_meta( $meta_key ) {
392 - // The _wpas_skip_ meta key is used by Publicize.
393 - return in_array( $meta_key, Settings::get_setting( 'post_meta_whitelist' ), true ) || str_starts_with( $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 );
394 624 }
395 625
396 626 /**
397 627 * Whether a post type is allowed.
@@ -443,8 +673,12 @@
443 673 * @param \WP_Post $post_object Post object.
444 674 */
445 675 public function filter_post_content_and_add_links( $post_object ) {
446 676 global $post;
677 +
678 + // Used to restore the post global.
679 + $current_post = $post;
680 +
447 681 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
448 682 $post = $post_object;
449 683
450 684 // Return non existant post.
@@ -455,8 +689,11 @@
455 689 $non_existant_post->post_modified = $post->post_modified;
456 690 $non_existant_post->post_modified_gmt = $post->post_modified_gmt;
457 691 $non_existant_post->post_status = 'jetpack_sync_non_registered_post_type';
458 692 $non_existant_post->post_type = $post->post_type;
693 + // Restore global post.
694 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
695 + $post = $current_post;
459 696
460 697 return $non_existant_post;
461 698 }
462 699 /**
@@ -482,8 +719,12 @@
482 719 $blocked_post->post_modified_gmt = $post->post_modified_gmt;
483 720 $blocked_post->post_status = 'jetpack_sync_blocked';
484 721 $blocked_post->post_type = $post->post_type;
485 722
723 + // Restore global post.
724 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
725 + $post = $current_post;
726 +
486 727 return $blocked_post;
487 728 }
488 729
489 730 // lets not do oembed just yet.
@@ -561,9 +802,15 @@
561 802 if ( function_exists( 'amp_get_permalink' ) ) {
562 803 $post->amp_permalink = amp_get_permalink( $post->ID );
563 804 }
564 805
565 - 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;
566 813 }
567 814
568 815 /**
569 816 * Handle transition from another post status to a published one.
@@ -572,8 +819,11 @@
572 819 * @param string $old_status Old post status.
573 820 * @param \WP_Post $post Post object.
574 821 */
575 822 public function save_published( $new_status, $old_status, $post ) {
823 + if ( ! $post instanceof \WP_Post ) {
824 + return;
825 + }
576 826 if ( 'publish' === $new_status && 'publish' !== $old_status ) {
577 827 $this->just_published[ $post->ID ] = true;
578 828 }
579 829
@@ -587,14 +837,12 @@
587 837 *
588 838 * The 2nd request is to update post meta, which is not supported on WP REST API.
589 839 * When syncing post data, we will include if this was a meta box update.
590 840 *
591 - * @todo Implement nonce verification.
592 - *
593 841 * @return boolean Whether this is a Gutenberg meta box update.
594 842 */
595 - public function is_gutenberg_meta_box_update() {
596 - // 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.
597 845 return (
598 846 isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
599 847 'editpost' === $_POST['action'] &&
600 848 '1' === $_GET['classic-editor'] &&
@@ -611,20 +859,20 @@
611 859 * @param \WP_Post $post Post object.
612 860 * @param boolean $update Whether this is an existing post being updated or not.
613 861 */
614 862 public function wp_insert_post( $post_ID, $post = null, $update = null ) {
615 - if ( ! is_numeric( $post_ID ) || $post === null ) {
863 + if ( ! is_numeric( $post_ID ) || ! $post instanceof \WP_Post ) {
616 864 return;
617 865 }
618 866
619 867 // Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
620 - if ( $post && 'shop_order' === $post->post_type ) {
868 + if ( 'shop_order' === $post->post_type ) {
621 869 $post = get_post( $post_ID );
622 870 }
623 871
624 - $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;
625 873
626 - $just_published = isset( $this->just_published[ $post_ID ] ) ? $this->just_published[ $post_ID ] : false;
874 + $just_published = $this->just_published[ $post_ID ] ?? false;
627 875
628 876 $state = array(
629 877 'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
630 878 'previous_status' => $previous_status,
@@ -655,14 +903,14 @@
655 903 * @param int $post_ID Post ID.
656 904 * @param \WP_Post $post Post object.
657 905 **/
658 906 public function wp_after_insert_post( $post_ID, $post ) {
659 - if ( ! is_numeric( $post_ID ) || $post === null ) {
907 + if ( ! is_numeric( $post_ID ) || ! $post instanceof \WP_Post ) {
660 908 return;
661 909 }
662 910
663 911 // Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
664 - if ( $post && 'shop_order' === $post->post_type ) {
912 + if ( 'shop_order' === $post->post_type ) {
665 913 $post = get_post( $post_ID );
666 914 }
667 915
668 916 $this->send_published( $post_ID, $post );
@@ -714,12 +962,8 @@
714 962
715 963 // Only Send Pulished Post event if post_type is not blacklisted.
716 964 if ( ! in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
717 965
718 - // Refreshing the post in the cache site before triggering the publish event.
719 - // The true parameter means that it's an update action, not create action.
720 - $this->wp_insert_post( $post_ID, $post, true );
721 -
722 966 /**
723 967 * Action that gets synced when a post type gets published.
724 968 *
725 969 * @since 1.6.3
@@ -726,10 +970,11 @@
726 970 * @since-jetpack 4.4.0
727 971 *
728 972 * @param int $post_ID
729 973 * @param mixed array $flags post flags that are added to the post
974 + * @param WP_Post $post The post object
730 975 */
731 - do_action( 'jetpack_published_post', $post_ID, $flags );
976 + do_action( 'jetpack_published_post', $post_ID, $flags, $post );
732 977 }
733 978 unset( $this->just_published[ $post_ID ] );
734 979
735 980 /**
@@ -736,8 +981,11 @@
736 981 * Send additional sync action for Activity Log when post is a Customizer publish
737 982 */
738 983 if ( 'customize_changeset' === $post->post_type ) {
739 984 $post_content = json_decode( $post->post_content, true );
985 + if ( ! is_iterable( $post_content ) ) {
986 + return;
987 + }
740 988 foreach ( $post_content as $key => $value ) {
741 989 // Skip if it isn't a widget.
742 990 if ( 'widget_' !== substr( $key, 0, strlen( 'widget_' ) ) ) {
743 991 continue;
@@ -751,9 +999,9 @@
751 999 if ( isset( $wp_registered_widgets[ $key ] ) ) {
752 1000 $widget_data = array(
753 1001 'name' => $wp_registered_widgets[ $key ]['name'],
754 1002 'id' => $key,
755 - 'title' => $value['value']['title'],
1003 + 'title' => $value['value']['title'] ?? '',
756 1004 );
757 1005 do_action( 'jetpack_widget_edited', $widget_data );
758 1006 }
759 1007 }
@@ -760,8 +1008,27 @@
760 1008 }
761 1009 }
762 1010
763 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 + /**
764 1031 * Add term relationships to post objects within a hook before they are serialized and sent to the server.
765 1032 * This is used in Full Sync Immediately
766 1033 *
767 1034 * @access public
@@ -767,17 +1034,18 @@
767 1034 * @access public
768 1035 *
769 1036 * @param array $args The hook parameters.
770 1037 * @return array $args The expanded hook parameters.
1038 + * @deprecated since 4.7.0
771 1039 */
772 1040 public function add_term_relationships( $args ) {
773 - list( $filtered_posts, $previous_interval_end ) = $args;
774 - list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $filtered_posts;
1041 + _deprecated_function( __METHOD__, '4.7.0' );
1042 + list( $filtered_posts, $previous_interval_end ) = $args;
775 1043
776 1044 return array(
777 - $filtered_posts,
778 - $filtered_posts_metadata,
779 - $this->get_term_relationships( $filtered_post_ids ),
1045 + $filtered_posts['objects'],
1046 + $filtered_posts['meta'],
1047 + $this->get_term_relationships( $filtered_posts['object_ids'] ),
780 1048 $previous_interval_end,
781 1049 );
782 1050 }
783 1051
@@ -836,17 +1104,35 @@
836 1104 if ( empty( $post_ids ) ) {
837 1105 return array();
838 1106 }
839 1107
840 - $posts = $this->expand_posts( $post_ids );
841 - $posts_metadata = $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
1108 + $posts = $this->expand_posts( $post_ids );
842 1109
843 - // Filter posts and metadata based on maximum size constraints.
844 - list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $this->filter_posts_and_metadata_max_size( $posts, $posts_metadata );
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 +
845 1131 return array(
846 - $filtered_post_ids,
847 - $filtered_posts,
848 - $filtered_posts_metadata,
1132 + 'object_ids' => $filtered_post_ids,
1133 + 'objects' => $filtered_posts,
1134 + 'meta' => $filtered_posts_metadata,
849 1135 );
850 1136 }
851 1137
852 1138 /**
@@ -860,73 +1146,6 @@
860 1146 $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
861 1147 $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
862 1148 $posts = array_values( $posts ); // Reindex in case posts were deleted.
863 1149 return $posts;
864 - }
865 -
866 - /**
867 - * Filters posts and metadata based on maximum size constraints.
868 - * It always allows the first post with its metadata even if they exceed the limit, otherwise they will never be synced.
869 - *
870 - * @access public
871 - *
872 - * @param array $posts The array of posts to filter.
873 - * @param array $metadata The array of metadata to filter.
874 - * @return array An array containing the filtered post IDs, filtered posts, and filtered metadata.
875 - */
876 - public function filter_posts_and_metadata_max_size( $posts, $metadata ) {
877 - $filtered_posts = array();
878 - $filtered_metadata = array();
879 - $filtered_post_ids = array();
880 - $current_size = 0;
881 - foreach ( $posts as $post ) {
882 - $post_content_size = isset( $post->post_content ) ? strlen( $post->post_content ) : 0;
883 - $current_metadata = array();
884 - $metadata_size = 0;
885 - foreach ( $metadata as $key => $metadata_item ) {
886 - if ( (int) $metadata_item->post_id === $post->ID ) {
887 - // Trimming metadata if it exceeds limit. Similar to trim_post_meta.
888 - $metadata_item_size = strlen( maybe_serialize( $metadata_item->meta_value ) );
889 - if ( $metadata_item_size >= self::MAX_POST_META_LENGTH ) {
890 - $metadata_item->meta_value = '';
891 - }
892 - $current_metadata[] = $metadata_item;
893 - $metadata_size += $metadata_item_size >= self::MAX_POST_META_LENGTH ? 0 : $metadata_item_size;
894 - if ( ! empty( $filtered_post_ids ) && ( $current_size + $post_content_size + $metadata_size ) > ( self::MAX_SIZE_FULL_SYNC ) ) {
895 - break 2; // Break both foreach loops.
896 - }
897 - unset( $metadata[ $key ] );
898 - }
899 - }
900 - // Always allow the first post with its metadata.
901 - if ( empty( $filtered_post_ids ) || ( $current_size + $post_content_size + $metadata_size ) <= ( self::MAX_SIZE_FULL_SYNC ) ) {
902 - $filtered_post_ids[] = strval( $post->ID );
903 - $filtered_posts[] = $post;
904 - $filtered_metadata = array_merge( $filtered_metadata, $current_metadata );
905 - $current_size += $post_content_size + $metadata_size;
906 - } else {
907 - break;
908 - }
909 - }
910 - return array(
911 - $filtered_post_ids,
912 - $filtered_posts,
913 - $filtered_metadata,
914 - );
915 - }
916 -
917 - /**
918 - * Set the status of the full sync action based on the objects that were sent.
919 - *
920 - * @access public
921 - *
922 - * @param array $status This module Full Sync status.
923 - * @param array $objects This module Full Sync objects.
924 - *
925 - * @return array The updated status.
926 - */
927 - public function set_send_full_sync_actions_status( $status, $objects ) {
928 - $status['last_sent'] = end( $objects[0] );
929 - $status['sent'] += count( $objects[0] );
930 - return $status;
931 1150 }
932 1151 }