← All changes
|
includes/frontend/class-global-seo-schema-output.php
+86
-9
2.1.0
→
2.9.0
View file →
| @@ -104,8 +104,22 @@ | ||
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | $settings = $this->get_global_seo_settings($post_type); |
| 107 | 107 | if (($settings['schema_type'] ?? '') === 'Product') { |
| 108 | + // ...but only if this class is actually going to emit it. The | |
| 109 | + // per-content-type Schema switch (#660) makes | |
| 110 | + // output_global_seo_schema() return before it builds anything, so | |
| 111 | + // claiming the entity here as well left the page with NO product | |
| 112 | + // structured data at all — strictly worse than the duplicate this | |
| 113 | + // method exists to prevent, and the opposite of what the docblock | |
| 114 | + // above promises for "ThinkRank's product schema disabled". | |
| 115 | + if (!\ThinkRank\SEO\Content_Type_Settings::is_enabled_for_current( | |
| 116 | + \ThinkRank\SEO\Content_Type_Settings::FEATURE_SCHEMA, | |
| 117 | + true | |
| 118 | + )) { | |
| 119 | + return $markup; | |
| 120 | + } | |
| 121 | + | |
| 108 | 122 | return []; |
| 109 | 123 | } |
| 110 | 124 | |
| 111 | 125 | // A per-post DEPLOYED Product schema duplicates WooCommerce's markup |
| @@ -153,8 +167,17 @@ | ||
| 153 | 167 | * @since 1.0.0 |
| 154 | 168 | * @return void |
| 155 | 169 | */ |
| 156 | 170 | public function output_global_seo_schema(): void { |
| 171 | + // Per-content-type schema switch. 'inherit' (the default) keeps schema | |
| 172 | + // on, exactly as before the matrix existed (#660). | |
| 173 | + if (!\ThinkRank\SEO\Content_Type_Settings::is_enabled_for_current( | |
| 174 | + \ThinkRank\SEO\Content_Type_Settings::FEATURE_SCHEMA, | |
| 175 | + true | |
| 176 | + )) { | |
| 177 | + return; | |
| 178 | + } | |
| 179 | + | |
| 157 | 180 | // Archives get a CollectionPage schema instead of the per-post-type one |
| 158 | 181 | if (!is_singular()) { |
| 159 | 182 | $this->output_archive_schema(); |
| 160 | 183 | return; |
| @@ -327,8 +350,16 @@ | ||
| 327 | 350 | * @param string $post_type Post type slug. |
| 328 | 351 | * @return bool True when structured data would be output for this post type. |
| 329 | 352 | */ |
| 330 | 353 | public function would_output_schema(string $post_type): bool { |
| 354 | + if (!\ThinkRank\SEO\Content_Type_Settings::is_enabled( | |
| 355 | + \ThinkRank\SEO\Content_Type_Settings::FEATURE_SCHEMA, | |
| 356 | + $post_type, | |
| 357 | + true | |
| 358 | + )) { | |
| 359 | + return false; | |
| 360 | + } | |
| 361 | + | |
| 331 | 362 | $settings = $this->get_global_seo_settings($post_type); |
| 332 | 363 | |
| 333 | 364 | return !empty($settings['schema_type']); |
| 334 | 365 | } |
| @@ -333,8 +364,27 @@ | ||
| 333 | 364 | return !empty($settings['schema_type']); |
| 334 | 365 | } |
| 335 | 366 | |
| 336 | 367 | /** |
| 368 | + * Whether this post type has a SAVED schema type, ignoring the built-in | |
| 369 | + * per-post-type default. | |
| 370 | + * | |
| 371 | + * would_output_schema() answers "will JSON-LD be emitted?", which the | |
| 372 | + * fallback in get_global_seo_settings() makes true for every public post | |
| 373 | + * type. The audit needs the different question "has the user configured | |
| 374 | + * anything?", so this reads the stored option without the default merge. | |
| 375 | + * | |
| 376 | + * @since 2.2.0 | |
| 377 | + * @param string $post_type Post type. | |
| 378 | + * @return bool True when an explicit schema_type is stored for this type. | |
| 379 | + */ | |
| 380 | + public function has_explicit_schema_type(string $post_type): bool { | |
| 381 | + $all_settings = get_option(self::OPTION_NAME, []); | |
| 382 | + | |
| 383 | + return !empty($all_settings[$post_type]['schema_type']); | |
| 384 | + } | |
| 385 | + | |
| 386 | + /** | |
| 337 | 387 | * Get Global SEO settings for a specific post type |
| 338 | 388 | * |
| 339 | 389 | * @since 1.0.0 |
| 340 | 390 | * @param string $post_type Post type |
| @@ -473,10 +523,11 @@ | ||
| 473 | 523 | 'datePublished' => get_the_date('c', $post), |
| 474 | 524 | 'dateModified' => get_the_modified_date('c', $post), |
| 475 | 525 | ]; |
| 476 | 526 | |
| 477 | - // Add description | |
| 478 | - $excerpt = get_the_excerpt($post); | |
| 527 | + // Add description. A Bricks page's stored `post_content` is not on the | |
| 528 | + // page, so core's derived excerpt must not describe it (#651). | |
| 529 | + $excerpt = $this->post_excerpt_text($post); | |
| 479 | 530 | if (!empty($excerpt)) { |
| 480 | 531 | $schema['description'] = wp_strip_all_tags($excerpt); |
| 481 | 532 | } |
| 482 | 533 | |
| @@ -547,9 +598,9 @@ | ||
| 547 | 598 | } |
| 548 | 599 | require_once $builder_file; |
| 549 | 600 | } |
| 550 | 601 | |
| 551 | - $excerpt = get_the_excerpt($post); | |
| 602 | + $excerpt = $this->post_excerpt_text($post); | |
| 552 | 603 | |
| 553 | 604 | $builder = new \ThinkRank\SEO\Schema_Builder(); |
| 554 | 605 | $schema = $builder->build_schema( |
| 555 | 606 | 'FAQPage', |
| @@ -554,9 +605,9 @@ | ||
| 554 | 605 | $schema = $builder->build_schema( |
| 555 | 606 | 'FAQPage', |
| 556 | 607 | [ |
| 557 | 608 | 'title' => get_the_title($post), |
| 558 | - 'content' => $post->post_content, | |
| 609 | + 'content' => \ThinkRank\SEO\Builder_Content::visible_content($post), | |
| 559 | 610 | 'excerpt' => $excerpt ? wp_strip_all_tags($excerpt) : '', |
| 560 | 611 | 'url' => get_permalink($post), |
| 561 | 612 | ], |
| 562 | 613 | get_post_type($post) === 'page' ? 'page' : 'post' |
| @@ -627,9 +678,9 @@ | ||
| 627 | 678 | 'dateModified' => get_the_modified_date('c', $post), |
| 628 | 679 | ]; |
| 629 | 680 | |
| 630 | 681 | // Add description |
| 631 | - $excerpt = get_the_excerpt($post); | |
| 682 | + $excerpt = $this->post_excerpt_text($post); | |
| 632 | 683 | if (!empty($excerpt)) { |
| 633 | 684 | $schema['description'] = wp_strip_all_tags($excerpt); |
| 634 | 685 | } |
| 635 | 686 | |
| @@ -697,9 +748,9 @@ | ||
| 697 | 748 | 'url' => get_permalink($post), |
| 698 | 749 | ]; |
| 699 | 750 | |
| 700 | 751 | // Add description |
| 701 | - $description = get_the_excerpt($post); | |
| 752 | + $description = $this->post_excerpt_text($post); | |
| 702 | 753 | if (empty($description)) { |
| 703 | 754 | $caption = wp_get_attachment_caption($post->ID); |
| 704 | 755 | if (!empty($caption)) { |
| 705 | 756 | $description = $caption; |
| @@ -863,15 +914,20 @@ | ||
| 863 | 914 | private function get_product_description(\WP_Post $post): string { |
| 864 | 915 | // Try custom meta field first |
| 865 | 916 | $description = get_post_meta($post->ID, '_thinkrank_product_description', true); |
| 866 | 917 | |
| 867 | - // Fallback to excerpt or content | |
| 918 | + // Fallback to excerpt or content. On a Bricks page the excerpt core | |
| 919 | + // derives comes from discarded `post_content`, so the visible body is | |
| 920 | + // used instead (#651). | |
| 868 | 921 | if (empty($description)) { |
| 869 | - $description = get_the_excerpt($post); | |
| 922 | + $description = $this->post_excerpt_text($post); | |
| 870 | 923 | } |
| 871 | 924 | |
| 872 | 925 | if (empty($description)) { |
| 873 | - $description = \ThinkRank\SEO\Pattern_Resolver::derive_excerpt((string) $post->post_content, 30); | |
| 926 | + $description = \ThinkRank\SEO\Pattern_Resolver::derive_excerpt( | |
| 927 | + \ThinkRank\SEO\Builder_Content::visible_content($post), | |
| 928 | + 30 | |
| 929 | + ); | |
| 874 | 930 | } |
| 875 | 931 | |
| 876 | 932 | return wp_strip_all_tags($description); |
| 877 | 933 | } |
| @@ -1135,8 +1191,29 @@ | ||
| 1135 | 1191 | } |
| 1136 | 1192 | } |
| 1137 | 1193 | |
| 1138 | 1194 | return $reviews; |
| 1195 | + } | |
| 1196 | + | |
| 1197 | + /** | |
| 1198 | + * The post's excerpt, taken from content the page actually renders. | |
| 1199 | + * | |
| 1200 | + * `get_the_excerpt()` falls back to trimming `post_content`, which a Bricks | |
| 1201 | + * page discards — so on one of those it describes text no visitor sees. A | |
| 1202 | + * hand-written excerpt is the author's own summary and still wins, because | |
| 1203 | + * `superseding_excerpt_source()` yields nothing for a post that has one | |
| 1204 | + * (#651). | |
| 1205 | + * | |
| 1206 | + * @since 2.3.1 | |
| 1207 | + * @param \WP_Post $post Post being described. | |
| 1208 | + * @return string | |
| 1209 | + */ | |
| 1210 | + private function post_excerpt_text(\WP_Post $post): string { | |
| 1211 | + $superseding = \ThinkRank\SEO\Builder_Content::superseding_excerpt_source($post); | |
| 1212 | + | |
| 1213 | + return '' !== $superseding | |
| 1214 | + ? \ThinkRank\SEO\Pattern_Resolver::derive_excerpt($superseding, 30) | |
| 1215 | + : (string) get_the_excerpt($post); | |
| 1139 | 1216 | } |
| 1140 | 1217 | |
| 1141 | 1218 | /** |
| 1142 | 1219 | * Register generated schema with the request's schema graph. |