-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathAbstract_Experiment.php
More file actions
233 lines (207 loc) · 5.57 KB
/
Abstract_Experiment.php
File metadata and controls
233 lines (207 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<?php
/**
* Abstract Experiment base class.
*
* @package WordPress\AI\Abstracts
*/
declare( strict_types=1 );
namespace WordPress\AI\Abstracts;
use WordPress\AI\Contracts\Experiment;
use WordPress\AI\Exception\Invalid_Experiment_Metadata_Exception;
use WordPress\AI\Settings\Settings_Registration;
/**
* Base implementation for experiments.
*
* Provides common functionality for all experiments including enable/disable state.
*
* @since 0.1.0
*/
abstract class Abstract_Experiment implements Experiment {
/**
* Experiment identifier.
*
* @since 0.1.0
* @var string
*/
protected string $id;
/**
* Experiment label.
*
* @since 0.1.0
* @var string
*/
protected string $label;
/**
* Experiment description.
*
* @since 0.1.0
* @var string
*/
protected string $description;
/**
* Cache for this experiment's enabled status.
*
* @since 0.1.0
* @var bool|null
*/
private ?bool $enabled_cache = null;
/**
* Constructor.
*
* Loads experiment metadata and initializes properties.
*
* @since 0.1.0
*
* @throws \WordPress\AI\Exception\Invalid_Experiment_Metadata_Exception If experiment metadata is invalid.
*/
final public function __construct() {
$metadata = $this->load_experiment_metadata();
if ( empty( $metadata['id'] ) ) {
throw new Invalid_Experiment_Metadata_Exception(
esc_html__( 'Experiment id is required in load_experiment_metadata().', 'ai' )
);
}
if ( empty( $metadata['label'] ) ) {
throw new Invalid_Experiment_Metadata_Exception(
esc_html__( 'Experiment label is required in load_experiment_metadata().', 'ai' )
);
}
if ( empty( $metadata['description'] ) ) {
throw new Invalid_Experiment_Metadata_Exception(
esc_html__( 'Experiment description is required in load_experiment_metadata().', 'ai' )
);
}
$this->id = $metadata['id'];
$this->label = $metadata['label'];
$this->description = $metadata['description'];
}
/**
* Loads experiment metadata.
*
* Must return an array with keys: id, label, description.
*
* @since 0.1.0
*
* @return array{id: string, label: string, description: string} Experiment metadata.
*/
abstract protected function load_experiment_metadata(): array;
/**
* Gets the experiment ID.
*
* @since 0.1.0
*
* @return string Experiment identifier.
*/
public function get_id(): string {
return $this->id;
}
/**
* Gets the experiment label.
*
* @since 0.1.0
*
* @return string Translated experiment label.
*/
public function get_label(): string {
return $this->label;
}
/**
* Gets the experiment description.
*
* @since 0.1.0
*
* @return string Translated experiment description.
*/
public function get_description(): string {
return $this->description;
}
/**
* Checks if experiment is enabled.
*
* Experiments require both the global toggle and individual experiment toggle to be enabled.
* Results are cached per instance to avoid redundant option lookups and filter calls.
*
* @since 0.1.0
*
* @return bool True if enabled, false otherwise.
*/
final public function is_enabled(): bool {
// Return cached result if available.
if ( null !== $this->enabled_cache ) {
return $this->enabled_cache;
}
// Check global experiments toggle first.
$global_enabled = (bool) get_option( Settings_Registration::GLOBAL_OPTION, false );
if ( ! $global_enabled ) {
$this->enabled_cache = false;
return false;
}
// Check experiment-specific option.
$experiment_enabled = (bool) get_option( "ai_experiment_{$this->id}_enabled", false );
/**
* Filters the enabled status for a specific experiment.
*
* The dynamic portion of the hook name, `$this->id`, refers to the experiment ID.
*
* @since 0.1.0
*
* @param bool $experiment_enabled Whether the experiment is enabled.
*/
$is_enabled = (bool) apply_filters( "ai_experiments_experiment_{$this->id}_enabled", $experiment_enabled );
// Cache the result.
$this->enabled_cache = $is_enabled;
return $is_enabled;
}
/**
* Registers experiment-specific settings.
*
* Override this method in child classes to register custom settings options
* using WordPress Settings API (register_setting).
*
* @since 0.1.0
*
* @return void
*/
public function register_settings(): void {
// Default implementation does nothing.
// Child classes can override to register custom settings.
}
/**
* Renders experiment-specific settings fields.
*
* Override this method in child classes to render custom settings UI
* that will appear within the experiment's card on the settings page.
* This is called after the experiment's main toggle control.
*
* @since 0.1.0
*
* @return void
*/
public function render_settings_fields(): void {
// Default implementation does nothing.
// Child classes can override to render custom settings UI.
}
/**
* Gets the option name for a custom experiment setting field.
*
* Generates a properly namespaced option name for experiment-specific settings.
* Use this when registering and rendering custom settings fields to ensure
* consistent naming across the plugin.
*
* @since 0.1.0
*
* @param string $option_name The base option name (e.g., 'api_key', 'temperature').
* @return string The fully namespaced option name.
*/
final protected function get_field_option_name( string $option_name ): string {
return "ai_experiment_{$this->id}_field_{$option_name}";
}
/**
* Initializes the experiment.
*
* Must be implemented by child classes to set up hooks and functionality.
*
* @since 0.1.0
*/
abstract public function init(): void;
}