PluginProbe
AI / trunk
AI vtrunk
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / Abstracts / Abstract_Feature.php

Abstract_Feature.php in AI trunk, at includes/Abstracts/Abstract_Feature.php

329 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abstract Feature base class.
4 *
5 * @package WordPress\AI\Abstracts
6 */
7
8 declare( strict_types=1 );
9
10 namespace WordPress\AI\Abstracts;
11
12 use InvalidArgumentException;
13 use WordPress\AI\Contracts\Feature;
14 use WordPress\AI\Features\Feature_Category;
15 use WordPress\AI\Settings\Settings_Registration;
16
17 /**
18 * Base implementation for features.
19 *
20 * Provides common functionality for all features including enable/disable state.
21 *
22 * @since 0.6.0
23 */
24 abstract class Abstract_Feature implements Feature {
25 /**
26 * Feature identifier.
27 *
28 * @since 0.6.0
29 * @var non-empty-string
30 */
31 protected string $id;
32
33 /**
34 * Feature label.
35 *
36 * @since 0.6.0
37 * @var non-empty-string
38 */
39 protected string $label;
40
41 /**
42 * Feature description.
43 *
44 * @since 0.6.0
45 * @var non-empty-string
46 */
47 protected string $description;
48
49 /**
50 * Feature category.
51 *
52 * @since 0.6.0
53 * @var non-empty-string
54 */
55 protected string $category;
56
57 /**
58 * Cache for this feature's enabled status.
59 *
60 * @since 0.6.0
61 * @var bool|null
62 */
63 private ?bool $enabled_cache = null;
64
65 /**
66 * The feature stability level.
67 * @var 'deprecated'|'experimental'|'stable'
68 */
69 private string $stability;
70
71 /**
72 * The image URL for feature showcase display.
73 *
74 * @since 0.8.0
75 * @var string
76 */
77 protected string $image;
78
79 /**
80 * The AI capability type required by this feature.
81 *
82 * @since 0.9.0
83 * @var string
84 */
85 protected string $capability;
86
87 /**
88 * Constructor.
89 *
90 * Loads feature metadata and initializes properties.
91 *
92 * @since 0.6.0
93 *
94 * @throws \InvalidArgumentException If feature metadata is invalid.
95 */
96 final public function __construct() {
97 $this->id = static::get_id();
98 if ( empty( $this->id ) ) {
99 throw new InvalidArgumentException(
100 esc_html__( 'Invalid Feature id returned by ::get_id().', 'ai' )
101 );
102 }
103
104 $metadata = $this->load_metadata();
105 if ( empty( $metadata['label'] ) ) {
106 throw new InvalidArgumentException(
107 esc_html__( 'Feature label is required in load_metadata().', 'ai' )
108 );
109 }
110
111 if ( empty( $metadata['description'] ) ) {
112 throw new InvalidArgumentException(
113 esc_html__( 'Feature description is required in load_metadata().', 'ai' )
114 );
115 }
116
117 if ( empty( $metadata['category'] ) ) {
118 $metadata['category'] = Feature_Category::OTHER;
119 }
120
121 $this->label = $metadata['label'];
122 $this->description = $metadata['description'];
123 $this->category = $metadata['category'];
124 $this->stability = $metadata['stability'] ?? 'experimental';
125 $this->image = $metadata['image'] ?? '';
126 $this->capability = $metadata['capability'] ?? 'text_generation';
127 }
128
129 /**
130 * Loads feature metadata.
131 *
132 * Must return an array with keys: label, description.
133 * Optionally includes: category, stability.
134 *
135 * @since 0.6.0
136 *
137 * @return array{
138 * label: string,
139 * description: string,
140 * category?: string,
141 * stability?: 'deprecated'|'experimental'|'stable',
142 * image?: string,
143 * } Feature metadata.
144 */
145 abstract protected function load_metadata(): array;
146
147 /**
148 * {@inheritDoc}
149 */
150 public function get_label(): string {
151 return $this->label;
152 }
153
154 /**
155 * {@inheritDoc}
156 */
157 public function get_description(): string {
158 return $this->description;
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 public function get_category(): string {
165 return $this->category;
166 }
167
168 /**
169 * {@inheritDoc}
170 */
171 final public function is_globally_enabled(): bool {
172 return (bool) get_option( Settings_Registration::GLOBAL_OPTION, false );
173 }
174
175 /**
176 * {@inheritDoc}
177 */
178 final public function is_individually_enabled(): bool {
179 $feature_enabled = (bool) get_option( "wpai_feature_{$this->id}_enabled", false );
180
181 // @todo remove in v1.0
182 $is_enabled = (bool) apply_filters_deprecated(
183 "ai_experiments_experiment_{$this->id}_enabled",
184 array( $feature_enabled ),
185 '0.6.0',
186 "wpai_feature_{$this->id}_enabled",
187 esc_html__( 'This will be removed in v1.0', 'ai' )
188 );
189
190 /**
191 * Filters the enabled status for a specific feature.
192 *
193 * The dynamic portion of the hook name, `$this->id`, refers to the feature ID.
194 *
195 * @since 0.6.0
196 *
197 * @param bool $feature_enabled Whether the feature is enabled.
198 */
199 return (bool) apply_filters( "wpai_feature_{$this->id}_enabled", $is_enabled );
200 }
201
202 /**
203 * {@inheritDoc}
204 *
205 * Features require both the global toggle and individual
206 * feature toggle to be enabled. Results are cached per
207 * instance to avoid redundant option lookups and filter calls.
208 */
209 final public function is_enabled(): bool {
210 // Return cached result if available.
211 if ( null !== $this->enabled_cache ) {
212 return $this->enabled_cache;
213 }
214
215 // Cache the result.
216 $this->enabled_cache = $this->is_globally_enabled() && $this->is_individually_enabled();
217
218 return $this->enabled_cache;
219 }
220
221 /**
222 * {@inheritDoc}
223 */
224 final public function get_stability(): string {
225 return $this->stability;
226 }
227
228 /**
229 * {@inheritDoc}
230 */
231 public function get_image(): string {
232 return $this->image;
233 }
234
235 /**
236 * {@inheritDoc}
237 */
238 public function get_capability(): string {
239 return $this->capability;
240 }
241
242 /**
243 * Registers feature-specific settings.
244 *
245 * Override this method in child classes to register custom settings options
246 * using WordPress Settings API (register_setting).
247 *
248 * @since 0.6.0
249 *
250 * @return void
251 */
252 public function register_settings(): void {
253 // Default implementation does nothing.
254 // Child classes can override to register custom settings.
255 }
256
257 /**
258 * Gets the field definitions for feature-specific settings.
259 *
260 * Override this method in child classes to declare custom settings fields
261 * that will be rendered as a DataForm on the settings page. Each field
262 * should use the short option name (e.g. 'strategy'), not the full
263 * namespaced option name.
264 *
265 * @since 0.7.0
266 *
267 * @return array<int, array{
268 * id: string,
269 * label: string,
270 * type: string,
271 * default?: mixed,
272 * elements?: list<array{value: string, label: string}>,
273 * isValid?: array{min?: int, max?: int},
274 * }> Array of field definitions matching the DataForm Field shape.
275 */
276 public function get_settings_fields(): array {
277 return array();
278 }
279
280 /**
281 * Gets field definitions with fully resolved option names.
282 *
283 * Transforms the short field IDs from get_settings_fields() into
284 * full WordPress option names suitable for the REST API and frontend.
285 *
286 * @since 0.7.0
287 *
288 * @return array<int, array{
289 * id: string,
290 * label: string,
291 * type: string,
292 * default?: mixed,
293 * elements?: list<array{value: string, label: string}>,
294 * isValid?: array{min?: int, max?: int},
295 * }> Array of field definitions with full option names.
296 */
297 public function get_settings_fields_metadata(): array {
298 $fields = $this->get_settings_fields();
299 foreach ( $fields as &$field ) {
300 $field['id'] = static::get_field_option_name( $field['id'] );
301 }
302 unset( $field );
303 return $fields;
304 }
305
306 /**
307 * Gets the option name for a custom feature setting field.
308 *
309 * Generates a properly namespaced option name for feature-specific settings.
310 * Use this when registering and storing custom settings fields to ensure
311 * consistent naming across the plugin.
312 *
313 * @since 0.6.0
314 *
315 * @param string $option_name The base option name (e.g., 'api_key', 'temperature').
316 * @return string The fully namespaced option name.
317 */
318 final public static function get_field_option_name( string $option_name ): string {
319 return 'wpai_feature_' . static::get_id() . '_field_' . $option_name;
320 }
321
322 /**
323 * {@inheritDoc}
324 *
325 * Must be implemented by child classes to set up hooks and functionality.
326 */
327 abstract public function register(): void;
328 }
329