PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 All 51 releases
thinkrank / includes / seo / class-global-seo-post-types.php

class-global-seo-post-types.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.10.0, at includes/seo/class-global-seo-post-types.php

127 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Global SEO Post Type Policy
4 *
5 * Single source of truth for which post types are valid Global SEO targets,
6 * shared by the admin post-type discovery (which populates the UI), the REST
7 * validator, and the MCP ability validator so all three agree. Previously the
8 * UI hid ineligible types while the write paths accepted any public type,
9 * letting direct REST/ability calls persist settings for types the UI
10 * classifies as unsuitable.
11 *
12 * @package ThinkRank
13 * @subpackage SEO
14 * @since 1.20.1
15 */
16
17 declare(strict_types=1);
18
19 namespace ThinkRank\SEO;
20
21 // Prevent direct access.
22 if (!defined('ABSPATH')) {
23 exit;
24 }
25
26 /**
27 * Global SEO post-type eligibility policy.
28 *
29 * @since 1.20.1
30 */
31 class Global_SEO_Post_Types {
32
33 /**
34 * Whether a post type may receive Global SEO settings.
35 *
36 * Policy: the type must be public; a non-built-in type must also be
37 * front-end viewable (excludes builder/utility CPTs that register
38 * `public => true` only for previews); and it must not be on the filterable
39 * `thinkrank_global_seo_excluded_post_types` deny list.
40 *
41 * @param \WP_Post_Type|string $post_type Post type object or name.
42 * @return bool
43 */
44 public static function is_allowed($post_type): bool {
45 $object = is_string($post_type) ? get_post_type_object($post_type) : $post_type;
46
47 if (!$object instanceof \WP_Post_Type || empty($object->public)) {
48 return false;
49 }
50
51 // Builder/utility CPTs that register public => true for preview purposes
52 // but aren't front-end viewable content (e.g. Elementor's "Floating
53 // Elements"). Built-in types (post/page/attachment) are always kept.
54 if ($object->_builtin === false && !is_post_type_viewable($object)) {
55 return false;
56 }
57
58 return !in_array($object->name, self::excluded_post_types($object), true);
59 }
60
61 /**
62 * Every post type that passes {@see self::is_allowed()}.
63 *
64 * The sitewide half of duplicate snippet detection needs the whole list
65 * rather than one name, because a duplicate title is a duplicate whichever
66 * post type the other page happens to be (#564).
67 *
68 * @since 2.10.0
69 *
70 * @return string[] Post type names.
71 */
72 public static function allowed(): array {
73 $allowed = [];
74
75 foreach (get_post_types(['public' => true], 'objects') as $object) {
76 if (self::is_allowed($object)) {
77 $allowed[] = $object->name;
78 }
79 }
80
81 return $allowed;
82 }
83
84 /**
85 * Builder and utility CPTs that are not optimizable content.
86 *
87 * These register `public => true` for preview purposes but are layout
88 * fragments, not pages anyone gives a title and a meta description.
89 *
90 * Shared rather than duplicated: the per-post SEO metabox discovers post
91 * types by the same public/`show_ui` rule and used to keep its own
92 * exclusion list, which named only WordPress internals. The two policies
93 * disagreed — a builder template CPT was denied Global SEO while still
94 * getting a full metabox — so both now read this one list (#621).
95 *
96 * @since 2.2.1
97 *
98 * Deliberately untyped: this is passed straight to a public filter whose
99 * existing contract accepts whatever the caller has (a `WP_Post_Type`, or
100 * null when the list is wanted without a subject).
101 *
102 * @param mixed $post_type_object Post type being judged, passed to the filter.
103 * @return string[] Post type names to exclude.
104 */
105 public static function excluded_post_types($post_type_object = null): array {
106 // Filterable so integrators can tune it without patching core. The
107 // post-type object is passed as the second argument (matches the admin
108 // discovery filter usage).
109 $excluded = apply_filters('thinkrank_global_seo_excluded_post_types', [
110 'elementor_library', 'oceanwp_library', 'ae_global_templates',
111 'e-floating-buttons', 'elementor_component',
112 // Divi: the library plus every Theme Builder template type.
113 'et_pb_layout', 'et_theme_builder', 'et_template',
114 'et_header_layout', 'et_body_layout', 'et_footer_layout',
115 // Beaver Builder: saved templates (rows, columns, modules) and
116 // Beaver Themer's layouts. Same clutter Divi's Theme Builder
117 // templates caused before they were listed here (#449).
118 'fl-builder-template', 'fl-theme-layout',
119 // Bricks: saved templates plus the header/footer/section layouts
120 // its Theme Builder stores as the same CPT (#257).
121 'bricks_template',
122 ], $post_type_object);
123
124 return array_values(array_filter((array) $excluded, 'is_string'));
125 }
126 }
127