wordpress-seo
/
src
/
schema-aggregator
/
infrastructure
/
schema-pieces
/
wordpress-global-state-adapter.php
wordpress-global-state-adapter.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI trunk, at src/schema-aggregator/infrastructure/schema-pieces/wordpress-global-state-adapter.php
| 1 | <?php |
| 2 | // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. |
| 3 | namespace Yoast\WP\SEO\Schema_Aggregator\Infrastructure\Schema_Pieces; |
| 4 | |
| 5 | use WP_Post; |
| 6 | use Yoast\WP\SEO\Context\Meta_Tags_Context; |
| 7 | use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; |
| 8 | use Yoast\WP\SEO\Models\Indexable; |
| 9 | /** |
| 10 | * Helper class to set and reset WordPress global state. |
| 11 | */ |
| 12 | class WordPress_Global_State_Adapter { |
| 13 | |
| 14 | /** |
| 15 | * The meta tags context memoizer. |
| 16 | * |
| 17 | * @var Meta_Tags_Context_Memoizer |
| 18 | */ |
| 19 | private $memoizer; |
| 20 | |
| 21 | /** |
| 22 | * Previous global $post |
| 23 | * |
| 24 | * @var WP_Post|null |
| 25 | */ |
| 26 | private $previous_post; |
| 27 | |
| 28 | /** |
| 29 | * Previous global $wp_query->queried_object |
| 30 | * |
| 31 | * @var WP_Post|null |
| 32 | */ |
| 33 | private $previous_queried_object; |
| 34 | |
| 35 | /** |
| 36 | * Previous global $wp_query->queried_object_id |
| 37 | * |
| 38 | * @var int|string|null |
| 39 | */ |
| 40 | private $previous_queried_object_id; |
| 41 | |
| 42 | /** |
| 43 | * Previous query flags |
| 44 | * |
| 45 | * @var array<string, bool> |
| 46 | */ |
| 47 | private $previous_query_flags; |
| 48 | |
| 49 | /** |
| 50 | * Constructor. |
| 51 | * |
| 52 | * @param Meta_Tags_Context_Memoizer $memoizer The meta tags context memoizer. |
| 53 | */ |
| 54 | public function __construct( Meta_Tags_Context_Memoizer $memoizer ) { |
| 55 | $this->memoizer = $memoizer; |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * Set WordPress global state |
| 60 | * |
| 61 | * Helper method to set $post and $wp_query globals based on the given indexable, and |
| 62 | * prime the memoizer's current_page slot with the indexable's context so external schema |
| 63 | * generators (e.g. WPSEO_WooCommerce_Schema) read the correct per-indexable values. |
| 64 | * This is critical to ensure that schema pieces relying on global state function correctly. |
| 65 | * |
| 66 | * @param Indexable $indexable The indexable to set the global state for. |
| 67 | * @param Meta_Tags_Context $context The indexable's context, installed as the current_page slot. |
| 68 | * |
| 69 | * @return void |
| 70 | */ |
| 71 | public function set_global_state( Indexable $indexable, Meta_Tags_Context $context ): void { |
| 72 | global $post, $wp_query; |
| 73 | $this->previous_post = $post; |
| 74 | $this->previous_queried_object = ( $wp_query->queried_object ?? null ); |
| 75 | $this->previous_queried_object_id = ( $wp_query->queried_object_id ?? null ); |
| 76 | |
| 77 | $this->previous_query_flags = [ |
| 78 | 'is_single' => ( $wp_query->is_single ?? false ), |
| 79 | 'is_page' => ( $wp_query->is_page ?? false ), |
| 80 | 'is_singular' => ( $wp_query->is_singular ?? false ), |
| 81 | ]; |
| 82 | |
| 83 | // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. |
| 84 | $post = \get_post( $indexable->object_id ); |
| 85 | $wp_query->queried_object = \get_post( $indexable->object_id ); |
| 86 | $wp_query->queried_object_id = $indexable->object_id; |
| 87 | |
| 88 | $wp_query->is_single = false; |
| 89 | $wp_query->is_page = false; |
| 90 | $wp_query->is_singular = true; |
| 91 | |
| 92 | if ( $indexable->object_sub_type === 'page' ) { |
| 93 | $wp_query->is_page = true; |
| 94 | } |
| 95 | else { |
| 96 | $wp_query->is_single = true; |
| 97 | |
| 98 | } |
| 99 | |
| 100 | \setup_postdata( $post ); |
| 101 | |
| 102 | // Make for_current_page() resolve to the indexable being processed, so external schema |
| 103 | // generators (e.g. WPSEO_WooCommerce_Schema) read the correct per-indexable canonical / |
| 104 | // main_schema_id. reset_global_state() clears this slot at the end of the iteration. |
| 105 | $this->memoizer->set_for_current_page( $context ); |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Restore WordPress global state |
| 110 | * |
| 111 | * Helper method to restore $post and $wp_query globals after schema collection. |
| 112 | * This is critical to prevent side effects that could corrupt WordPress's global context. |
| 113 | * |
| 114 | * @return void |
| 115 | */ |
| 116 | public function reset_global_state(): void { |
| 117 | global $post, $wp_query; |
| 118 | |
| 119 | \wp_reset_postdata(); |
| 120 | |
| 121 | // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To reset the post we need to do this explicitly. |
| 122 | $post = $this->previous_post; |
| 123 | |
| 124 | if ( isset( $wp_query ) && \is_object( $wp_query ) ) { |
| 125 | $wp_query->queried_object = $this->previous_queried_object; |
| 126 | $wp_query->queried_object_id = $this->previous_queried_object_id; |
| 127 | $wp_query->is_single = $this->previous_query_flags['is_single']; |
| 128 | $wp_query->is_page = $this->previous_query_flags['is_page']; |
| 129 | $wp_query->is_singular = $this->previous_query_flags['is_singular']; |
| 130 | } |
| 131 | |
| 132 | // Drop the per-iteration current_page context primed by Schema_Piece_Repository::get(), |
| 133 | // so the next iteration re-resolves cleanly. |
| 134 | $this->memoizer->clear_for_current_page(); |
| 135 | } |
| 136 | } |
| 137 |