| @@ -735,8 +735,19 @@ | ||
| 735 | 735 | * runs after the form prints its hidden `post_ID` and before the title and |
| 736 | 736 | * editor, which then showed the related post, and Update saved it over the |
| 737 | 737 | * original (#860). |
| 738 | 738 | * |
| 739 | + * Secondary queries also run with front-end statuses. In wp-admin core | |
| 740 | + * marks every `WP_Query` as an admin query and, when no `post_status` is | |
| 741 | + * set, adds the statuses the admin post list shows, draft among them, so | |
| 742 | + * a related-posts shortcode listed drafts the front end never shows and | |
| 743 | + * the editor-load analysis disagreed with REST and the page (#902). | |
| 744 | + * | |
| 745 | + * The main query points at the post too. Restoring the globals afterwards | |
| 746 | + * (#860) did not reach between shortcodes: a related-posts loop's own | |
| 747 | + * `wp_reset_postdata()` still found no post on the main query, so every | |
| 748 | + * later shortcode in the same render saw the last looped post (#903). | |
| 749 | + * | |
| 739 | 750 | * @param string $raw Raw post content. |
| 740 | 751 | * @param \WP_Post $post Post the content belongs to. |
| 741 | 752 | * @return string Rendered content. |
| 742 | 753 | */ |
| @@ -747,11 +758,29 @@ | ||
| 747 | 758 | |
| 748 | 759 | $content = $raw; |
| 749 | 760 | $previous = self::snapshot_post_globals(); |
| 750 | 761 | |
| 762 | + // After pre_get_posts core reads `is_admin` only to add the admin | |
| 763 | + // list's statuses when none were asked for, so queries that set | |
| 764 | + // `post_status`, and the main query, are untouched. | |
| 765 | + $front_end_statuses = static function ($query): void { | |
| 766 | + if ($query instanceof \WP_Query && !$query->is_main_query()) { | |
| 767 | + $query->is_admin = false; | |
| 768 | + } | |
| 769 | + }; | |
| 770 | + | |
| 771 | + // `wp_reset_postdata()` returns to the main query's post, which admin | |
| 772 | + // and REST requests do not have. Restored in finally, null included. | |
| 773 | + $main_query = (isset($GLOBALS['wp_query']) && $GLOBALS['wp_query'] instanceof \WP_Query) ? $GLOBALS['wp_query'] : null; | |
| 774 | + $main_query_post = $main_query ? $main_query->post : null; | |
| 775 | + | |
| 751 | 776 | try { |
| 752 | 777 | // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Made current for the render, restored in finally. |
| 753 | 778 | $GLOBALS['post'] = $post; |
| 779 | + add_action('pre_get_posts', $front_end_statuses, PHP_INT_MIN); | |
| 780 | + if ($main_query) { | |
| 781 | + $main_query->post = $post; | |
| 782 | + } | |
| 754 | 783 | |
| 755 | 784 | // Fires `the_post`, which these paths never fired before: admin, |
| 756 | 785 | // REST and cron analysis had no current post at all. That is the |
| 757 | 786 | // same signal the front-end loop sends and it is what makes |
| @@ -774,8 +803,12 @@ | ||
| 774 | 803 | } |
| 775 | 804 | } catch (\Throwable $e) { |
| 776 | 805 | return $raw; |
| 777 | 806 | } finally { |
| 807 | + if ($main_query) { | |
| 808 | + $main_query->post = $main_query_post; | |
| 809 | + } | |
| 810 | + remove_action('pre_get_posts', $front_end_statuses, PHP_INT_MIN); | |
| 778 | 811 | self::restore_post_globals($previous); |
| 779 | 812 | } |
| 780 | 813 | |
| 781 | 814 | return self::is_blank($content) ? $raw : $content; |