Skip to content
21 changes: 12 additions & 9 deletions src/wp-includes/class-wp-connector-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
* @phpstan-type Connector array{
* name: string,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting this is not consistent with the return value of wp_get_connector() which uses non-empty-string.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I'm updating in the svn commit directly.

* description: string,
* logo_url?: string|null,
* logo_url?: string,
* type: string,
* authentication: array{
* method: string,
* credentials_url?: string|null,
* credentials_url?: string,
* setting_name?: string
* },
* plugin?: array{
Expand Down Expand Up @@ -62,15 +62,15 @@ final class WP_Connector_Registry {
*
* @type string $name Required. The connector's display name.
* @type string $description Optional. The connector's description. Default empty string.
* @type string|null $logo_url Optional. URL to the connector's logo image. Default null.
* @type string $logo_url Optional. URL to the connector's logo image.
* @type string $type Required. The connector type. Currently, only 'ai_provider' is supported.
* @type array $authentication {
* Required. Authentication configuration.
*
* @type string $method Required. The authentication method: 'api_key' or 'none'.
* @type string|null $credentials_url Optional. URL where users can obtain API credentials.
* @type string $method Required. The authentication method: 'api_key' or 'none'.
* @type string $credentials_url Optional. URL where users can obtain API credentials.
* }
* @type array $plugin {
* @type array $plugin {
* Optional. Plugin data for install/activate UI.
*
* @type string $slug The WordPress.org plugin slug.
Expand Down Expand Up @@ -158,8 +158,10 @@ public function register( string $id, array $args ): ?array {
}

if ( 'api_key' === $args['authentication']['method'] ) {
$connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'] ?? null;
$connector['authentication']['setting_name'] = "connectors_ai_{$id}_api_key";
if ( ! empty( $args['authentication']['credentials_url'] ) && is_string( $args['authentication']['credentials_url'] ) ) {
$connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'];
}
$connector['authentication']['setting_name'] = "connectors_ai_{$id}_api_key";
}

if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {
Expand Down Expand Up @@ -206,7 +208,8 @@ public function unregister( string $id ): ?array {
*
* @see wp_get_connectors()
*
* @return array<string, array> The array of registered connectors keyed by connector ID.
* @return array Connector settings keyed by connector ID.
*
* @phpstan-return array<string, Connector>
*/
public function get_all_registered(): array {
Expand Down
124 changes: 81 additions & 43 deletions src/wp-includes/connectors.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,41 @@ function wp_is_connector_registered( string $id ): bool {
* @see WP_Connector_Registry::get_registered()
*
* @param string $id The connector identifier.
* @return array|null The registered connector data, or null if not registered.
* @return array|null {
* Connector data, or null if not registered.
*
* @type string $name The connector's display name.
* @type string $description The connector's description.
* @type string $logo_url Optional. URL to the connector's logo image.
* @type string $type The connector type. Currently, only 'ai_provider' is supported.
* @type array $authentication {
* Authentication configuration. When method is 'api_key', includes
* credentials_url and setting_name. When 'none', only method is present.
*
* @type string $method The authentication method: 'api_key' or 'none'.
* @type string $credentials_url Optional. URL where users can obtain API credentials.
* @type string $setting_name Optional. The setting name for the API key.
* }
* @type array $plugin {
* Optional. Plugin data for install/activate UI.
*
* @type string $slug The WordPress.org plugin slug.
* }
* }
* @phpstan-return ?array{
* name: non-empty-string,
* description: non-empty-string,
* logo_url?: non-empty-string,
* type: 'ai_provider',
* authentication: array{
* method: 'api_key'|'none',
* credentials_url?: non-empty-string,
* setting_name?: non-empty-string
* },
* plugin?: array{
* slug: non-empty-string
* }
* }
*/
function wp_get_connector( string $id ): ?array {
$registry = WP_Connector_Registry::get_instance();
Expand All @@ -55,7 +89,45 @@ function wp_get_connector( string $id ): ?array {
*
* @see WP_Connector_Registry::get_all_registered()
*
* @return array[] An array of registered connectors keyed by connector ID.
* @return array {
* Connector settings keyed by connector ID.
*
* @type array ...$0 {
* Data for a single connector.
*
* @type string $name The connector's display name.
* @type string $description The connector's description.
* @type string $logo_url Optional. URL to the connector's logo image.
* @type string $type The connector type. Currently, only 'ai_provider' is supported.
* @type array $authentication {
* Authentication configuration. When method is 'api_key', includes
* credentials_url and setting_name. When 'none', only method is present.
*
* @type string $method The authentication method: 'api_key' or 'none'.
* @type string $credentials_url Optional. URL where users can obtain API credentials.
* @type string $setting_name Optional. The setting name for the API key.
* }
* @type array $plugin {
* Optional. Plugin data for install/activate UI.
*
* @type string $slug The WordPress.org plugin slug.
* }
* }
* }
* @phpstan-return array<string, array{
* name: non-empty-string,
* description: non-empty-string,
* logo_url?: non-empty-string,
* type: 'ai_provider',
* authentication: array{
* method: 'api_key'|'none',
* credentials_url?: non-empty-string,
* setting_name?: non-empty-string
* },
* plugin?: array{
* slug: non-empty-string
* }
* }>
*/
function wp_get_connectors(): array {
$registry = WP_Connector_Registry::get_instance();
Expand Down Expand Up @@ -324,41 +396,6 @@ function _wp_connectors_get_real_api_key( string $option_name, callable $mask_ca
return (string) $value;
}

/**
* Gets the registered connector settings.
*
* @since 7.0.0
* @access private
*
* @return array {
* Connector settings keyed by connector ID.
*
* @type array ...$0 {
* Data for a single connector.
*
* @type string $name The connector's display name.
* @type string $description The connector's description.
* @type string $type The connector type. Currently, only 'ai_provider' is supported.
* @type array $plugin Optional. Plugin data for install/activate UI.
* @type string $slug The WordPress.org plugin slug.
* }
* @type array $authentication {
* Authentication configuration. When method is 'api_key', includes
* credentials_url and setting_name. When 'none', only method is present.
*
* @type string $method The authentication method: 'api_key' or 'none'.
* @type string|null $credentials_url Optional. URL where users can obtain API credentials.
* @type string $setting_name Optional. The setting name for the API key.
* }
* }
* }
*/
function _wp_connectors_get_connector_settings(): array {
$connectors = wp_get_connectors();
ksort( $connectors );
return $connectors;
}

/**
* Validates connector API keys in the REST response when explicitly requested.
*
Expand Down Expand Up @@ -396,7 +433,7 @@ function _wp_connectors_validate_keys_in_rest( WP_REST_Response $response, WP_RE
return $response;
}

foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
$auth = $connector_data['authentication'];
if ( 'ai_provider' !== $connector_data['type'] || 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
continue;
Expand Down Expand Up @@ -431,7 +468,7 @@ function _wp_connectors_validate_keys_in_rest( WP_REST_Response $response, WP_RE
function _wp_register_default_connector_settings(): void {
$ai_registry = AiClient::defaultRegistry();

foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
$auth = $connector_data['authentication'];
if ( 'ai_provider' !== $connector_data['type'] || 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
continue;
Expand Down Expand Up @@ -485,7 +522,7 @@ function _wp_register_default_connector_settings(): void {
function _wp_connectors_pass_default_keys_to_ai_client(): void {
try {
$ai_registry = AiClient::defaultRegistry();
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
if ( 'ai_provider' !== $connector_data['type'] ) {
continue;
}
Expand Down Expand Up @@ -521,12 +558,12 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
* @since 7.0.0
* @access private
*
* @param array $data Existing script module data.
* @return array Script module data with connectors added.
* @param array<string, mixed> $data Existing script module data.
* @return array<string, mixed> Script module data with connectors added.
*/
function _wp_connectors_get_connector_script_module_data( array $data ): array {
$connectors = array();
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
$auth = $connector_data['authentication'];
$auth_out = array( 'method' => $auth['method'] );

Expand All @@ -548,6 +585,7 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array {

$connectors[ $connector_id ] = $connector_out;
}
ksort( $connectors );
$data['connectors'] = $connectors;
return $data;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,36 @@
require_once dirname( __DIR__, 2 ) . '/includes/wp-ai-client-mock-provider-trait.php';

/**
* Tests for _wp_connectors_get_connector_settings().
* Tests for wp_get_connectors().
*
* @group connectors
* @covers ::_wp_connectors_get_connector_settings
* @covers ::wp_get_connectors
*/
class Tests_Connectors_WpConnectorsGetConnectorSettings extends WP_UnitTestCase {
class Tests_Connectors_WpGetConnectors extends WP_UnitTestCase {

use WP_AI_Client_Mock_Provider_Trait;

/**
* Registers the mock provider once before any tests in this class run.
*/
public static function set_up_before_class() {
public static function set_up_before_class(): void {
parent::set_up_before_class();
self::register_mock_connectors_provider();
}

/**
* Unregisters the mock provider setting added by `init`.
*/
public static function tear_down_after_class() {
public static function tear_down_after_class(): void {
self::unregister_mock_connector_setting();
parent::tear_down_after_class();
}

/**
* @ticket 64730
*/
public function test_returns_expected_connector_keys() {
$connectors = _wp_connectors_get_connector_settings();
public function test_returns_expected_connector_keys(): void {
$connectors = wp_get_connectors();

$this->assertArrayHasKey( 'google', $connectors );
$this->assertArrayHasKey( 'openai', $connectors );
Expand All @@ -44,8 +44,8 @@ public function test_returns_expected_connector_keys() {
/**
* @ticket 64730
*/
public function test_each_connector_has_required_fields() {
$connectors = _wp_connectors_get_connector_settings();
public function test_each_connector_has_required_fields(): void {
$connectors = wp_get_connectors();

$this->assertNotEmpty( $connectors, 'Connector settings should not be empty.' );

Expand All @@ -67,8 +67,8 @@ public function test_each_connector_has_required_fields() {
/**
* @ticket 64730
*/
public function test_api_key_connectors_have_setting_name_and_credentials_url() {
$connectors = _wp_connectors_get_connector_settings();
public function test_api_key_connectors_have_setting_name_and_credentials_url(): void {
$connectors = wp_get_connectors();
$api_key_count = 0;

foreach ( $connectors as $connector_id => $connector_data ) {
Expand All @@ -81,10 +81,9 @@ public function test_api_key_connectors_have_setting_name_and_credentials_url()
$this->assertArrayHasKey( 'setting_name', $connector_data['authentication'], "Connector '{$connector_id}' authentication is missing 'setting_name'." );
$this->assertSame(
"connectors_ai_{$connector_id}_api_key",
$connector_data['authentication']['setting_name'],
$connector_data['authentication']['setting_name'] ?? null,
"Connector '{$connector_id}' setting_name does not match expected format."
);
$this->assertArrayHasKey( 'credentials_url', $connector_data['authentication'], "Connector '{$connector_id}' authentication is missing 'credentials_url'." );
}

$this->assertGreaterThan( 0, $api_key_count, 'At least one connector should use api_key authentication.' );
Expand All @@ -93,8 +92,8 @@ public function test_api_key_connectors_have_setting_name_and_credentials_url()
/**
* @ticket 64730
*/
public function test_featured_provider_names_match_expected() {
$connectors = _wp_connectors_get_connector_settings();
public function test_featured_provider_names_match_expected(): void {
$connectors = wp_get_connectors();

$this->assertSame( 'Google', $connectors['google']['name'] );
$this->assertSame( 'OpenAI', $connectors['openai']['name'] );
Expand All @@ -104,27 +103,15 @@ public function test_featured_provider_names_match_expected() {
/**
* @ticket 64730
*/
public function test_includes_registered_provider_from_registry() {
$connectors = _wp_connectors_get_connector_settings();
public function test_includes_registered_provider_from_registry(): void {
$connectors = wp_get_connectors();
$mock = $connectors['mock_connectors_test'];

$this->assertSame( 'Mock Connectors Test', $mock['name'] );
$this->assertSame( '', $mock['description'] );
$this->assertSame( 'ai_provider', $mock['type'] );
$this->assertSame( 'api_key', $mock['authentication']['method'] );
$this->assertNull( $mock['authentication']['credentials_url'] );
$this->assertSame( 'connectors_ai_mock_connectors_test_api_key', $mock['authentication']['setting_name'] );
}

/**
* @ticket 64730
*/
public function test_connectors_are_sorted_alphabetically() {
$connectors = _wp_connectors_get_connector_settings();
$keys = array_keys( $connectors );
$sorted = $keys;
sort( $sorted );

$this->assertSame( $sorted, $keys, 'Connectors should be sorted alphabetically by ID.' );
$this->assertNull( $mock['authentication']['credentials_url'] ?? null );
$this->assertSame( 'connectors_ai_mock_connectors_test_api_key', $mock['authentication']['setting_name'] ?? null );
}
}
Loading