| @@ -377,8 +377,16 @@ | ||
| 377 | 377 | } |
| 378 | 378 | |
| 379 | 379 | // Initialize Global SEO Schema Output and store reference |
| 380 | 380 | $this->global_seo_schema = new Global_SEO_Schema_Output(); |
| 381 | + // Let schema reuse the description this class already resolves, so the | |
| 382 | + // JSON-LD and the meta/og/twitter tags cannot disagree about what the | |
| 383 | + // page is (#766). Passed as a callback rather than a value: schema is | |
| 384 | + // built during wp_head, by which point the request context this | |
| 385 | + // resolution depends on is set, and it must not be captured earlier. | |
| 386 | + $this->global_seo_schema->set_description_resolver( | |
| 387 | + fn (): string => (string) $this->get_meta_description() | |
| 388 | + ); | |
| 381 | 389 | $this->global_seo_schema->init(); |
| 382 | 390 | } |
| 383 | 391 | |
| 384 | 392 | /** |
| @@ -2954,10 +2962,22 @@ | ||
| 2954 | 2962 | $context_type, |
| 2955 | 2963 | $context_id |
| 2956 | 2964 | ); |
| 2957 | 2965 | |
| 2966 | + // A deployed node is a snapshot from Deploy time and outranks | |
| 2967 | + // the automatic node, so page and article types would publish | |
| 2968 | + // a frozen excerpt instead of the description the head | |
| 2969 | + // resolves. Give them the live one, as the automatic node has. | |
| 2970 | + $context_post = get_post($context_id); | |
| 2971 | + | |
| 2958 | 2972 | foreach ($page_specific_schemas as $schema_type => $schema_info) { |
| 2959 | - Schema_Graph::instance()->add_primary($schema_info['data'], (string) $schema_type, 'schema_manager'); | |
| 2973 | + $node = $schema_info['data']; | |
| 2974 | + | |
| 2975 | + if ($this->global_seo_schema && $context_post instanceof \WP_Post) { | |
| 2976 | + $node = $this->global_seo_schema->refresh_deployed_description($node, (string) $schema_type, $context_post); | |
| 2977 | + } | |
| 2978 | + | |
| 2979 | + Schema_Graph::instance()->add_primary($node, (string) $schema_type, 'schema_manager'); | |
| 2960 | 2980 | } |
| 2961 | 2981 | $has_schema_manager_output = true; |
| 2962 | 2982 | } |
| 2963 | 2983 | } |
| @@ -3015,8 +3035,12 @@ | ||
| 3015 | 3035 | 'url' => home_url('/'), |
| 3016 | 3036 | ]; |
| 3017 | 3037 | |
| 3018 | 3038 | $description = !empty($settings['site_description']) ? $settings['site_description'] : get_bloginfo('description'); |
| 3039 | + // The tagline is stored esc_html()'d by sanitize_option(), so a site | |
| 3040 | + // called "Fish & Chips" published `&` literally in its WebSite | |
| 3041 | + // node; nothing decodes JSON-LD downstream. | |
| 3042 | + $description = \ThinkRank\Core\Seo_Text::normalize_schema_text((string) $description); | |
| 3019 | 3043 | if (!empty($description)) { |
| 3020 | 3044 | $schema['description'] = $description; |
| 3021 | 3045 | } |
| 3022 | 3046 | |