Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/wp-includes/class-wp-connector-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
* },
* plugin?: array{
* slug: non-empty-string
* }
* },
* provider_id?: non-empty-string
* }
*/
final class WP_Connector_Registry {
Expand Down Expand Up @@ -75,6 +76,9 @@ final class WP_Connector_Registry {
*
* @type string $slug The WordPress.org plugin slug.
* }
* @type string $provider_id Optional. The original AI Client provider ID. Used when the
* provider ID differs from the connector ID (e.g. hyphens replaced
* with underscores).
* }
* @return array|null The registered connector data on success, null on failure.
*
Expand Down Expand Up @@ -168,6 +172,10 @@ public function register( string $id, array $args ): ?array {
$connector['plugin'] = $args['plugin'];
}

if ( ! empty( $args['provider_id'] ) && is_string( $args['provider_id'] ) ) {
$connector['provider_id'] = $args['provider_id'];
}

$this->registered_connectors[ $id ] = $connector;
return $connector;
}
Expand Down
46 changes: 34 additions & 12 deletions src/wp-includes/connectors.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ function wp_is_connector_registered( string $id ): bool {
*
* @type string $slug The WordPress.org plugin slug.
* }
* @type string $provider_id Optional. The original AI Client provider ID, present when
* it differs from the connector ID.
* }
* @phpstan-return ?array{
* name: non-empty-string,
Expand All @@ -70,7 +72,8 @@ function wp_is_connector_registered( string $id ): bool {
* },
* plugin?: array{
* slug: non-empty-string
* }
* },
* provider_id?: non-empty-string
* }
*/
function wp_get_connector( string $id ): ?array {
Expand Down Expand Up @@ -112,6 +115,8 @@ function wp_get_connector( string $id ): ?array {
*
* @type string $slug The WordPress.org plugin slug.
* }
* @type string $provider_id Optional. The original AI Client provider ID, present when
* it differs from the connector ID.
* }
* }
* @phpstan-return array<string, array{
Expand All @@ -126,7 +131,8 @@ function wp_get_connector( string $id ): ?array {
* },
* plugin?: array{
* slug: non-empty-string
* }
* },
* provider_id?: non-empty-string
* }>
*/
function wp_get_connectors(): array {
Expand Down Expand Up @@ -236,8 +242,10 @@ function _wp_connectors_init(): void {
// Registry values (from provider plugins) take precedence over hardcoded fallbacks.
$ai_registry = AiClient::defaultRegistry();

foreach ( $ai_registry->getRegisteredProviderIds() as $connector_id ) {
$provider_class_name = $ai_registry->getProviderClassName( $connector_id );
foreach ( $ai_registry->getRegisteredProviderIds() as $provider_id ) {
// Connector IDs only allow [a-z0-9_]; provider IDs may use hyphens.
$connector_id = str_replace( '-', '_', $provider_id );
$provider_class_name = $ai_registry->getProviderClassName( $provider_id );
$provider_metadata = $provider_class_name::metadata();

$auth_method = $provider_metadata->getAuthenticationMethod();
Expand Down Expand Up @@ -275,13 +283,15 @@ function _wp_connectors_init(): void {
if ( ! empty( $authentication['credentials_url'] ) ) {
$defaults[ $connector_id ]['authentication']['credentials_url'] = $authentication['credentials_url'];
}
$defaults[ $connector_id ]['provider_id'] = $provider_id;
} else {
$defaults[ $connector_id ] = array(
'name' => $name ? $name : ucwords( $connector_id ),
'name' => $name ? $name : ucwords( str_replace( '_', ' ', $connector_id ) ),
'description' => $description ? $description : '',
'type' => 'ai_provider',
'authentication' => $authentication,
'logo_url' => $logo_url,
'provider_id' => $provider_id,
);
}
}
Expand Down Expand Up @@ -456,6 +466,9 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R
continue;
}

// Use the original provider ID for AiClient lookups.
$provider_id = $connector_data['provider_id'] ?? $connector_id;

$setting_name = $auth['setting_name'];
if ( ! array_key_exists( $setting_name, $data ) ) {
continue;
Expand All @@ -465,7 +478,7 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R

// On update, validate the key before masking.
if ( $is_update && is_string( $value ) && '' !== $value ) {
if ( true !== _wp_connectors_is_ai_api_key_valid( $value, $connector_id ) ) {
if ( true !== _wp_connectors_is_ai_api_key_valid( $value, $provider_id ) ) {
update_option( $setting_name, '' );
$data[ $setting_name ] = '';
continue;
Expand Down Expand Up @@ -498,8 +511,11 @@ function _wp_register_default_connector_settings(): void {
continue;
}

// Use the original provider ID for AiClient lookups.
$provider_id = $connector_data['provider_id'] ?? $connector_id;

// Skip registering the setting if the provider is not in the registry.
if ( ! $ai_registry->hasProvider( $connector_id ) ) {
if ( ! $ai_registry->hasProvider( $provider_id ) ) {
continue;
}

Expand Down Expand Up @@ -546,12 +562,15 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
continue;
}

if ( ! $ai_registry->hasProvider( $connector_id ) ) {
// Use the original provider ID for AiClient lookups.
$provider_id = $connector_data['provider_id'] ?? $connector_id;

if ( ! $ai_registry->hasProvider( $provider_id ) ) {
continue;
}

// Skip if the key is already provided via env var or constant.
$key_source = _wp_connectors_get_api_key_source( $connector_id, $auth['setting_name'] );
$key_source = _wp_connectors_get_api_key_source( $provider_id, $auth['setting_name'] );
if ( 'env' === $key_source || 'constant' === $key_source ) {
continue;
}
Expand All @@ -562,7 +581,7 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
}

$ai_registry->setProviderRequestAuthentication(
$connector_id,
$provider_id,
new ApiKeyRequestAuthentication( $api_key )
);
}
Expand Down Expand Up @@ -599,12 +618,15 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array {
$auth = $connector_data['authentication'];
$auth_out = array( 'method' => $auth['method'] );

// Use the original provider ID for AiClient lookups.
$provider_id = $connector_data['provider_id'] ?? $connector_id;

if ( 'api_key' === $auth['method'] ) {
$auth_out['settingName'] = $auth['setting_name'] ?? '';
$auth_out['credentialsUrl'] = $auth['credentials_url'] ?? null;
$auth_out['keySource'] = _wp_connectors_get_api_key_source( $connector_id, $auth['setting_name'] ?? '' );
$auth_out['keySource'] = _wp_connectors_get_api_key_source( $provider_id, $auth['setting_name'] ?? '' );
try {
$auth_out['isConnected'] = $registry->hasProvider( $connector_id ) && $registry->isProviderConfigured( $connector_id );
$auth_out['isConnected'] = $registry->hasProvider( $provider_id ) && $registry->isProviderConfigured( $provider_id );
} catch ( Exception $e ) {
$auth_out['isConnected'] = false;
}
Expand Down
51 changes: 51 additions & 0 deletions tests/phpunit/tests/connectors/wpConnectorRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,55 @@ public function test_get_instance_returns_same_instance() {

$this->assertSame( $instance1, $instance2 );
}

/**
* @ticket 64861
*/
public function test_register_stores_provider_id() {
$args = self::$default_args;
$args['provider_id'] = 'my-custom-provider';

$result = $this->registry->register( 'my_custom_provider', $args );

$this->assertIsArray( $result );
$this->assertArrayHasKey( 'provider_id', $result );
$this->assertSame( 'my-custom-provider', $result['provider_id'] );
}

/**
* @ticket 64861
*/
public function test_register_omits_provider_id_when_not_provided() {
$result = $this->registry->register( 'no_provider_id', self::$default_args );

$this->assertIsArray( $result );
$this->assertArrayNotHasKey( 'provider_id', $result );
}

/**
* @ticket 64861
*/
public function test_register_omits_provider_id_when_empty() {
$args = self::$default_args;
$args['provider_id'] = '';

$result = $this->registry->register( 'empty_provider_id', $args );

$this->assertIsArray( $result );
$this->assertArrayNotHasKey( 'provider_id', $result );
}

/**
* @ticket 64861
*/
public function test_get_registered_includes_provider_id() {
$args = self::$default_args;
$args['provider_id'] = 'azure-openai';

$this->registry->register( 'azure_openai', $args );
$result = $this->registry->get_registered( 'azure_openai' );

$this->assertIsArray( $result );
$this->assertSame( 'azure-openai', $result['provider_id'] );
}
}
Loading