PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.0
2.14.0 2.13.0 2.12.0 2.11.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 All 55 releases
← All changes | includes/api/class-manager.php +69 -7 2.13.0 → 2.14.0 View file →
@@ -520,9 +520,9 @@
520 520
521 521 register_rest_route(self::NAMESPACE, '/schema/enable-for-post', [
522 522 'methods' => 'POST',
523 523 'callback' => [$this, 'enable_schema_for_post'],
524 - 'permission_callback' => [$this, 'check_admin_permissions'],
524 + 'permission_callback' => [$this, 'check_schema_permissions'],
525 525 'args' => [
526 526 'post_id' => [
527 527 'type' => 'integer',
528 528 'required' => true,
@@ -533,9 +533,9 @@
533 533
534 534 register_rest_route(self::NAMESPACE, '/ai/test-connection', [
535 535 'methods' => 'POST',
536 536 'callback' => [$this, 'test_ai_connection'],
537 - 'permission_callback' => [$this, 'check_admin_permissions'],
537 + 'permission_callback' => [$this, 'check_ai_tools_permissions'],
538 538 'args' => [
539 539 'api_key' => [
540 540 'type' => 'string',
541 541 'required' => false,
@@ -573,9 +573,9 @@
573 573 // simply leaves the user typing the id by hand (#721).
574 574 register_rest_route(self::NAMESPACE, '/ai/models', [
575 575 'methods' => 'POST',
576 576 'callback' => [$this, 'list_endpoint_models'],
577 - 'permission_callback' => [$this, 'check_admin_permissions'],
577 + 'permission_callback' => [$this, 'check_ai_tools_permissions'],
578 578 'args' => [
579 579 'base_url' => [
580 580 'type' => 'string',
581 581 'required' => false,
@@ -844,8 +844,70 @@
844 844 return true;
845 845 }
846 846
847 847 /**
848 + * Check Schema Manager section permissions.
849 + *
850 + * Delegable via Role Manager: route_map() maps the `schema` prefix to
851 + * thinkrank_schema. /schema/enable-for-post is what the editor's "enable
852 + * structured data" suggestion posts to, so gating it on manage_options made
853 + * that button fail for exactly the roles the Schema grant was meant to
854 + * serve (#844).
855 + *
856 + * @param \WP_REST_Request $request Request object
857 + * @return bool|\WP_Error Permission status
858 + */
859 + public function check_schema_permissions(\WP_REST_Request $request) {
860 + return $this->check_mapped_capability('thinkrank_schema');
861 + }
862 +
863 + /**
864 + * Check AI Tools section permissions.
865 + *
866 + * Delegable via Role Manager: route_map() maps the `ai` prefix to
867 + * thinkrank_content_tools. Testing a provider and listing its models are
868 + * configuration reads that report whether the stored key works; neither
869 + * returns the key.
870 + *
871 + * @param \WP_REST_Request $request Request object
872 + * @return bool|\WP_Error Permission status
873 + */
874 + public function check_ai_tools_permissions(\WP_REST_Request $request) {
875 + return $this->check_mapped_capability('thinkrank_content_tools');
876 + }
877 +
878 + /**
879 + * Logged in, and holding a ThinkRank capability from the map.
880 + *
881 + * One body for the per-section callbacks above so they cannot drift apart
882 + * the way the hardcoded manage_options checks drifted from the map.
883 + * Administrators are unaffected: Role_Manager grants every ThinkRank
884 + * capability to manage_options holders through `user_has_cap`.
885 + *
886 + * @param string $capability ThinkRank capability slug.
887 + * @return bool|\WP_Error Permission status
888 + */
889 + private function check_mapped_capability(string $capability) {
890 + if (!is_user_logged_in()) {
891 + return new \WP_Error(
892 + 'rest_forbidden',
893 + __('You must be logged in to access this endpoint.', 'thinkrank'),
894 + ['status' => 401]
895 + );
896 + }
897 +
898 + if (!\ThinkRank\Core\Capability_Manager::current_user_can($capability)) {
899 + return new \WP_Error(
900 + 'rest_forbidden',
901 + __('You do not have permission to access this ThinkRank feature.', 'thinkrank'),
902 + ['status' => 403]
903 + );
904 + }
905 +
906 + return true;
907 + }
908 +
909 + /**
848 910 * Get user capabilities
849 911 *
850 912 * @param \WP_REST_Request $request Request object
851 913 * @return \WP_REST_Response Response object
@@ -2140,9 +2202,9 @@
2140 2202 'model' => $model,
2141 2203 'model_available' => $model !== '',
2142 2204 'message' => $model !== ''
2143 2205 /* translators: %s: the model id that was tested. */
2144 - ? sprintf(__('OpenAI API connection successful — model "%s" is available.', 'thinkrank'), $model)
2206 + ? sprintf(__('OpenAI API connection successful. Model "%s" is available.', 'thinkrank'), $model)
2145 2207 : __('OpenAI API connection successful!', 'thinkrank'),
2146 2208 'models_count' => count($data['data']),
2147 2209 ];
2148 2210 }
@@ -2218,9 +2280,9 @@
2218 2280 'model' => $model,
2219 2281 'model_available' => $model !== '',
2220 2282 'message' => $model !== ''
2221 2283 /* translators: %s: the model id that was tested. */
2222 - ? sprintf(__('OpenRouter API connection successful — model "%s" is available.', 'thinkrank'), $model)
2284 + ? sprintf(__('OpenRouter API connection successful. Model "%s" is available.', 'thinkrank'), $model)
2223 2285 : __('OpenRouter API connection successful!', 'thinkrank'),
2224 2286 ];
2225 2287 }
2226 2288 }
@@ -2344,9 +2406,9 @@
2344 2406 'success' => true,
2345 2407 'model' => $claude_model,
2346 2408 'model_available' => true,
2347 2409 /* translators: %s: the model id that was tested. */
2348 - 'message' => sprintf(__('Claude API connection successful — model "%s" is available.', 'thinkrank'), $claude_model),
2410 + 'message' => sprintf(__('Claude API connection successful. Model "%s" is available.', 'thinkrank'), $claude_model),
2349 2411 ];
2350 2412 } else {
2351 2413 $error_data = json_decode($response_body, true);
2352 2414 $error_message = $error_data['error']['message'] ?? __('Unknown API error', 'thinkrank');
@@ -2431,9 +2493,9 @@
2431 2493 'success' => true,
2432 2494 'model' => $gemini_model,
2433 2495 'model_available' => true,
2434 2496 /* translators: %s: the model id that was tested. */
2435 - 'message' => sprintf(__('Gemini API connection successful — model "%s" is available.', 'thinkrank'), $gemini_model),
2497 + 'message' => sprintf(__('Gemini API connection successful. Model "%s" is available.', 'thinkrank'), $gemini_model),
2436 2498 ];
2437 2499 } else {
2438 2500 $error_data = json_decode($response_body, true);
2439 2501 $error_message = $error_data['error']['message'] ?? __('Unknown API error', 'thinkrank');