@@ -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 */
297292function _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}
320331add_action ( 'init ' , '_wp_register_default_connector_settings ' );
@@ -326,26 +337,62 @@ function _wp_register_default_connector_settings(): void {
326337 * @access private
327338 */
328339function _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}
351366add_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