PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.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 1.0.2 All 50 releases
← All changes | includes/api/class-llms-txt-endpoint.php +62 -9 1.28.0 → 2.9.0 View file →
@@ -18,8 +18,9 @@
18 18
19 19 use ThinkRank\SEO\LLMs_Txt_Manager;
20 20 use ThinkRank\AI\Manager as AI_Manager;
21 21 use ThinkRank\API\Traits\CSRF_Protection;
22 +use ThinkRank\API\Traits\Context_Authorization;
22 23 use WP_REST_Controller;
23 24 use WP_REST_Request;
24 25 use WP_REST_Response;
25 26 use WP_Error;
@@ -30,8 +31,9 @@
30 31 }
31 32
32 33 // Load CSRF Protection trait
33 34 require_once THINKRANK_PLUGIN_DIR . 'includes/api/traits/trait-csrf-protection.php';
35 +require_once THINKRANK_PLUGIN_DIR . 'includes/api/traits/trait-context-authorization.php';
34 36
35 37 /**
36 38 * LLMs.txt API Endpoints Class
37 39 *
@@ -42,8 +44,9 @@
42 44 * @since 1.0.0
43 45 */
44 46 class LLMs_Txt_Endpoint extends WP_REST_Controller {
45 47 use CSRF_Protection;
48 + use Context_Authorization;
46 49
47 50 /**
48 51 * LLMs.txt Manager instance
49 52 *
@@ -124,9 +127,10 @@
124 127 [
125 128 [
126 129 'methods' => 'GET',
127 130 'callback' => [$this, 'get_settings'],
128 - 'permission_callback' => [$this, 'check_permissions']
131 + 'permission_callback' => [$this, 'check_permissions'],
132 + 'args' => $this->get_context_route_args()
129 133 ],
130 134 [
131 135 'methods' => 'POST',
132 136 'callback' => [$this, 'update_settings'],
@@ -200,9 +204,21 @@
200 204 [
201 205 [
202 206 'methods' => 'GET',
203 207 'callback' => [$this, 'get_optimization_results'],
204 - 'permission_callback' => [$this, 'check_permissions']
208 + 'permission_callback' => [$this, 'check_permissions'],
209 + // The handler reads `limit` and forwards it to a prepared
210 + // LIMIT %d. Not injectable, but unbounded — and unregistered
211 + // means no coercion either (#394).
212 + 'args' => [
213 + 'limit' => [
214 + 'required' => false,
215 + 'type' => 'integer',
216 + 'default' => 20,
217 + 'minimum' => 1,
218 + 'maximum' => 100,
219 + ],
220 + ]
205 221 ]
206 222 ]
207 223 );
208 224
@@ -225,14 +241,19 @@
225 241 *
226 242 * @since 1.0.0
227 243 *
228 244 * @param WP_REST_Request $request Request object
229 - * @return WP_REST_Response Response object
245 + * @return WP_REST_Response|WP_Error Response object, or the context error
230 246 */
231 - public function get_settings(WP_REST_Request $request): WP_REST_Response {
247 + public function get_settings(WP_REST_Request $request) {
232 248 try {
233 - $context_type = $request->get_param('context_type') ?? 'site';
234 - $context_id = $request->get_param('context_id');
249 + // SECURITY: the settings are stored per context, so the object has
250 + // to be authorised before it is read (#385).
251 + $context = $this->resolve_request_context($request);
252 + if (is_wp_error($context)) {
253 + return $context;
254 + }
255 + [$context_type, $context_id] = $context;
235 256
236 257 // Get settings from LLMs.txt Manager
237 258 $settings = $this->llms_txt_manager->get_settings($context_type, $context_id);
238 259
@@ -268,11 +289,17 @@
268 289 */
269 290 public function update_settings(WP_REST_Request $request) {
270 291 try {
271 292 $settings = $request->get_param('settings');
272 - $context_type = $request->get_param('context_type') ?? 'site';
273 - $context_id = $request->get_param('context_id');
274 293
294 + // SECURITY: this write is keyed by the context, so the object has to
295 + // be authorised before anything is persisted (#385).
296 + $context = $this->resolve_request_context($request);
297 + if (is_wp_error($context)) {
298 + return $context;
299 + }
300 + [$context_type, $context_id] = $context;
301 +
275 302 // Validate settings
276 303 if (empty($settings) || !is_array($settings)) {
277 304 return new WP_Error(
278 305 'invalid_settings',
@@ -321,8 +348,29 @@
321 348 'message' => 'Settings were saved, but the published llms.txt file could not be removed and may still be served. Please remove it manually.'
322 349 ], 200);
323 350 }
324 351
352 + // A delivery-mode switch that could not move the already-published
353 + // document leaves /llms.txt on the old path; say so instead of
354 + // reporting a clean save. A switch that worked but landed on a
355 + // server that answers the file without a charset warns under its own
356 + // key — reporting that one as a failed switch misdescribes it.
357 + $delivery_warning = $this->llms_txt_manager->delivery_switch_warning();
358 +
359 + if ('' !== $delivery_warning) {
360 + $switch_failed = $this->llms_txt_manager->delivery_switch_failed();
361 +
362 + return new WP_REST_Response([
363 + 'success' => true,
364 + ($switch_failed ? 'delivery_switch_failed' : 'delivery_warning') => true,
365 + 'data' => [
366 + 'settings' => $settings,
367 + 'validation' => $validation
368 + ],
369 + 'message' => $delivery_warning
370 + ], 200);
371 + }
372 +
325 373 return new WP_REST_Response([
326 374 'success' => true,
327 375 'data' => [
328 376 'settings' => $settings,
@@ -894,9 +942,14 @@
894 942 'optimization_history' => $optimization_history
895 943 ]
896 944 ], 200);
897 945
898 - } catch (Exception $e) {
946 + } catch (\Exception $e) {
947 + // Was `catch (Exception $e)` inside `namespace ThinkRank\API;` with
948 + // no `use Exception;`, so it resolved to ThinkRank\API\Exception —
949 + // a class that does not exist. The catch never matched and every
950 + // exception escaped as a fatal. Every other catch in this file
951 + // already uses the leading backslash (#394).
899 952 return new WP_REST_Response([
900 953 'success' => false,
901 954 'error' => 'Failed to load overview data: ' . $e->getMessage()
902 955 ], 500);