PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / abilities / matrix / class-update-content-type-matrix.php

class-update-content-type-matrix.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.7.0, at includes/abilities/matrix/class-update-content-type-matrix.php

233 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Update content type matrix ability.
4 *
5 * @package ThinkRank\Abilities\Matrix
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Matrix;
11
12 use ThinkRank\Abilities\Ability_Base;
13 use ThinkRank\SEO\Content_Type_Settings;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Writes one entity's row of the per-content-type SEO feature matrix.
21 *
22 * Mirrors `POST /thinkrank/v1/global-seo/matrix`, which takes one entity at a
23 * time. Only the features sent are changed.
24 *
25 * Two things a caller has to get right, both enforced here rather than left to
26 * fail quietly:
27 *
28 * - The entity key is not guessable. A post type is its bare slug, everything
29 * else is prefixed (`taxonomy:category`, `archive:author`, `special:404`). An
30 * unknown key is refused with the valid ones named, instead of writing a row
31 * nothing reads.
32 * - A feature the entity does not expose is dropped by the endpoint. Sending
33 * `schema_enabled` for `special:search` would otherwise report success while
34 * storing nothing, so it is refused up front.
35 */
36 class Update_Content_Type_Matrix extends Ability_Base {
37
38 /**
39 * Constructor.
40 */
41 public function __construct() {
42 $this->id = 'thinkrank/update-content-type-matrix';
43 $this->label = __( 'Update ThinkRank Content Type Matrix', 'thinkrank' );
44 $this->description = __( 'Set the SEO features for one content type: metas, schema, Open Graph, Twitter cards and analytics, each "inherit", "on" or "off", plus robots noindex and sitemap inclusion. Call get-content-type-matrix first for valid entity keys; a post type is its bare slug, other entities are prefixed with taxonomy:, archive: or special:.', 'thinkrank' );
45 }
46
47 /**
48 * {@inheritDoc}
49 *
50 * @return array<string, bool|float|string>
51 */
52 public function get_annotations() {
53 return [
54 'readonly' => false,
55 'destructive' => false,
56 'idempotent' => true,
57 'priority' => 0.8,
58 'openWorldHint' => false,
59 ];
60 }
61
62 /**
63 * {@inheritDoc}
64 *
65 * @return array<string, mixed>
66 */
67 public function get_input_schema() {
68 $properties = [
69 'entity' => [
70 'type' => 'string',
71 'description' => __( 'Entity key from get-content-type-matrix, e.g. "post", "taxonomy:category", "archive:author", "special:404".', 'thinkrank' ),
72 ],
73 ];
74
75 foreach ( Content_Type_Settings::FEATURES as $feature ) {
76 $properties[ $feature ] = [
77 'type' => 'string',
78 'enum' => Content_Type_Settings::STATES,
79 ];
80 }
81
82 $properties['noindex'] = [
83 'type' => 'boolean',
84 'description' => __( 'Whether this entity is noindexed. Search results and the 404 page are noindexed by default.', 'thinkrank' ),
85 ];
86 $properties['sitemap_include'] = [
87 'type' => 'boolean',
88 'description' => __( 'Whether this entity belongs in the XML sitemap. Only accepted for entities the sitemap generator emits.', 'thinkrank' ),
89 ];
90
91 return [
92 'type' => 'object',
93 'additionalProperties' => false,
94 'required' => [ 'entity' ],
95 'properties' => $properties,
96 ];
97 }
98
99 /**
100 * {@inheritDoc}
101 *
102 * @return array<string, mixed>
103 */
104 public function get_output_schema() {
105 return [
106 'type' => 'object',
107 'properties' => [
108 'success' => [ 'type' => 'boolean' ],
109 'entity' => [ 'type' => 'string' ],
110 'data' => [
111 'type' => 'object',
112 'additionalProperties' => true,
113 'description' => __( 'The entity as stored after the write: resolved feature values, robots directives and sitemap inclusion.', 'thinkrank' ),
114 ],
115 ],
116 ];
117 }
118
119 /**
120 * Execute ability.
121 *
122 * @param array<string, mixed> $input Ability input payload.
123 * @return array<string, mixed>|\WP_Error
124 */
125 public function execute( $input ) {
126 $input = (array) $input;
127 $entity = (string) ( $input['entity'] ?? '' );
128
129 $descriptor = null;
130 $keys = [];
131
132 foreach ( Content_Type_Settings::get_entities() as $candidate ) {
133 $keys[] = $candidate['key'];
134 if ( $candidate['key'] === $entity ) {
135 $descriptor = $candidate;
136 }
137 }
138
139 if ( null === $descriptor ) {
140 return new \WP_Error(
141 'thinkrank_unknown_entity',
142 sprintf(
143 /* translators: 1: the entity key that was sent. 2: comma-separated list of valid keys. */
144 __( 'Unknown entity "%1$s". Valid keys on this site: %2$s.', 'thinkrank' ),
145 $entity,
146 implode( ', ', $keys )
147 ),
148 [ 'status' => 400 ]
149 );
150 }
151
152 $settings = [];
153
154 foreach ( Content_Type_Settings::FEATURES as $feature ) {
155 if ( ! array_key_exists( $feature, $input ) ) {
156 continue;
157 }
158
159 // The endpoint drops a feature this entity does not expose. Silently
160 // accepting it would report a success that stored nothing.
161 if ( ! in_array( $feature, (array) $descriptor['features'], true ) ) {
162 return new \WP_Error(
163 'thinkrank_feature_not_available',
164 sprintf(
165 /* translators: 1: feature key. 2: entity key. */
166 __( '"%1$s" is not available for "%2$s".', 'thinkrank' ),
167 $feature,
168 $entity
169 ),
170 [ 'status' => 400 ]
171 );
172 }
173
174 $settings[ $feature ] = (string) $input[ $feature ];
175 }
176
177 if ( array_key_exists( 'noindex', $input ) ) {
178 $noindex = (bool) $input['noindex'];
179 $resolved = Content_Type_Settings::resolve_robots_meta( $entity );
180
181 // index and noindex are one decision stored as two booleans; writing
182 // only half leaves the pair saying two different things.
183 $resolved['noindex'] = $noindex;
184 $resolved['index'] = ! $noindex;
185
186 $settings['robots_meta_enabled'] = true;
187 $settings['robots_meta'] = $resolved;
188 }
189
190 if ( array_key_exists( 'sitemap_include', $input ) ) {
191 if ( empty( $descriptor['supports_sitemap'] ) ) {
192 return new \WP_Error(
193 'thinkrank_sitemap_not_supported',
194 sprintf(
195 /* translators: %s: entity key. */
196 __( '"%s" is not emitted in the sitemap, so its inclusion cannot be set.', 'thinkrank' ),
197 $entity
198 ),
199 [ 'status' => 400 ]
200 );
201 }
202
203 $settings['sitemap_include'] = (bool) $input['sitemap_include'];
204 }
205
206 if ( ! $settings ) {
207 return new \WP_Error(
208 'thinkrank_no_settings_provided',
209 __( 'Provide at least one feature, noindex, or sitemap_include to change.', 'thinkrank' ),
210 [ 'status' => 400 ]
211 );
212 }
213
214 $request = new \WP_REST_Request( 'POST', '/thinkrank/v1/global-seo/matrix' );
215 $request->set_param( 'entity', $entity );
216 $request->set_param( 'settings', $settings );
217
218 $response = rest_do_request( $request );
219
220 if ( $response->is_error() ) {
221 return $response->as_error();
222 }
223
224 $data = $response->get_data();
225
226 return [
227 'success' => true,
228 'entity' => $entity,
229 'data' => $data['data'] ?? [],
230 ];
231 }
232 }
233