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 / api / class-thin-content-endpoint.php

class-thin-content-endpoint.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.10.0, at includes/api/class-thin-content-endpoint.php

205 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Thin content report REST endpoints.
4 *
5 * @package ThinkRank
6 * @subpackage API
7 * @since 2.10.0
8 */
9
10 declare(strict_types=1);
11
12 namespace ThinkRank\API;
13
14 use ThinkRank\Core\Capability_Manager;
15 use ThinkRank\SEO\Thin_Content;
16 use WP_REST_Request;
17 use WP_REST_Response;
18
19 // Prevent direct access.
20 if (!defined('ABSPATH')) {
21 exit;
22 }
23
24 /**
25 * Thin content endpoints.
26 *
27 * Under `global-seo/` for the same reason Bulk Snippets is: the Role Manager
28 * maps that route prefix to the Bulk SEO Optimization capability, so a role
29 * granted the section gets the screen and one denied it does not, with no
30 * second mapping to keep in step.
31 *
32 * @since 2.10.0
33 */
34 class Thin_Content_Endpoint {
35
36 /**
37 * API namespace.
38 */
39 private const NAMESPACE = 'thinkrank/v1';
40
41 /**
42 * Route base.
43 */
44 private const REST_BASE = 'global-seo/thin-content';
45
46 /**
47 * Register routes.
48 *
49 * @return void
50 */
51 public function register_routes(): void {
52 register_rest_route(self::NAMESPACE, '/' . self::REST_BASE, [
53 'methods' => 'GET',
54 'callback' => [$this, 'get_report'],
55 'permission_callback' => [$this, 'check_permissions'],
56 'args' => [
57 'refresh' => [
58 'type' => 'boolean',
59 'default' => false,
60 ],
61 ],
62 ]);
63
64 register_rest_route(self::NAMESPACE, '/' . self::REST_BASE . '/settings', [
65 [
66 'methods' => 'GET',
67 'callback' => [$this, 'get_settings'],
68 'permission_callback' => [$this, 'check_permissions'],
69 ],
70 [
71 'methods' => 'POST',
72 'callback' => [$this, 'update_settings'],
73 'permission_callback' => [$this, 'check_permissions'],
74 'args' => [
75 'default' => [
76 'type' => 'integer',
77 'minimum' => Thin_Content::MIN_THRESHOLD,
78 'maximum' => Thin_Content::MAX_THRESHOLD,
79 ],
80 'overrides' => [
81 'type' => 'object',
82 'description' => 'Post type => threshold. null clears an override.',
83 // Spelled out per post type rather than left as a bare
84 // object. Without this the route accepted anything and
85 // `(int)` turned it into a number: `"abc"` and `0` both
86 // became 1, the strictest threshold there is, and came
87 // back as a 200. A threshold the caller never asked for
88 // is worse than a rejection, and `default` beside it was
89 // already rejecting the same values with a 400.
90 'additionalProperties' => false,
91 'properties' => self::override_schema(),
92 ],
93 ],
94 ],
95 ]);
96 }
97
98 /**
99 * One schema per post type in scope, for the overrides object.
100 *
101 * The same shape `update-thin-content-settings` declares, so the route the
102 * screen calls and the ability an agent calls accept and refuse the same
103 * values. `null` is a member of the type rather than a special case: it is
104 * how an override is cleared.
105 *
106 * @return array<string,array<string,mixed>>
107 */
108 private static function override_schema(): array {
109 $properties = [];
110
111 foreach (Thin_Content::post_types() as $post_type) {
112 $properties[$post_type] = [
113 'type' => ['integer', 'null'],
114 'minimum' => Thin_Content::MIN_THRESHOLD,
115 'maximum' => Thin_Content::MAX_THRESHOLD,
116 ];
117 }
118
119 return $properties;
120 }
121
122 /**
123 * Section permission.
124 *
125 * @return bool
126 */
127 public function check_permissions(): bool {
128 return Capability_Manager::current_user_can('thinkrank_global_seo');
129 }
130
131 /**
132 * GET — the report.
133 *
134 * Bounded: each call counts one batch of uncounted posts and reports how
135 * many are left, so the screen keeps asking until `pending` reaches zero
136 * rather than any one request walking the site. Resolving builder content
137 * is what makes that necessary.
138 *
139 * @param WP_REST_Request $request Request.
140 * @return WP_REST_Response
141 */
142 public function get_report(WP_REST_Request $request): WP_REST_Response {
143 return new WP_REST_Response(
144 Thin_Content::report((bool) $request->get_param('refresh'))
145 );
146 }
147
148 /**
149 * GET — the thresholds alone.
150 *
151 * @return WP_REST_Response
152 */
153 public function get_settings(): WP_REST_Response {
154 return new WP_REST_Response(self::settings_payload());
155 }
156
157 /**
158 * POST — change the thresholds.
159 *
160 * Returns what is now **stored** rather than what was submitted, so a value
161 * the clamp reduced is reported as the value it became.
162 *
163 * @param WP_REST_Request $request Request.
164 * @return WP_REST_Response
165 */
166 public function update_settings(WP_REST_Request $request): WP_REST_Response {
167 $input = [];
168
169 if (null !== $request->get_param('default')) {
170 $input['default'] = (int) $request->get_param('default');
171 }
172
173 $overrides = $request->get_param('overrides');
174 if (is_array($overrides)) {
175 $input['overrides'] = $overrides;
176 }
177
178 Thin_Content::save_settings($input);
179
180 return new WP_REST_Response(self::settings_payload());
181 }
182
183 /**
184 * The thresholds, plus what a caller needs to render them.
185 *
186 * @return array<string,mixed>
187 */
188 private static function settings_payload(): array {
189 $settings = Thin_Content::settings();
190
191 return [
192 'default' => $settings['default'],
193 'overrides' => (object) $settings['overrides'],
194 // The effective threshold for every post type in scope, so a
195 // caller never has to reimplement "override, else default".
196 'effective' => (object) Thin_Content::thresholds(),
197 'unit' => \ThinkRank\SEO\Word_Count_Index::unit(),
198 'limits' => [
199 'min_threshold' => Thin_Content::MIN_THRESHOLD,
200 'max_threshold' => Thin_Content::MAX_THRESHOLD,
201 ],
202 ];
203 }
204 }
205