@@ -58,13 +58,15 @@ function wp_is_connector_registered( string $id ): bool {
5858 * @type array $plugin {
5959 * Optional. Plugin data for install/activate UI.
6060 *
61- * @type string $file The plugin's main file path relative to the plugins
62- * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php').
61+ * @type string $file The plugin's main file path relative to the plugins
62+ * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php').
63+ * @type callable $is_active Callback to determine whether the plugin is active. Receives no arguments and must return bool.
64+ * Defaults to `__return_true`.
6365 * }
6466 * }
6567 * @phpstan-return ?array{
6668 * name: non-empty-string,
67- * description: non-empty- string,
69+ * description: string,
6870 * logo_url?: non-empty-string,
6971 * type: non-empty-string,
7072 * authentication: array{
@@ -74,8 +76,9 @@ function wp_is_connector_registered( string $id ): bool {
7476 * constant_name?: non-empty-string,
7577 * env_var_name?: non-empty-string
7678 * },
77- * plugin?: array{
78- * file: non-empty-string
79+ * plugin: array{
80+ * file?: non-empty-string,
81+ * is_active: callable(): bool,
7982 * }
8083 * }
8184 */
@@ -119,14 +122,16 @@ function wp_get_connector( string $id ): ?array {
119122 * @type array $plugin {
120123 * Optional. Plugin data for install/activate UI.
121124 *
122- * @type string $file The plugin's main file path relative to the plugins
123- * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php').
125+ * @type string $file The plugin's main file path relative to the plugins
126+ * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php').
127+ * @type callable $is_active Callback to determine whether the plugin is active. Receives no arguments and must return bool.
128+ * Defaults to `__return_true`.
124129 * }
125130 * }
126131 * }
127132 * @phpstan-return array<string, array{
128133 * name: non-empty-string,
129- * description: non-empty- string,
134+ * description: string,
130135 * logo_url?: non-empty-string,
131136 * type: non-empty-string,
132137 * authentication: array{
@@ -136,8 +141,9 @@ function wp_get_connector( string $id ): ?array {
136141 * constant_name?: non-empty-string,
137142 * env_var_name?: non-empty-string
138143 * },
139- * plugin?: array{
140- * file: non-empty-string
144+ * plugin: array{
145+ * file?: non-empty-string,
146+ * is_active: callable(): bool,
141147 * }
142148 * }>
143149 */
@@ -160,7 +166,7 @@ function wp_get_connectors(): array {
160166 * @access private
161167 *
162168 * @param string $path Absolute path to the logo file.
163- * @return string|null The URL to the logo file, or null if the path is invalid.
169+ * @return non-empty- string|null The URL to the logo file, or null if the path is invalid.
164170 */
165171function _wp_connectors_resolve_ai_provider_logo_url ( string $ path ): ?string {
166172 if ( ! $ path ) {
@@ -175,12 +181,14 @@ function _wp_connectors_resolve_ai_provider_logo_url( string $path ): ?string {
175181
176182 $ mu_plugin_dir = wp_normalize_path ( WPMU_PLUGIN_DIR );
177183 if ( str_starts_with ( $ path , $ mu_plugin_dir . '/ ' ) ) {
178- return plugins_url ( substr ( $ path , strlen ( $ mu_plugin_dir ) ), WPMU_PLUGIN_DIR . '/. ' );
184+ $ logo_url = plugins_url ( substr ( $ path , strlen ( $ mu_plugin_dir ) ), WPMU_PLUGIN_DIR . '/. ' );
185+ return $ logo_url ? $ logo_url : null ;
179186 }
180187
181188 $ plugin_dir = wp_normalize_path ( WP_PLUGIN_DIR );
182189 if ( str_starts_with ( $ path , $ plugin_dir . '/ ' ) ) {
183- return plugins_url ( substr ( $ path , strlen ( $ plugin_dir ) ) );
190+ $ logo_url = plugins_url ( substr ( $ path , strlen ( $ plugin_dir ) ) );
191+ return $ logo_url ? $ logo_url : null ;
184192 }
185193
186194 _doing_it_wrong (
@@ -317,7 +325,7 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re
317325 // Registry values (from provider plugins) take precedence over hardcoded fallbacks.
318326 $ ai_registry = AiClient::defaultRegistry ();
319327
320- foreach ( $ ai_registry ->getRegisteredProviderIds () as $ connector_id ) {
328+ foreach ( array_filter ( $ ai_registry ->getRegisteredProviderIds () ) as $ connector_id ) {
321329 $ provider_class_name = $ ai_registry ->getProviderClassName ( $ connector_id );
322330 $ provider_metadata = $ provider_class_name ::metadata ();
323331
@@ -327,9 +335,11 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re
327335 if ( $ is_api_key ) {
328336 $ credentials_url = $ provider_metadata ->getCredentialsUrl ();
329337 $ authentication = array (
330- 'method ' => 'api_key ' ,
331- 'credentials_url ' => $ credentials_url ? $ credentials_url : null ,
338+ 'method ' => 'api_key ' ,
332339 );
340+ if ( $ credentials_url ) {
341+ $ authentication ['credentials_url ' ] = $ credentials_url ;
342+ }
333343 } else {
334344 $ authentication = array ( 'method ' => 'none ' );
335345 }
@@ -362,8 +372,10 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re
362372 'description ' => $ description ? $ description : '' ,
363373 'type ' => 'ai_provider ' ,
364374 'authentication ' => $ authentication ,
365- 'logo_url ' => $ logo_url ,
366375 );
376+ if ( $ logo_url ) {
377+ $ defaults [ $ connector_id ]['logo_url ' ] = $ logo_url ;
378+ }
367379 }
368380 }
369381
@@ -372,33 +384,22 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re
372384 if ( 'api_key ' === $ args ['authentication ' ]['method ' ] ) {
373385 $ sanitized_id = str_replace ( '- ' , '_ ' , $ id );
374386
375- if ( ! isset ( $ args ['authentication ' ]['setting_name ' ] ) ) {
376- $ args ['authentication ' ]['setting_name ' ] = "connectors_ai_ {$ sanitized_id }_api_key " ;
377- }
387+ $ args ['authentication ' ]['setting_name ' ] = "connectors_ai_ {$ sanitized_id }_api_key " ;
378388
379389 // All AI providers use the {CONSTANT_CASE_ID}_API_KEY naming convention.
380- if ( ! isset ( $ args ['authentication ' ]['constant_name ' ] ) || ! isset ( $ args ['authentication ' ]['env_var_name ' ] ) ) {
381- $ constant_case_key = strtoupper ( preg_replace ( '/([a-z])([A-Z])/ ' , '$1_$2 ' , $ sanitized_id ) ) . '_API_KEY ' ;
382-
383- if ( ! isset ( $ args ['authentication ' ]['constant_name ' ] ) ) {
384- $ args ['authentication ' ]['constant_name ' ] = $ constant_case_key ;
385- }
390+ $ constant_case_key = strtoupper ( (string ) preg_replace ( '/([a-z])([A-Z])/ ' , '$1_$2 ' , $ sanitized_id ) ) . '_API_KEY ' ;
386391
387- if ( ! isset ( $ args ['authentication ' ]['env_var_name ' ] ) ) {
388- $ args ['authentication ' ]['env_var_name ' ] = $ constant_case_key ;
389- }
390- }
392+ $ args ['authentication ' ]['constant_name ' ] = $ constant_case_key ;
393+ $ args ['authentication ' ]['env_var_name ' ] = $ constant_case_key ;
391394 }
392395
393- if ( ! isset ( $ args ['plugin ' ]['is_active ' ] ) ) {
394- $ args ['plugin ' ]['is_active ' ] = static function () use ( $ ai_registry , $ id ): bool {
395- try {
396- return $ ai_registry ->hasProvider ( $ id );
397- } catch ( Exception $ e ) {
398- return false ;
399- }
400- };
401- }
396+ $ args ['plugin ' ]['is_active ' ] = static function () use ( $ ai_registry , $ id ): bool {
397+ try {
398+ return $ ai_registry ->hasProvider ( $ id );
399+ } catch ( Exception $ e ) {
400+ return false ;
401+ }
402+ };
402403
403404 $ registry ->register ( $ id , $ args );
404405 }
@@ -646,7 +647,7 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
646647 }
647648
648649 $ api_key = get_option ( $ auth ['setting_name ' ], '' );
649- if ( '' === $ api_key ) {
650+ if ( ! is_string ( $ api_key ) || '' === $ api_key ) {
650651 continue ;
651652 }
652653
@@ -673,6 +674,10 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
673674function _wp_connectors_get_connector_script_module_data ( array $ data ): array {
674675 $ registry = AiClient::defaultRegistry ();
675676
677+ if ( ! function_exists ( 'validate_plugin ' ) ) {
678+ require_once ABSPATH . 'wp-admin/includes/plugin.php ' ;
679+ }
680+
676681 $ connectors = array ();
677682 foreach ( wp_get_connectors () as $ connector_id => $ connector_data ) {
678683 $ auth = $ connector_data ['authentication ' ];
@@ -706,7 +711,7 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array {
706711 if ( ! empty ( $ connector_data ['plugin ' ]['file ' ] ) ) {
707712 $ file = $ connector_data ['plugin ' ]['file ' ];
708713 $ is_activated = (bool ) call_user_func ( $ connector_data ['plugin ' ]['is_active ' ] );
709- $ is_installed = $ is_activated || file_exists ( wp_normalize_path ( WP_PLUGIN_DIR . ' / ' . $ file ) );
714+ $ is_installed = $ is_activated || 0 === validate_plugin ( $ file );
710715
711716 $ connector_out ['plugin ' ] = array (
712717 'file ' => $ file ,
0 commit comments