PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
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 13.9.2 14.0.1 All 504 releases
← All changes | jetpack_vendor/automattic/jetpack-sync/src/modules/class-posts.php +441 -66 12.2.3 → 16.3-a.1 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,26 +153,33 @@
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 - add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_post', array( $this, 'filter_blacklisted_post_types' ) );
148 167
149 168 // Listen for meta changes.
150 169 $this->init_listeners_for_meta_type( 'post', $callable );
151 - $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' ) );
152 173
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' ) );
176 +
153 177 add_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
154 178 add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
155 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 );
156 182 }
157 183
158 184 /**
159 185 * Before Akismet's daily cleanup of spam detection metadata.
@@ -213,17 +239,19 @@
213 239 *
214 240 * @access public
215 241 */
216 242 public function init_before_send() {
217 - add_filter( 'jetpack_sync_before_send_jetpack_sync_save_post', array( $this, 'expand_jetpack_sync_save_post' ) );
218 -
219 243 // meta.
220 - add_filter( 'jetpack_sync_before_send_added_post_meta', array( $this, 'trim_post_meta' ) );
221 - 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.
222 246 add_filter( 'jetpack_sync_before_send_deleted_post_meta', array( $this, 'trim_post_meta' ) );
223 -
224 247 // Full sync.
225 - 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 + }
226 254 }
227 255
228 256 /**
229 257 * Enqueue the posts actions for full sync.
@@ -248,16 +276,16 @@
248 276 *
249 277 * @todo Use $wpdb->prepare for the SQL query.
250 278 *
251 279 * @param array $config Full sync configuration for this sync module.
252 - * @return array Number of items yet to be enqueued.
280 + * @return int Number of items yet to be enqueued.
253 281 */
254 282 public function estimate_full_sync_actions( $config ) {
255 283 global $wpdb;
256 284
257 285 $query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config );
258 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
259 - $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 );
260 288
261 289 return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
262 290 }
263 291
@@ -272,9 +300,9 @@
272 300 public function get_where_sql( $config ) {
273 301 $where_sql = Settings::get_blacklisted_post_types_sql();
274 302
275 303 // Config is a list of post IDs to sync.
276 - if ( is_array( $config ) ) {
304 + if ( is_array( $config ) && ! empty( $config ) ) {
277 305 $where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
278 306 }
279 307
280 308 return $where_sql;
@@ -291,9 +319,9 @@
291 319 return array( 'jetpack_full_sync_posts' );
292 320 }
293 321
294 322 /**
295 - * 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.
296 324 *
297 325 * @param array $args action arguments.
298 326 *
299 327 * @return array filtered action arguments.
@@ -302,9 +330,9 @@
302 330 list( $meta_id, $object_id, $meta_key, $meta_value ) = $args;
303 331 // Explicitly truncate meta_value when it exceeds limit.
304 332 // Large content will cause OOM issues and break Sync.
305 333 $serialized_value = maybe_serialize( $meta_value );
306 - if ( strlen( $serialized_value ) >= self::MAX_POST_META_LENGTH ) {
334 + if ( $serialized_value === null || strlen( $serialized_value ) >= self::MAX_META_LENGTH ) {
307 335 $meta_value = '';
308 336 }
309 337 return array( $meta_id, $object_id, $meta_key, $meta_value );
310 338 }
@@ -309,8 +337,112 @@
309 337 return array( $meta_id, $object_id, $meta_key, $meta_value );
310 338 }
311 339
312 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 + /**
313 445 * Process content before send.
314 446 *
315 447 * @param array $args Arguments of the `wp_insert_post` hook.
316 448 *
@@ -321,8 +453,67 @@
321 453 return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
322 454 }
323 455
324 456 /**
457 + * Filter all blacklisted post types and add filtered post content.
458 + *
459 + * @param array $args Hook arguments.
460 + * @return array|false Hook arguments, or false if the post type is a blacklisted one.
461 + */
462 + public function filter_jetpack_sync_before_enqueue_jetpack_sync_save_post( $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 + }
470 +
471 + list( $post_id, $post, $update, $previous_state ) = array_pad( $args, 4, null );
472 +
473 + if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
474 + return false;
475 + }
476 +
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 );
488 + }
489 +
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 + /**
325 516 * Filter all blacklisted post types.
326 517 *
327 518 * @param array $args Hook arguments.
328 519 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
@@ -327,9 +518,11 @@
327 518 * @param array $args Hook arguments.
328 519 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
329 520 */
330 521 public function filter_blacklisted_post_types_deleted( $args ) {
331 -
522 + if ( ! is_array( $args ) || ! array_key_exists( 0, $args ) || ! is_numeric( $args[0] ) ) {
523 + return false;
524 + }
332 525 // deleted_post is called after the SQL delete but before cache cleanup.
333 526 // There is the potential we can't detect post_type at this point.
334 527 if ( ! $this->is_post_type_allowed( $args[0] ) ) {
335 528 return false;
@@ -338,38 +531,86 @@
338 531 return $args;
339 532 }
340 533
341 534 /**
342 - * Filter all blacklisted post types.
535 + * Filter all meta that is not blacklisted, or is stored for a disallowed post type.
343 536 *
344 - * @param array $args Hook arguments.
345 - * @return array|false Hook arguments, or false if the post type is a blacklisted one.
537 + * @param array|false $args Hook arguments.
538 + * @return array|false Hook arguments, or false if meta was filtered.
346 539 */
347 - public function filter_blacklisted_post_types( $args ) {
348 - $post = $args[1];
540 + public function filter_meta( $args ) {
541 + if ( ! $this->has_valid_meta_args( $args ) || ! is_numeric( $args[1] ) ) {
542 + return false;
543 + }
349 544
350 - if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
545 + return $this->is_allowed_post_meta( $args[1], $args[2] ) ? $args : false;
546 + }
547 +
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 ) {
351 558 return false;
352 559 }
353 560
354 - return $args;
561 + return $this->filter_meta( $args );
355 562 }
356 563
357 564 /**
358 - * Filter all meta that is not blacklisted, or is stored for a disallowed post type.
565 + * Filter deleted post meta that is not whitelisted, or is stored for a disallowed post type.
359 566 *
360 - * @param array $args Hook arguments.
567 + * @param array|false $args Hook arguments.
361 568 * @return array|false Hook arguments, or false if meta was filtered.
362 569 */
363 - public function filter_meta( $args ) {
364 - if ( $this->is_post_type_allowed( $args[1] ) && $this->is_whitelisted_post_meta( $args[2] ) ) {
365 - return $args;
570 + public function filter_deleted_post_meta( $args ) {
571 + if ( ! $this->has_valid_meta_args( $args ) ) {
572 + return false;
366 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 + }
367 579
368 - return false;
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 );
369 589 }
370 590
371 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 + /**
372 613 * Whether a post meta key is whitelisted.
373 614 *
374 615 * @param string $meta_key Meta key.
375 616 * @return boolean Whether the post meta key is whitelisted.
@@ -374,10 +615,13 @@
374 615 * @param string $meta_key Meta key.
375 616 * @return boolean Whether the post meta key is whitelisted.
376 617 */
377 618 public function is_whitelisted_post_meta( $meta_key ) {
378 - // The _wpas_skip_ meta key is used by Publicize.
379 - 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 );
380 624 }
381 625
382 626 /**
383 627 * Whether a post type is allowed.
@@ -429,8 +673,12 @@
429 673 * @param \WP_Post $post_object Post object.
430 674 */
431 675 public function filter_post_content_and_add_links( $post_object ) {
432 676 global $post;
677 +
678 + // Used to restore the post global.
679 + $current_post = $post;
680 +
433 681 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
434 682 $post = $post_object;
435 683
436 684 // Return non existant post.
@@ -441,8 +689,11 @@
441 689 $non_existant_post->post_modified = $post->post_modified;
442 690 $non_existant_post->post_modified_gmt = $post->post_modified_gmt;
443 691 $non_existant_post->post_status = 'jetpack_sync_non_registered_post_type';
444 692 $non_existant_post->post_type = $post->post_type;
693 + // Restore global post.
694 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
695 + $post = $current_post;
445 696
446 697 return $non_existant_post;
447 698 }
448 699 /**
@@ -468,8 +719,12 @@
468 719 $blocked_post->post_modified_gmt = $post->post_modified_gmt;
469 720 $blocked_post->post_status = 'jetpack_sync_blocked';
470 721 $blocked_post->post_type = $post->post_type;
471 722
723 + // Restore global post.
724 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
725 + $post = $current_post;
726 +
472 727 return $blocked_post;
473 728 }
474 729
475 730 // lets not do oembed just yet.
@@ -513,11 +768,20 @@
513 768 }
514 769 }
515 770
516 771 array_map( 'remove_shortcode', array_keys( $removed_shortcode_callbacks ) );
772 + /**
773 + * Certain modules such as Likes, Related Posts and Sharedaddy are using `Settings::is_syncing`
774 + * in order to NOT get rendered in filtered post content.
775 + * Since the current method runs now before enqueueing instead of before sending,
776 + * we are setting `is_syncing` flag to true in order to preserve the existing functionality.
777 + */
517 778
779 + $is_syncing_current = Settings::is_syncing();
780 + Settings::set_is_syncing( true );
518 781 $post->post_content_filtered = apply_filters( 'the_content', $post->post_content );
519 782 $post->post_excerpt_filtered = apply_filters( 'the_excerpt', $post->post_excerpt );
783 + Settings::set_is_syncing( $is_syncing_current );
520 784
521 785 foreach ( $removed_shortcode_callbacks as $shortcode => $callback ) {
522 786 add_shortcode( $shortcode, $callback );
523 787 }
@@ -538,9 +802,15 @@
538 802 if ( function_exists( 'amp_get_permalink' ) ) {
539 803 $post->amp_permalink = amp_get_permalink( $post->ID );
540 804 }
541 805
542 - 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;
543 813 }
544 814
545 815 /**
546 816 * Handle transition from another post status to a published one.
@@ -549,8 +819,11 @@
549 819 * @param string $old_status Old post status.
550 820 * @param \WP_Post $post Post object.
551 821 */
552 822 public function save_published( $new_status, $old_status, $post ) {
823 + if ( ! $post instanceof \WP_Post ) {
824 + return;
825 + }
553 826 if ( 'publish' === $new_status && 'publish' !== $old_status ) {
554 827 $this->just_published[ $post->ID ] = true;
555 828 }
556 829
@@ -564,14 +837,12 @@
564 837 *
565 838 * The 2nd request is to update post meta, which is not supported on WP REST API.
566 839 * When syncing post data, we will include if this was a meta box update.
567 840 *
568 - * @todo Implement nonce verification.
569 - *
570 841 * @return boolean Whether this is a Gutenberg meta box update.
571 842 */
572 - public function is_gutenberg_meta_box_update() {
573 - // 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.
574 845 return (
575 846 isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
576 847 'editpost' === $_POST['action'] &&
577 848 '1' === $_GET['classic-editor'] &&
@@ -588,20 +859,20 @@
588 859 * @param \WP_Post $post Post object.
589 860 * @param boolean $update Whether this is an existing post being updated or not.
590 861 */
591 862 public function wp_insert_post( $post_ID, $post = null, $update = null ) {
592 - if ( ! is_numeric( $post_ID ) || $post === null ) {
863 + if ( ! is_numeric( $post_ID ) || ! $post instanceof \WP_Post ) {
593 864 return;
594 865 }
595 866
596 867 // Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
597 - if ( $post && 'shop_order' === $post->post_type ) {
868 + if ( 'shop_order' === $post->post_type ) {
598 869 $post = get_post( $post_ID );
599 870 }
600 871
601 - $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;
602 873
603 - $just_published = isset( $this->just_published[ $post_ID ] ) ? $this->just_published[ $post_ID ] : false;
874 + $just_published = $this->just_published[ $post_ID ] ?? false;
604 875
605 876 $state = array(
606 877 'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
607 878 'previous_status' => $previous_status,
@@ -632,14 +903,14 @@
632 903 * @param int $post_ID Post ID.
633 904 * @param \WP_Post $post Post object.
634 905 **/
635 906 public function wp_after_insert_post( $post_ID, $post ) {
636 - if ( ! is_numeric( $post_ID ) || $post === null ) {
907 + if ( ! is_numeric( $post_ID ) || ! $post instanceof \WP_Post ) {
637 908 return;
638 909 }
639 910
640 911 // Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
641 - if ( $post && 'shop_order' === $post->post_type ) {
912 + if ( 'shop_order' === $post->post_type ) {
642 913 $post = get_post( $post_ID );
643 914 }
644 915
645 916 $this->send_published( $post_ID, $post );
@@ -691,12 +962,8 @@
691 962
692 963 // Only Send Pulished Post event if post_type is not blacklisted.
693 964 if ( ! in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
694 965
695 - // Refreshing the post in the cache site before triggering the publish event.
696 - // The true parameter means that it's an update action, not create action.
697 - $this->wp_insert_post( $post_ID, $post, true );
698 -
699 966 /**
700 967 * Action that gets synced when a post type gets published.
701 968 *
702 969 * @since 1.6.3
@@ -703,10 +970,11 @@
703 970 * @since-jetpack 4.4.0
704 971 *
705 972 * @param int $post_ID
706 973 * @param mixed array $flags post flags that are added to the post
974 + * @param WP_Post $post The post object
707 975 */
708 - do_action( 'jetpack_published_post', $post_ID, $flags );
976 + do_action( 'jetpack_published_post', $post_ID, $flags, $post );
709 977 }
710 978 unset( $this->just_published[ $post_ID ] );
711 979
712 980 /**
@@ -713,8 +981,11 @@
713 981 * Send additional sync action for Activity Log when post is a Customizer publish
714 982 */
715 983 if ( 'customize_changeset' === $post->post_type ) {
716 984 $post_content = json_decode( $post->post_content, true );
985 + if ( ! is_iterable( $post_content ) ) {
986 + return;
987 + }
717 988 foreach ( $post_content as $key => $value ) {
718 989 // Skip if it isn't a widget.
719 990 if ( 'widget_' !== substr( $key, 0, strlen( 'widget_' ) ) ) {
720 991 continue;
@@ -728,9 +999,9 @@
728 999 if ( isset( $wp_registered_widgets[ $key ] ) ) {
729 1000 $widget_data = array(
730 1001 'name' => $wp_registered_widgets[ $key ]['name'],
731 1002 'id' => $key,
732 - 'title' => $value['value']['title'],
1003 + 'title' => $value['value']['title'] ?? '',
733 1004 );
734 1005 do_action( 'jetpack_widget_edited', $widget_data );
735 1006 }
736 1007 }
@@ -737,9 +1008,51 @@
737 1008 }
738 1009 }
739 1010
740 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 + /**
741 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
742 1055 *
743 1056 * @access public
744 1057 *
745 1058 * @param array $args The hook parameters.
@@ -744,19 +1057,19 @@
744 1057 *
745 1058 * @param array $args The hook parameters.
746 1059 * @return array $args The expanded hook parameters.
747 1060 */
748 - public function expand_post_ids( $args ) {
749 - 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;
750 1063
751 - $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
752 - $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
753 - $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 );
754 1067
755 1068 return array(
756 1069 $posts,
757 - $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) ),
758 - $this->get_term_relationships( $post_ids ),
1070 + $posts_metadata,
1071 + $term_relationships,
759 1072 $previous_interval_end,
760 1073 );
761 1074 }
762 1075
@@ -771,6 +1084,68 @@
771 1084 * @return array|bool An array of min and max ids for each batch. FALSE if no table can be found.
772 1085 */
773 1086 public function get_min_max_object_ids_for_batches( $batch_size, $where_sql = false ) {
774 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;
775 1150 }
776 1151 }