@@ -43,14 +43,17 @@ function wp_is_connector_registered( string $id ): bool {
4343 * @type string $name The connector's display name.
4444 * @type string $description The connector's description.
4545 * @type string $logo_url Optional. URL to the connector's logo image.
46- * @type string $type The connector type. Currently, only 'ai_provider' is supported .
46+ * @type string $type The connector type, e.g. 'ai_provider'.
4747 * @type array $authentication {
4848 * Authentication configuration. When method is 'api_key', includes
49- * credentials_url and setting_name. When 'none', only method is present.
49+ * credentials_url, setting_name, and optionally constant_name and
50+ * env_var_name. When 'none', only method is present.
5051 *
5152 * @type string $method The authentication method: 'api_key' or 'none'.
5253 * @type string $credentials_url Optional. URL where users can obtain API credentials.
5354 * @type string $setting_name Optional. The setting name for the API key.
55+ * @type string $constant_name Optional. PHP constant name for the API key.
56+ * @type string $env_var_name Optional. Environment variable name for the API key.
5457 * }
5558 * @type array $plugin {
5659 * Optional. Plugin data for install/activate UI.
@@ -62,11 +65,13 @@ function wp_is_connector_registered( string $id ): bool {
6265 * name: non-empty-string,
6366 * description: non-empty-string,
6467 * logo_url?: non-empty-string,
65- * type: 'ai_provider' ,
68+ * type: non-empty-string ,
6669 * authentication: array{
6770 * method: 'api_key'|'none',
6871 * credentials_url?: non-empty-string,
69- * setting_name?: non-empty-string
72+ * setting_name?: non-empty-string,
73+ * constant_name?: non-empty-string,
74+ * env_var_name?: non-empty-string
7075 * },
7176 * plugin?: array{
7277 * slug: non-empty-string
@@ -98,14 +103,17 @@ function wp_get_connector( string $id ): ?array {
98103 * @type string $name The connector's display name.
99104 * @type string $description The connector's description.
100105 * @type string $logo_url Optional. URL to the connector's logo image.
101- * @type string $type The connector type. Currently, only 'ai_provider' is supported .
106+ * @type string $type The connector type, e.g. 'ai_provider'.
102107 * @type array $authentication {
103108 * Authentication configuration. When method is 'api_key', includes
104- * credentials_url and setting_name. When 'none', only method is present.
109+ * credentials_url, setting_name, and optionally constant_name and
110+ * env_var_name. When 'none', only method is present.
105111 *
106112 * @type string $method The authentication method: 'api_key' or 'none'.
107113 * @type string $credentials_url Optional. URL where users can obtain API credentials.
108114 * @type string $setting_name Optional. The setting name for the API key.
115+ * @type string $constant_name Optional. PHP constant name for the API key.
116+ * @type string $env_var_name Optional. Environment variable name for the API key.
109117 * }
110118 * @type array $plugin {
111119 * Optional. Plugin data for install/activate UI.
@@ -118,11 +126,13 @@ function wp_get_connector( string $id ): ?array {
118126 * name: non-empty-string,
119127 * description: non-empty-string,
120128 * logo_url?: non-empty-string,
121- * type: 'ai_provider' ,
129+ * type: non-empty-string ,
122130 * authentication: array{
123131 * method: 'api_key'|'none',
124132 * credentials_url?: non-empty-string,
125- * setting_name?: non-empty-string
133+ * setting_name?: non-empty-string,
134+ * constant_name?: non-empty-string,
135+ * env_var_name?: non-empty-string
126136 * },
127137 * plugin?: array{
128138 * slug: non-empty-string
@@ -216,10 +226,10 @@ function _wp_connectors_init(): void {
216226 * Example — overriding metadata on an auto-discovered connector:
217227 *
218228 * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) {
219- * if ( $registry->is_registered( 'openai ' ) ) {
220- * $connector = $registry->unregister( 'openai ' );
221- * $connector['description'] = __( 'Custom description for OpenAI .', 'my-plugin' );
222- * $registry->register( 'openai ', $connector );
229+ * if ( $registry->is_registered( 'anthropic ' ) ) {
230+ * $connector = $registry->unregister( 'anthropic ' );
231+ * $connector['description'] = __( 'Custom description for Anthropic .', 'my-plugin' );
232+ * $registry->register( 'anthropic ', $connector );
223233 * }
224234 * } );
225235 *
@@ -335,6 +345,26 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re
335345
336346 // Register all default connectors directly on the registry.
337347 foreach ( $ defaults as $ id => $ args ) {
348+ if ( 'api_key ' === $ args ['authentication ' ]['method ' ] ) {
349+ $ sanitized_id = str_replace ( '- ' , '_ ' , $ id );
350+
351+ if ( ! isset ( $ args ['authentication ' ]['setting_name ' ] ) ) {
352+ $ args ['authentication ' ]['setting_name ' ] = "connectors_ai_ {$ sanitized_id }_api_key " ;
353+ }
354+
355+ // All AI providers use the {CONSTANT_CASE_ID}_API_KEY naming convention.
356+ if ( ! isset ( $ args ['authentication ' ]['constant_name ' ] ) || ! isset ( $ args ['authentication ' ]['env_var_name ' ] ) ) {
357+ $ constant_case_key = strtoupper ( preg_replace ( '/([a-z])([A-Z])/ ' , '$1_$2 ' , $ sanitized_id ) ) . '_API_KEY ' ;
358+
359+ if ( ! isset ( $ args ['authentication ' ]['constant_name ' ] ) ) {
360+ $ args ['authentication ' ]['constant_name ' ] = $ constant_case_key ;
361+ }
362+
363+ if ( ! isset ( $ args ['authentication ' ]['env_var_name ' ] ) ) {
364+ $ args ['authentication ' ]['env_var_name ' ] = $ constant_case_key ;
365+ }
366+ }
367+ }
338368 $ registry ->register ( $ id , $ args );
339369 }
340370}
@@ -357,35 +387,32 @@ function _wp_connectors_mask_api_key( string $key ): string {
357387}
358388
359389/**
360- * Determines the source of an API key for a given provider .
390+ * Determines the source of an API key for a given connector .
361391 *
362392 * Checks in order: environment variable, PHP constant, database.
363- * Uses the same naming convention as the WP AI Client ProviderRegistry.
393+ * Environment variable and constant are only checked when their
394+ * respective names are provided.
364395 *
365396 * @since 7.0.0
366397 * @access private
367398 *
368- * @param string $provider_id The provider ID (e.g., 'openai', 'anthropic', 'google').
369- * @param string $setting_name The option name for the API key (e.g., 'connectors_ai_openai_api_key').
399+ * @param string $setting_name The option name for the API key (e.g., 'connectors_spam_filtering_akismet_api_key').
400+ * @param string $env_var_name Optional. Environment variable name to check (e.g., 'AKISMET_API_KEY').
401+ * @param string $constant_name Optional. PHP constant name to check (e.g., 'AKISMET_API_KEY').
370402 * @return string The key source: 'env', 'constant', 'database', or 'none'.
371403 */
372- function _wp_connectors_get_api_key_source ( string $ provider_id , string $ setting_name ): string {
373- // Convert provider ID to CONSTANT_CASE for env var name.
374- // e.g., 'openai' -> 'OPENAI', 'anthropic' -> 'ANTHROPIC'.
375- $ constant_case_id = strtoupper (
376- preg_replace ( '/([a-z])([A-Z])/ ' , '$1_$2 ' , str_replace ( '- ' , '_ ' , $ provider_id ) )
377- );
378- $ env_var_name = "{$ constant_case_id }_API_KEY " ;
379-
404+ function _wp_connectors_get_api_key_source ( string $ setting_name , string $ env_var_name = '' , string $ constant_name = '' ): string {
380405 // Check environment variable first.
381- $ env_value = getenv ( $ env_var_name );
382- if ( false !== $ env_value && '' !== $ env_value ) {
383- return 'env ' ;
406+ if ( '' !== $ env_var_name ) {
407+ $ env_value = getenv ( $ env_var_name );
408+ if ( false !== $ env_value && '' !== $ env_value ) {
409+ return 'env ' ;
410+ }
384411 }
385412
386413 // Check PHP constant.
387- if ( defined ( $ env_var_name ) ) {
388- $ const_value = constant ( $ env_var_name );
414+ if ( '' !== $ constant_name && defined ( $ constant_name ) ) {
415+ $ const_value = constant ( $ constant_name );
389416 if ( is_string ( $ const_value ) && '' !== $ const_value ) {
390417 return 'constant ' ;
391418 }
@@ -470,7 +497,7 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R
470497
471498 foreach ( wp_get_connectors () as $ connector_id => $ connector_data ) {
472499 $ auth = $ connector_data ['authentication ' ];
473- if ( 'ai_provider ' !== $ connector_data [ ' type ' ] || ' api_key ' !== $ auth ['method ' ] || empty ( $ auth ['setting_name ' ] ) ) {
500+ if ( 'api_key ' !== $ auth ['method ' ] || empty ( $ auth ['setting_name ' ] ) ) {
474501 continue ;
475502 }
476503
@@ -481,8 +508,9 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R
481508
482509 $ value = $ data [ $ setting_name ];
483510
484- // On update, validate the key before masking.
485- if ( $ is_update && is_string ( $ value ) && '' !== $ value ) {
511+ // On update, validate AI provider keys before masking.
512+ // Non-AI connectors accept keys as-is; the service plugin handles its own validation.
513+ if ( $ is_update && is_string ( $ value ) && '' !== $ value && 'ai_provider ' === $ connector_data ['type ' ] ) {
486514 if ( true !== _wp_connectors_is_ai_api_key_valid ( $ value , $ connector_id ) ) {
487515 update_option ( $ setting_name , '' );
488516 $ data [ $ setting_name ] = '' ;
@@ -508,16 +536,22 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R
508536 * @access private
509537 */
510538function _wp_register_default_connector_settings (): void {
511- $ ai_registry = AiClient::defaultRegistry ();
539+ $ ai_registry = AiClient::defaultRegistry ();
540+ $ registered_settings = get_registered_settings ();
512541
513542 foreach ( wp_get_connectors () as $ connector_id => $ connector_data ) {
514543 $ auth = $ connector_data ['authentication ' ];
515- if ( 'ai_provider ' !== $ connector_data ['type ' ] || 'api_key ' !== $ auth ['method ' ] || empty ( $ auth ['setting_name ' ] ) ) {
544+ if ( 'api_key ' !== $ auth ['method ' ] || empty ( $ auth ['setting_name ' ] ) ) {
545+ continue ;
546+ }
547+
548+ // Skip if the setting is already registered (e.g. by an owning plugin).
549+ if ( isset ( $ registered_settings [ $ auth ['setting_name ' ] ] ) ) {
516550 continue ;
517551 }
518552
519- // Skip registering the setting if the provider is not in the registry.
520- if ( ! $ ai_registry ->hasProvider ( $ connector_id ) ) {
553+ // For AI providers, skip if the provider is not in the AI Client registry.
554+ if ( ' ai_provider ' === $ connector_data [ ' type ' ] && ! $ ai_registry ->hasProvider ( $ connector_id ) ) {
521555 continue ;
522556 }
523557
@@ -527,13 +561,13 @@ function _wp_register_default_connector_settings(): void {
527561 array (
528562 'type ' => 'string ' ,
529563 'label ' => sprintf (
530- /* translators: %s: AI provider name. */
564+ /* translators: %s: Connector name. */
531565 __ ( '%s API Key ' ),
532566 $ connector_data ['name ' ]
533567 ),
534568 'description ' => sprintf (
535- /* translators: %s: AI provider name. */
536- __ ( 'API key for the %s AI provider . ' ),
569+ /* translators: %s: Connector name. */
570+ __ ( 'API key for the %s connector . ' ),
537571 $ connector_data ['name ' ]
538572 ),
539573 'default ' => '' ,
@@ -569,7 +603,7 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
569603 }
570604
571605 // Skip if the key is already provided via env var or constant.
572- $ key_source = _wp_connectors_get_api_key_source ( $ connector_id , $ auth ['setting_name ' ] );
606+ $ key_source = _wp_connectors_get_api_key_source ( $ auth [ ' setting_name ' ] , $ auth ['env_var_name ' ] ?? '' , $ auth [ ' constant_name ' ] ?? '' );
573607 if ( 'env ' === $ key_source || 'constant ' === $ key_source ) {
574608 continue ;
575609 }
@@ -620,11 +654,17 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array {
620654 if ( 'api_key ' === $ auth ['method ' ] ) {
621655 $ auth_out ['settingName ' ] = $ auth ['setting_name ' ] ?? '' ;
622656 $ auth_out ['credentialsUrl ' ] = $ auth ['credentials_url ' ] ?? null ;
623- $ auth_out ['keySource ' ] = _wp_connectors_get_api_key_source ( $ connector_id , $ auth ['setting_name ' ] ?? '' );
624- try {
625- $ auth_out ['isConnected ' ] = $ registry ->hasProvider ( $ connector_id ) && $ registry ->isProviderConfigured ( $ connector_id );
626- } catch ( Exception $ e ) {
627- $ auth_out ['isConnected ' ] = false ;
657+ $ key_source = _wp_connectors_get_api_key_source ( $ auth ['setting_name ' ] ?? '' , $ auth ['env_var_name ' ] ?? '' , $ auth ['constant_name ' ] ?? '' );
658+ $ auth_out ['keySource ' ] = $ key_source ;
659+
660+ if ( 'ai_provider ' === $ connector_data ['type ' ] ) {
661+ try {
662+ $ auth_out ['isConnected ' ] = $ registry ->hasProvider ( $ connector_id ) && $ registry ->isProviderConfigured ( $ connector_id );
663+ } catch ( Exception $ e ) {
664+ $ auth_out ['isConnected ' ] = false ;
665+ }
666+ } else {
667+ $ auth_out ['isConnected ' ] = 'none ' !== $ key_source ;
628668 }
629669 }
630670
@@ -645,7 +685,9 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array {
645685
646686 $ connector_out ['plugin ' ] = array (
647687 'slug ' => $ plugin_slug ,
648- 'isInstalled ' => $ is_installed ,
688+ 'pluginFile ' => $ is_installed
689+ ? ( str_ends_with ( $ plugin_file , '.php ' ) ? substr ( $ plugin_file , 0 , -4 ) : $ plugin_file )
690+ : null ,
649691 'isActivated ' => $ is_activated ,
650692 );
651693 }
0 commit comments