diff --git a/src/wp-includes/class-wp-connector-registry.php b/src/wp-includes/class-wp-connector-registry.php index 75a6b8ef0c993..a1cd1ab1252ba 100644 --- a/src/wp-includes/class-wp-connector-registry.php +++ b/src/wp-includes/class-wp-connector-registry.php @@ -18,11 +18,11 @@ * @phpstan-type Connector array{ * name: string, * 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{ @@ -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. @@ -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'] ) ) { @@ -206,7 +208,8 @@ public function unregister( string $id ): ?array { * * @see wp_get_connectors() * - * @return array The array of registered connectors keyed by connector ID. + * @return array Connector settings keyed by connector ID. + * * @phpstan-return array */ public function get_all_registered(): array { diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 60f97839dabb6..b8b9004354652 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -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(); @@ -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 */ function wp_get_connectors(): array { $registry = WP_Connector_Registry::get_instance(); @@ -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. * @@ -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; @@ -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; @@ -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; } @@ -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 $data Existing script module data. + * @return array 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'] ); @@ -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; } diff --git a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php b/tests/phpunit/tests/connectors/wpGetConnectors.php similarity index 73% rename from tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php rename to tests/phpunit/tests/connectors/wpGetConnectors.php index 2a7ce199fa777..8cb7a5c5d2d90 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php +++ b/tests/phpunit/tests/connectors/wpGetConnectors.php @@ -3,19 +3,19 @@ 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(); } @@ -23,7 +23,7 @@ public static function set_up_before_class() { /** * 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(); } @@ -31,8 +31,8 @@ public static function 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 ); @@ -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.' ); @@ -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 ) { @@ -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.' ); @@ -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'] ); @@ -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 ); } }