PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
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 1.11.0 All 47 releases
thinkrank / includes / abilities / class-ability-base.php

class-ability-base.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/abilities/class-ability-base.php

219 lines 4.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability base class.
4 *
5 * @package ThinkRank\Abilities
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 /**
17 * Base ability implementation for ThinkRank.
18 *
19 * Each ThinkRank MCP ability extends this class, providing an input/output JSON
20 * schema and an execute() body. Abilities are registered with the WordPress
21 * Abilities API and exposed to AI clients through the MCP server.
22 */
23 abstract class Ability_Base {
24 /**
25 * Minimum capability allowed for ThinkRank abilities.
26 */
27 private const MIN_CAPABILITY = 'manage_options';
28
29 /**
30 * Unique ability identifier.
31 *
32 * @var string
33 */
34 protected $id = '';
35
36 /**
37 * Human-readable label.
38 *
39 * @var string
40 */
41 protected $label = '';
42
43 /**
44 * Ability description.
45 *
46 * @var string
47 */
48 protected $description = '';
49
50 /**
51 * Ability category.
52 *
53 * @var string
54 */
55 protected $category = 'thinkrank';
56
57 /**
58 * Required WordPress capability.
59 *
60 * @var string
61 */
62 protected $capability = 'manage_options';
63
64 /**
65 * An empty JSON Schema `properties` map.
66 *
67 * A schema that takes no arguments still has to say so, and the shape it
68 * says it in matters twice over:
69 *
70 * - `[]` JSON-encodes as `[]`. JSON Schema requires an object there, and
71 * strict MCP clients reject the array form (#476).
72 * - `(object) []` encodes as `{}` but is not array-accessible, and
73 * `rest_validate_object_value_from_schema()` indexes into the map —
74 * `isset( $args['properties'][ $key ] )`. On PHP 8 that is a fatal
75 * `Error`, so a single unexpected input key turns a clean 400 into a 500.
76 *
77 * `ArrayObject` is the shape that satisfies both: it JSON-encodes as `{}`
78 * and still answers array access, so core validation runs as it always did.
79 *
80 * @return \ArrayObject<string, mixed> Empty properties map.
81 */
82 protected static function empty_properties() {
83 return new \ArrayObject();
84 }
85
86 /**
87 * Get the JSON Schema for ability input.
88 *
89 * @return array<string, mixed>
90 */
91 abstract public function get_input_schema();
92
93 /**
94 * Get the JSON Schema for ability output.
95 *
96 * @return array<string, mixed>
97 */
98 abstract public function get_output_schema();
99
100 /**
101 * Execute the ability.
102 *
103 * @param array<string, mixed> $input Validated input.
104 * @return array<string, mixed>|\WP_Error
105 */
106 abstract public function execute( $input );
107
108 /**
109 * Check whether abilities are enabled.
110 *
111 * @return bool
112 */
113 public static function abilities_enabled() {
114 return (bool) apply_filters( 'thinkrank_abilities_api_enabled', true );
115 }
116
117 /**
118 * Check whether this ability can be registered and executed.
119 *
120 * @return bool
121 */
122 public function is_enabled() {
123 return (bool) apply_filters( 'thinkrank_ability_enabled', self::abilities_enabled(), $this->id, $this );
124 }
125
126 /**
127 * Permission callback for the abilities API.
128 *
129 * @return bool
130 */
131 public function permission_callback() {
132 if ( ! $this->is_enabled() ) {
133 return false;
134 }
135
136 return current_user_can( $this->capability );
137 }
138
139 /**
140 * Enforce ThinkRank's current admin capability policy.
141 *
142 * @return bool
143 */
144 public function meets_capability_policy() {
145 return self::MIN_CAPABILITY === $this->capability;
146 }
147
148 /**
149 * MCP-compatible annotations for this ability.
150 *
151 * @return array<string, bool|float|string>
152 */
153 public function get_annotations() {
154 return [
155 'readonly' => false,
156 'destructive' => false,
157 'idempotent' => false,
158 'priority' => 2.0,
159 'openWorldHint' => false,
160 ];
161 }
162
163 /**
164 * Wrapper around execute() with action hooks.
165 *
166 * @param array<string, mixed> $input Validated input.
167 * @return array<string, mixed>|\WP_Error
168 */
169 public function execute_wrapper( $input ) {
170 do_action( 'thinkrank_before_ability_execute', $this->id, $input );
171
172 $output = $this->execute( $input );
173
174 do_action( 'thinkrank_after_ability_execute', $this->id, $input, $output );
175
176 return $output;
177 }
178
179 /**
180 * Register the ability with the WordPress Abilities API.
181 *
182 * @return void
183 */
184 public function register() {
185 if ( ! function_exists( 'wp_register_ability' ) ) {
186 return;
187 }
188
189 wp_register_ability(
190 $this->id,
191 [
192 'label' => $this->label,
193 'description' => $this->description,
194 'category' => $this->category,
195 'input_schema' => $this->get_input_schema(),
196 'output_schema' => $this->get_output_schema(),
197 'permission_callback' => [ $this, 'permission_callback' ],
198 'execute_callback' => [ $this, 'execute_wrapper' ],
199 'meta' => [
200 'show_in_rest' => true,
201 'annotations' => $this->get_annotations(),
202 'mcp' => [
203 'public' => false,
204 ],
205 ],
206 ]
207 );
208 }
209
210 /**
211 * Get the ability ID.
212 *
213 * @return string
214 */
215 public function get_id() {
216 return $this->id;
217 }
218 }
219