Skip to content

Commit b78d7dd

Browse files
gzioloclaude
andcommitted
Connectors: Restructure provider data with authentication sub-object and expose to script module.
Polyfills WordPress/gutenberg#76014: - Rename _wp_connectors_get_provider_settings() to _wp_connectors_get_connector_settings() with a restructured return format: type field, authentication sub-object (method, credentials_url, setting_name) replacing the nested settings array. - Include all registered providers regardless of auth method (api_key or none) instead of filtering out non-API-key providers. - Move label, description, and sanitize logic into consumer functions. - Add ucwords() fallback for providers with no name. - Add _wp_connectors_get_connector_script_module_data() to expose connector data to the connectors-wp-admin script module. - Update all consumer functions and tests to use the new structure. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 300afe4 commit b78d7dd

2 files changed

Lines changed: 235 additions & 170 deletions

File tree

src/wp-includes/connectors.php

Lines changed: 175 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -109,120 +109,115 @@ function _wp_connectors_get_real_api_key( string $option_name, callable $mask_ca
109109
}
110110

111111
/**
112-
* Gets the registered connector provider settings.
112+
* Gets the registered connector settings.
113113
*
114114
* @since 7.0.0
115115
* @access private
116116
*
117117
* @return array {
118-
* Provider settings keyed by provider ID.
118+
* Connector settings keyed by connector ID.
119119
*
120120
* @type array ...$0 {
121-
* Data for a single provider.
121+
* Data for a single connector.
122122
*
123-
* @type string $name The provider's display name.
124-
* @type string $description The provider's description.
125-
* @type string|null $credentials_url URL where users can obtain API credentials.
126-
* @type array $settings {
127-
* Settings keyed by setting name.
123+
* @type string $name The connector's display name.
124+
* @type string $description The connector's description.
125+
* @type string $type The connector type: 'ai_provider'.
126+
* @type array $authentication {
127+
* Authentication configuration. When method is 'api_key', includes
128+
* credentials_url and setting_name. When 'none', only method is present.
128129
*
129-
* @type array ...$0 {
130-
* Data for a single setting.
131-
*
132-
* @type string $label The setting label.
133-
* @type string $description The setting description.
134-
* @type callable $sanitize Callback to sanitize the input value.
135-
* }
130+
* @type string $method The authentication method: 'api_key' or 'none'.
131+
* @type string|null $credentials_url Optional. URL where users can obtain API credentials.
132+
* @type string $setting_name Optional. The setting name for the API key.
136133
* }
137134
* }
138135
* }
139136
*/
140-
function _wp_connectors_get_provider_settings(): array {
141-
$providers = array(
137+
function _wp_connectors_get_connector_settings(): array {
138+
$connectors = array(
142139
'google' => array(
143-
'name' => 'Gemini',
144-
'description' => __( 'Content generation, translation, and vision with Google\'s Gemini.' ),
145-
'credentials_url' => 'https://aistudio.google.com/',
140+
'name' => 'Gemini',
141+
'description' => __( 'Content generation, translation, and vision with Google\'s Gemini.' ),
142+
'type' => 'ai_provider',
143+
'authentication' => array(
144+
'method' => 'api_key',
145+
'credentials_url' => 'https://aistudio.google.com/api-keys',
146+
),
146147
),
147148
'openai' => array(
148-
'name' => 'OpenAI',
149-
'description' => __( 'Text, image, and code generation with GPT and DALL-E.' ),
150-
'credentials_url' => 'https://platform.openai.com/',
149+
'name' => 'OpenAI',
150+
'description' => __( 'Text, image, and code generation with GPT and DALL-E.' ),
151+
'type' => 'ai_provider',
152+
'authentication' => array(
153+
'method' => 'api_key',
154+
'credentials_url' => 'https://platform.openai.com/api-keys',
155+
),
151156
),
152157
'anthropic' => array(
153-
'name' => 'Claude',
154-
'description' => __( 'Writing, research, and analysis with Claude.' ),
155-
'credentials_url' => 'https://console.anthropic.com/',
158+
'name' => 'Claude',
159+
'description' => __( 'Writing, research, and analysis with Claude.' ),
160+
'type' => 'ai_provider',
161+
'authentication' => array(
162+
'method' => 'api_key',
163+
'credentials_url' => 'https://platform.claude.com/settings/keys',
164+
),
156165
),
157166
);
158167

159168
$registry = AiClient::defaultRegistry();
160169

161-
foreach ( $registry->getRegisteredProviderIds() as $provider_id ) {
162-
$provider_class_name = $registry->getProviderClassName( $provider_id );
170+
foreach ( $registry->getRegisteredProviderIds() as $connector_id ) {
171+
$provider_class_name = $registry->getProviderClassName( $connector_id );
163172
$provider_metadata = $provider_class_name::metadata();
164173

165174
$auth_method = $provider_metadata->getAuthenticationMethod();
166-
if ( null === $auth_method || ! $auth_method->isApiKey() ) {
167-
continue;
175+
$is_api_key = null !== $auth_method && $auth_method->isApiKey();
176+
177+
if ( $is_api_key ) {
178+
$credentials_url = $provider_metadata->getCredentialsUrl();
179+
$authentication = array(
180+
'method' => 'api_key',
181+
'credentials_url' => $credentials_url ? $credentials_url : null,
182+
);
183+
} else {
184+
$authentication = array( 'method' => 'none' );
168185
}
169186

170-
$registry_data = array_filter(
171-
array(
172-
'name' => $provider_metadata->getName(),
173-
'credentials_url' => $provider_metadata->getCredentialsUrl(),
174-
)
175-
);
187+
$name = $provider_metadata->getName();
188+
$description = method_exists( $provider_metadata, 'getDescription' ) ? $provider_metadata->getDescription() : null;
176189

177-
if ( isset( $providers[ $provider_id ] ) ) {
178-
// Merge non-empty registry data over hardcoded fallbacks.
179-
$providers[ $provider_id ] = array_merge( $providers[ $provider_id ], $registry_data );
190+
if ( isset( $connectors[ $connector_id ] ) ) {
191+
// Override fields with non-empty registry values.
192+
if ( $name ) {
193+
$connectors[ $connector_id ]['name'] = $name;
194+
}
195+
if ( $description ) {
196+
$connectors[ $connector_id ]['description'] = $description;
197+
}
198+
// Always update auth method; keep existing credentials_url as fallback.
199+
$connectors[ $connector_id ]['authentication']['method'] = $authentication['method'];
200+
if ( ! empty( $authentication['credentials_url'] ) ) {
201+
$connectors[ $connector_id ]['authentication']['credentials_url'] = $authentication['credentials_url'];
202+
}
180203
} else {
181-
$providers[ $provider_id ] = array_merge(
182-
array(
183-
'name' => '',
184-
'description' => '',
185-
'credentials_url' => null,
186-
),
187-
$registry_data
204+
$connectors[ $connector_id ] = array(
205+
'name' => $name ? $name : ucwords( $connector_id ),
206+
'description' => $description ? $description : '',
207+
'type' => 'ai_provider',
208+
'authentication' => $authentication,
188209
);
189210
}
190211
}
191212

192-
$provider_settings = array();
193-
foreach ( $providers as $provider => $data ) {
194-
$setting_name = "connectors_ai_{$provider}_api_key";
195-
196-
$provider_settings[ $provider ] = array(
197-
'name' => $data['name'],
198-
'description' => $data['description'],
199-
'credentials_url' => $data['credentials_url'],
200-
'settings' => array(
201-
$setting_name => array(
202-
'label' => sprintf(
203-
/* translators: %s: AI provider name. */
204-
__( '%s API Key' ),
205-
$data['name']
206-
),
207-
'description' => sprintf(
208-
/* translators: %s: AI provider name. */
209-
__( 'API key for the %s AI provider.' ),
210-
$data['name']
211-
),
212-
'sanitize' => static function ( string $value ) use ( $provider ): string {
213-
$value = sanitize_text_field( $value );
214-
if ( '' === $value ) {
215-
return $value;
216-
}
217-
218-
$valid = _wp_connectors_is_api_key_valid( $value, $provider );
219-
return true === $valid ? $value : '';
220-
},
221-
),
222-
),
223-
);
213+
// Add setting_name for connectors that use API key authentication.
214+
foreach ( $connectors as $connector_id => $connector ) {
215+
if ( 'api_key' === $connector['authentication']['method'] ) {
216+
$connectors[ $connector_id ]['authentication']['setting_name'] = "connectors_ai_{$connector_id}_api_key";
217+
}
224218
}
225-
return $provider_settings;
219+
220+
return $connectors;
226221
}
227222

228223
/**
@@ -246,10 +241,6 @@ function _wp_connectors_validate_keys_in_rest( WP_REST_Response $response, WP_RE
246241
return $response;
247242
}
248243

249-
if ( ! class_exists( '\WordPress\AiClient\AiClient' ) ) {
250-
return $response;
251-
}
252-
253244
$fields = $request->get_param( '_fields' );
254245
if ( ! $fields ) {
255246
return $response;
@@ -266,20 +257,24 @@ function _wp_connectors_validate_keys_in_rest( WP_REST_Response $response, WP_RE
266257
return $response;
267258
}
268259

269-
foreach ( _wp_connectors_get_provider_settings() as $provider => $provider_data ) {
270-
foreach ( $provider_data['settings'] as $setting_name => $config ) {
271-
if ( ! in_array( $setting_name, $requested, true ) ) {
272-
continue;
273-
}
260+
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
261+
$auth = $connector_data['authentication'];
262+
if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
263+
continue;
264+
}
274265

275-
$real_key = _wp_connectors_get_real_api_key( $setting_name, '_wp_connectors_mask_api_key' );
276-
if ( '' === $real_key ) {
277-
continue;
278-
}
266+
$setting_name = $auth['setting_name'];
267+
if ( ! in_array( $setting_name, $requested, true ) ) {
268+
continue;
269+
}
279270

280-
if ( true !== _wp_connectors_is_api_key_valid( $real_key, $provider ) ) {
281-
$data[ $setting_name ] = 'invalid_key';
282-
}
271+
$real_key = _wp_connectors_get_real_api_key( $setting_name, '_wp_connectors_mask_api_key' );
272+
if ( '' === $real_key ) {
273+
continue;
274+
}
275+
276+
if ( true !== _wp_connectors_is_api_key_valid( $real_key, $connector_id ) ) {
277+
$data[ $setting_name ] = 'invalid_key';
283278
}
284279
}
285280

@@ -295,26 +290,42 @@ function _wp_connectors_validate_keys_in_rest( WP_REST_Response $response, WP_RE
295290
* @access private
296291
*/
297292
function _wp_register_default_connector_settings(): void {
298-
if ( ! class_exists( '\WordPress\AiClient\AiClient' ) ) {
299-
return;
300-
}
301-
302-
foreach ( _wp_connectors_get_provider_settings() as $provider_data ) {
303-
foreach ( $provider_data['settings'] as $setting_name => $config ) {
304-
register_setting(
305-
'connectors',
306-
$setting_name,
307-
array(
308-
'type' => 'string',
309-
'label' => $config['label'],
310-
'description' => $config['description'],
311-
'default' => '',
312-
'show_in_rest' => true,
313-
'sanitize_callback' => $config['sanitize'],
314-
)
315-
);
316-
add_filter( "option_{$setting_name}", '_wp_connectors_mask_api_key' );
293+
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
294+
$auth = $connector_data['authentication'];
295+
if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
296+
continue;
317297
}
298+
299+
$setting_name = $auth['setting_name'];
300+
register_setting(
301+
'connectors',
302+
$setting_name,
303+
array(
304+
'type' => 'string',
305+
'label' => sprintf(
306+
/* translators: %s: AI provider name. */
307+
__( '%s API Key' ),
308+
$connector_data['name']
309+
),
310+
'description' => sprintf(
311+
/* translators: %s: AI provider name. */
312+
__( 'API key for the %s AI provider.' ),
313+
$connector_data['name']
314+
),
315+
'default' => '',
316+
'show_in_rest' => true,
317+
'sanitize_callback' => static function ( string $value ) use ( $connector_id ): string {
318+
$value = sanitize_text_field( $value );
319+
if ( '' === $value ) {
320+
return $value;
321+
}
322+
323+
$valid = _wp_connectors_is_api_key_valid( $value, $connector_id );
324+
return true === $valid ? $value : '';
325+
},
326+
)
327+
);
328+
add_filter( "option_{$setting_name}", '_wp_connectors_mask_api_key' );
318329
}
319330
}
320331
add_action( 'init', '_wp_register_default_connector_settings' );
@@ -326,26 +337,62 @@ function _wp_register_default_connector_settings(): void {
326337
* @access private
327338
*/
328339
function _wp_connectors_pass_default_keys_to_ai_client(): void {
329-
if ( ! class_exists( '\WordPress\AiClient\AiClient' ) ) {
330-
return;
331-
}
332340
try {
333341
$registry = AiClient::defaultRegistry();
334-
foreach ( _wp_connectors_get_provider_settings() as $provider => $provider_data ) {
335-
foreach ( $provider_data['settings'] as $setting_name => $config ) {
336-
$api_key = _wp_connectors_get_real_api_key( $setting_name, '_wp_connectors_mask_api_key' );
337-
if ( '' === $api_key || ! $registry->hasProvider( $provider ) ) {
338-
continue;
339-
}
340-
341-
$registry->setProviderRequestAuthentication(
342-
$provider,
343-
new ApiKeyRequestAuthentication( $api_key )
344-
);
342+
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
343+
if ( 'ai_provider' !== $connector_data['type'] ) {
344+
continue;
345+
}
346+
347+
$auth = $connector_data['authentication'];
348+
if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
349+
continue;
345350
}
351+
352+
$api_key = _wp_connectors_get_real_api_key( $auth['setting_name'], '_wp_connectors_mask_api_key' );
353+
if ( '' === $api_key || ! $registry->hasProvider( $connector_id ) ) {
354+
continue;
355+
}
356+
357+
$registry->setProviderRequestAuthentication(
358+
$connector_id,
359+
new ApiKeyRequestAuthentication( $api_key )
360+
);
346361
}
347362
} catch ( Exception $e ) {
348363
wp_trigger_error( __FUNCTION__, $e->getMessage() );
349364
}
350365
}
351366
add_action( 'init', '_wp_connectors_pass_default_keys_to_ai_client' );
367+
368+
/**
369+
* Exposes connector settings to the connectors-wp-admin script module.
370+
*
371+
* @since 7.0.0
372+
* @access private
373+
*
374+
* @param array $data Existing script module data.
375+
* @return array Script module data with connectors added.
376+
*/
377+
function _wp_connectors_get_connector_script_module_data( array $data ): array {
378+
$connectors = array();
379+
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
380+
$auth = $connector_data['authentication'];
381+
$auth_out = array( 'method' => $auth['method'] );
382+
383+
if ( 'api_key' === $auth['method'] ) {
384+
$auth_out['settingName'] = $auth['setting_name'] ?? '';
385+
$auth_out['credentialsUrl'] = $auth['credentials_url'] ?? null;
386+
}
387+
388+
$connectors[ $connector_id ] = array(
389+
'name' => $connector_data['name'],
390+
'description' => $connector_data['description'],
391+
'type' => $connector_data['type'],
392+
'authentication' => $auth_out,
393+
);
394+
}
395+
$data['connectors'] = $connectors;
396+
return $data;
397+
}
398+
add_filter( 'script_module_data_connectors-wp-admin', '_wp_connectors_get_connector_script_module_data' );

0 commit comments

Comments
 (0)