PluginProbe
Gutenberg / 23.5.2
Gutenberg v23.5.2
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / compat / wordpress-7.0 / class-wp-connector-registry.php

class-wp-connector-registry.php in Gutenberg 23.5.2, at lib/compat/wordpress-7.0/class-wp-connector-registry.php

404 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Connectors API
4 *
5 * Defines WP_Connector_Registry class.
6 *
7 * @package gutenberg
8 * @since 7.0.0
9 */
10
11 if ( ! class_exists( 'WP_Connector_Registry' ) ) {
12 /**
13 * Manages the registration and lookup of connectors.
14 *
15 * @since 7.0.0
16 * @access private
17 *
18 * @phpstan-type Connector array{
19 * name: non-empty-string,
20 * description: string,
21 * logo_url?: non-empty-string,
22 * type: non-empty-string,
23 * authentication: array{
24 * method: 'api_key'|'none',
25 * credentials_url?: non-empty-string,
26 * setting_name?: non-empty-string,
27 * constant_name?: non-empty-string,
28 * env_var_name?: non-empty-string
29 * },
30 * plugin: array{
31 * file?: non-empty-string,
32 * is_active: callable(): bool
33 * }
34 * }
35 */
36 final class WP_Connector_Registry {
37 /**
38 * The singleton instance of the registry.
39 *
40 * @since 7.0.0
41 */
42 private static ?WP_Connector_Registry $instance = null;
43
44 /**
45 * Holds the registered connectors.
46 *
47 * Each connector is stored as an associative array with keys:
48 * name, description, type, authentication, and optionally plugin.
49 *
50 * @since 7.0.0
51 * @var array<string, array>
52 * @phpstan-var array<string, Connector>
53 */
54 private array $registered_connectors = array();
55
56 /**
57 * Registers a new connector.
58 *
59 * Validates the provided arguments and stores the connector in the registry.
60 * For connectors with `api_key` authentication, a `setting_name` can be provided
61 * explicitly. If omitted, one is automatically generated using the pattern
62 * `connectors_{$type}_{$id}_api_key`, with hyphens in the type and ID normalized
63 * to underscores (e.g., connector type `spam_filtering` with ID `akismet` produces
64 * `connectors_spam_filtering_akismet_api_key`). This setting name is used for the
65 * Settings API registration and REST API exposure.
66 *
67 * Registering a connector with an ID that is already registered will trigger a
68 * `_doing_it_wrong()` notice and return `null`. To override an existing connector,
69 * call `unregister()` first.
70 *
71 * @since 7.0.0
72 *
73 * @see WP_Connector_Registry::unregister()
74 *
75 * @param string $id The unique connector identifier. Must match the pattern
76 * `/^[a-z0-9_-]+$/` (lowercase alphanumeric, hyphens, and underscores only).
77 * @param array $args {
78 * An associative array of arguments for the connector.
79 *
80 * @type string $name Required. The connector's display name.
81 * @type string $description Optional. The connector's description. Default empty string.
82 * @type string $logo_url Optional. URL to the connector's logo image.
83 * @type string $type Required. The connector type, e.g. 'ai_provider'.
84 * @type array $authentication {
85 * Required. Authentication configuration.
86 *
87 * @type string $method Required. The authentication method: 'api_key' or 'none'.
88 * @type string $credentials_url Optional. URL where users can obtain API credentials.
89 * @type string $setting_name Optional. The setting name for the API key.
90 * When omitted, auto-generated as
91 * `connectors_{$type}_{$id}_api_key`.
92 * Must be a non-empty string when provided.
93 * @type string $constant_name Optional. PHP constant name for the API key
94 * (e.g. 'ANTHROPIC_API_KEY'). Only checked when provided.
95 * @type string $env_var_name Optional. Environment variable name for the API key
96 * (e.g. 'ANTHROPIC_API_KEY'). Only checked when provided.
97 * }
98 * @type array $plugin {
99 * Optional. Plugin data for install/activate UI.
100 *
101 * @type string $file Optional. The plugin's main file path relative to the
102 * plugins directory (e.g. 'my-plugin/my-plugin.php' or
103 * 'hello.php').
104 * @type callable $is_active Optional callback to determine whether the plugin
105 * is active. Receives no arguments and must return bool.
106 * Defaults to `__return_true`.
107 * }
108 * }
109 * @return array|null The registered connector data on success, null on failure.
110 *
111 * @phpstan-param array{
112 * name: non-empty-string,
113 * description?: string,
114 * logo_url?: non-empty-string,
115 * type: non-empty-string,
116 * authentication: array{
117 * method: 'api_key'|'none',
118 * credentials_url?: non-empty-string,
119 * setting_name?: non-empty-string,
120 * constant_name?: non-empty-string,
121 * env_var_name?: non-empty-string
122 * },
123 * plugin?: array{
124 * file?: non-empty-string,
125 * is_active?: callable(): bool
126 * }
127 * } $args
128 * @phpstan-return Connector|null
129 */
130 public function register( string $id, array $args ): ?array {
131 if ( ! preg_match( '/^[a-z0-9_-]+$/', $id ) ) {
132 _doing_it_wrong(
133 __METHOD__,
134 __(
135 'Connector ID must contain only lowercase alphanumeric characters, hyphens, and underscores.'
136 ),
137 '7.0.0'
138 );
139 return null;
140 }
141
142 if ( $this->is_registered( $id ) ) {
143 _doing_it_wrong(
144 __METHOD__,
145 /* translators: %s: Connector ID. */
146 sprintf( __( 'Connector "%s" is already registered.' ), esc_html( $id ) ),
147 '7.0.0'
148 );
149 return null;
150 }
151
152 // Validate required fields.
153 if ( empty( $args['name'] ) || ! is_string( $args['name'] ) ) {
154 _doing_it_wrong(
155 __METHOD__,
156 /* translators: %s: Connector ID. */
157 sprintf( __( 'Connector "%s" requires a non-empty "name" string.' ), esc_html( $id ) ),
158 '7.0.0'
159 );
160 return null;
161 }
162
163 if ( empty( $args['type'] ) || ! is_string( $args['type'] ) ) {
164 _doing_it_wrong(
165 __METHOD__,
166 /* translators: %s: Connector ID. */
167 sprintf( __( 'Connector "%s" requires a non-empty "type" string.' ), esc_html( $id ) ),
168 '7.0.0'
169 );
170 return null;
171 }
172
173 if ( ! isset( $args['authentication'] ) || ! is_array( $args['authentication'] ) ) {
174 _doing_it_wrong(
175 __METHOD__,
176 /* translators: %s: Connector ID. */
177 sprintf( __( 'Connector "%s" requires an "authentication" array.' ), esc_html( $id ) ),
178 '7.0.0'
179 );
180 return null;
181 }
182
183 if ( empty( $args['authentication']['method'] ) || ! in_array( $args['authentication']['method'], array( 'api_key', 'none' ), true ) ) {
184 _doing_it_wrong(
185 __METHOD__,
186 /* translators: %s: Connector ID. */
187 sprintf( __( 'Connector "%s" authentication method must be "api_key" or "none".' ), esc_html( $id ) ),
188 '7.0.0'
189 );
190 return null;
191 }
192
193 if ( 'ai_provider' === $args['type'] && ! class_exists( '\WordPress\AiClient\AiClient' ) ) {
194 // No need for a doing_it_wrong as AI support is disabled intentionally.
195 return null;
196 }
197
198 $connector = array(
199 'name' => $args['name'],
200 'description' => isset( $args['description'] ) && is_string( $args['description'] ) ? $args['description'] : '',
201 'type' => $args['type'],
202 'authentication' => array(
203 'method' => $args['authentication']['method'],
204 ),
205 );
206
207 if ( ! empty( $args['logo_url'] ) && is_string( $args['logo_url'] ) ) {
208 $connector['logo_url'] = $args['logo_url'];
209 }
210
211 if ( 'api_key' === $args['authentication']['method'] ) {
212 if ( ! empty( $args['authentication']['credentials_url'] ) && is_string( $args['authentication']['credentials_url'] ) ) {
213 $connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'];
214 }
215 if ( isset( $args['authentication']['setting_name'] ) ) {
216 if ( ! is_string( $args['authentication']['setting_name'] ) || '' === $args['authentication']['setting_name'] ) {
217 _doing_it_wrong(
218 __METHOD__,
219 /* translators: %s: Connector ID. */
220 sprintf( __( 'Connector "%s" authentication setting_name must be a non-empty string.' ), esc_html( $id ) ),
221 '7.0.0'
222 );
223 return null;
224 }
225 $connector['authentication']['setting_name'] = $args['authentication']['setting_name'];
226 } else {
227 $connector['authentication']['setting_name'] = str_replace( '-', '_', "connectors_{$connector['type']}_{$id}_api_key" );
228 }
229 if ( isset( $args['authentication']['constant_name'] ) ) {
230 if ( ! is_string( $args['authentication']['constant_name'] ) || '' === $args['authentication']['constant_name'] ) {
231 _doing_it_wrong(
232 __METHOD__,
233 /* translators: %s: Connector ID. */
234 sprintf( __( 'Connector "%s" authentication constant_name must be a non-empty string.' ), esc_html( $id ) ),
235 '7.0.0'
236 );
237 return null;
238 }
239 $connector['authentication']['constant_name'] = $args['authentication']['constant_name'];
240 }
241 if ( isset( $args['authentication']['env_var_name'] ) ) {
242 if ( ! is_string( $args['authentication']['env_var_name'] ) || '' === $args['authentication']['env_var_name'] ) {
243 _doing_it_wrong(
244 __METHOD__,
245 /* translators: %s: Connector ID. */
246 sprintf( __( 'Connector "%s" authentication env_var_name must be a non-empty string.' ), esc_html( $id ) ),
247 '7.0.0'
248 );
249 return null;
250 }
251 $connector['authentication']['env_var_name'] = $args['authentication']['env_var_name'];
252 }
253 }
254
255 $connector['plugin'] = array();
256
257 if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {
258 if ( ! empty( $args['plugin']['file'] ) ) {
259 $connector['plugin']['file'] = $args['plugin']['file'];
260 }
261
262 if ( isset( $args['plugin']['is_active'] ) ) {
263 if ( ! is_callable( $args['plugin']['is_active'] ) ) {
264 _doing_it_wrong(
265 __METHOD__,
266 /* translators: %s: Connector ID. */
267 sprintf( __( 'Connector "%s" plugin is_active must be callable.' ), esc_html( $id ) ),
268 '7.0.0'
269 );
270 return null;
271 }
272
273 $connector['plugin']['is_active'] = $args['plugin']['is_active'];
274 }
275 }
276
277 if ( ! isset( $connector['plugin']['is_active'] ) ) {
278 $connector['plugin']['is_active'] = '__return_true';
279 }
280
281 $this->registered_connectors[ $id ] = $connector;
282 return $connector;
283 }
284
285 /**
286 * Unregisters a connector.
287 *
288 * @since 7.0.0
289 *
290 * @param string $id The connector identifier.
291 * @return array|null The unregistered connector data on success, null on failure.
292 *
293 * @phpstan-return Connector|null
294 */
295 public function unregister( string $id ): ?array {
296 if ( ! $this->is_registered( $id ) ) {
297 _doing_it_wrong(
298 __METHOD__,
299 /* translators: %s: Connector ID. */
300 sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
301 '7.0.0'
302 );
303 return null;
304 }
305
306 $unregistered = $this->registered_connectors[ $id ];
307 unset( $this->registered_connectors[ $id ] );
308
309 return $unregistered;
310 }
311
312 /**
313 * Retrieves the list of all registered connectors.
314 *
315 * Do not use this method directly. Instead, use the `wp_get_connectors()` function.
316 *
317 * @since 7.0.0
318 *
319 * @see wp_get_connectors()
320 *
321 * @return array<string, array> The array of registered connectors keyed by connector ID.
322 *
323 * @phpstan-return array<string, Connector>
324 */
325 public function get_all_registered(): array {
326 return $this->registered_connectors;
327 }
328
329 /**
330 * Checks if a connector is registered.
331 *
332 * Do not use this method directly. Instead, use the `wp_is_connector_registered()` function.
333 *
334 * @since 7.0.0
335 *
336 * @see wp_is_connector_registered()
337 *
338 * @param string $id The connector identifier.
339 * @return bool True if the connector is registered, false otherwise.
340 */
341 public function is_registered( string $id ): bool {
342 return isset( $this->registered_connectors[ $id ] );
343 }
344
345 /**
346 * Retrieves a registered connector.
347 *
348 * Do not use this method directly. Instead, use the `wp_get_connector()` function.
349 *
350 * @since 7.0.0
351 *
352 * @see wp_get_connector()
353 *
354 * @param string $id The connector identifier.
355 * @return array|null The registered connector data, or null if it is not registered.
356 * @phpstan-return Connector|null
357 */
358 public function get_registered( string $id ): ?array {
359 if ( ! $this->is_registered( $id ) ) {
360 _doing_it_wrong(
361 __METHOD__,
362 /* translators: %s: Connector ID. */
363 sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
364 '7.0.0'
365 );
366 return null;
367 }
368 return $this->registered_connectors[ $id ];
369 }
370
371 /**
372 * Retrieves the main instance of the registry class.
373 *
374 * @since 7.0.0
375 *
376 * @return WP_Connector_Registry|null The main registry instance, or null if not yet initialized.
377 */
378 public static function get_instance(): ?self {
379 return self::$instance;
380 }
381
382 /**
383 * Sets the main instance of the registry class.
384 *
385 * @since 7.0.0
386 * @access private
387 *
388 * @param WP_Connector_Registry $registry The registry instance.
389 */
390 public static function set_instance( WP_Connector_Registry $registry ): void {
391 if ( ! doing_action( 'init' ) ) {
392 _doing_it_wrong(
393 __METHOD__,
394 __( 'The connector registry instance must be set during the <code>init</code> action.' ),
395 '7.0.0'
396 );
397 return;
398 }
399
400 self::$instance = $registry;
401 }
402 }
403 }
404