| @@ -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 | /** |
| @@ -1791,12 +1799,16 @@ | ||
| 1791 | 1799 | // Get image dimensions and alt text |
| 1792 | 1800 | $image_id = get_post_thumbnail_id($this->current_post_id); |
| 1793 | 1801 | $image_meta = wp_get_attachment_metadata($image_id); |
| 1794 | 1802 | if ($image_meta) { |
| 1803 | + // The `large` file being published, not the original it | |
| 1804 | + // was generated from: the metadata's own width and height | |
| 1805 | + // describe an image this tag does not point at (#847). | |
| 1806 | + $image_file = \ThinkRank\SEO\Attachment_Lookup::describe((int) $image_id, (string) $image_url); | |
| 1795 | 1807 | // SVGs (and other vector uploads) report 0x0 — emitting |
| 1796 | 1808 | // those as og:image dimensions is invalid, so skip them. |
| 1797 | - $og_width = isset($image_meta['width']) ? (int) $image_meta['width'] : 0; | |
| 1798 | - $og_height = isset($image_meta['height']) ? (int) $image_meta['height'] : 0; | |
| 1809 | + $og_width = $image_file['width']; | |
| 1810 | + $og_height = $image_file['height']; | |
| 1799 | 1811 | if ($og_width > 0 && $og_height > 0) { |
| 1800 | 1812 | echo "<meta property=\"og:image:width\" content=\"" . esc_attr($og_width) . "\" />\n"; |
| 1801 | 1813 | echo "<meta property=\"og:image:height\" content=\"" . esc_attr($og_height) . "\" />\n"; |
| 1802 | 1814 | } |
| @@ -1801,9 +1813,9 @@ | ||
| 1801 | 1813 | echo "<meta property=\"og:image:height\" content=\"" . esc_attr($og_height) . "\" />\n"; |
| 1802 | 1814 | } |
| 1803 | 1815 | // Derive the real mime type instead of hardcoding image/jpeg, |
| 1804 | 1816 | // which mislabels PNG/WebP featured images. |
| 1805 | - $image_mime = get_post_mime_type($image_id); | |
| 1817 | + $image_mime = $image_file['type']; | |
| 1806 | 1818 | if ($image_mime) { |
| 1807 | 1819 | echo "<meta property=\"og:image:type\" content=\"" . esc_attr($image_mime) . "\" />\n"; |
| 1808 | 1820 | } |
| 1809 | 1821 | } |
| @@ -2954,10 +2966,22 @@ | ||
| 2954 | 2966 | $context_type, |
| 2955 | 2967 | $context_id |
| 2956 | 2968 | ); |
| 2957 | 2969 | |
| 2970 | + // A deployed node is a snapshot from Deploy time and outranks | |
| 2971 | + // the automatic node, so page and article types would publish | |
| 2972 | + // a frozen excerpt instead of the description the head | |
| 2973 | + // resolves. Give them the live one, as the automatic node has. | |
| 2974 | + $context_post = get_post($context_id); | |
| 2975 | + | |
| 2958 | 2976 | foreach ($page_specific_schemas as $schema_type => $schema_info) { |
| 2959 | - Schema_Graph::instance()->add_primary($schema_info['data'], (string) $schema_type, 'schema_manager'); | |
| 2977 | + $node = $schema_info['data']; | |
| 2978 | + | |
| 2979 | + if ($this->global_seo_schema && $context_post instanceof \WP_Post) { | |
| 2980 | + $node = $this->global_seo_schema->refresh_deployed_description($node, (string) $schema_type, $context_post); | |
| 2981 | + } | |
| 2982 | + | |
| 2983 | + Schema_Graph::instance()->add_primary($node, (string) $schema_type, 'schema_manager'); | |
| 2960 | 2984 | } |
| 2961 | 2985 | $has_schema_manager_output = true; |
| 2962 | 2986 | } |
| 2963 | 2987 | } |
| @@ -3015,8 +3039,12 @@ | ||
| 3015 | 3039 | 'url' => home_url('/'), |
| 3016 | 3040 | ]; |
| 3017 | 3041 | |
| 3018 | 3042 | $description = !empty($settings['site_description']) ? $settings['site_description'] : get_bloginfo('description'); |
| 3043 | + // The tagline is stored esc_html()'d by sanitize_option(), so a site | |
| 3044 | + // called "Fish & Chips" published `&` literally in its WebSite | |
| 3045 | + // node; nothing decodes JSON-LD downstream. | |
| 3046 | + $description = \ThinkRank\Core\Seo_Text::normalize_schema_text((string) $description); | |
| 3019 | 3047 | if (!empty($description)) { |
| 3020 | 3048 | $schema['description'] = $description; |
| 3021 | 3049 | } |
| 3022 | 3050 | |