Make WordPress Core

Changeset 62056


Ignore:
Timestamp:
03/19/2026 06:50:08 AM (2 weeks ago)
Author:
gziolo
Message:

Connectors: Allow hyphens in connector IDs

Expands the connector ID validation regex from /^[a-z0-9_]+$/ to /^[a-z0-9_-]+$/, aligning with the PHP AI Client library naming conventions. Hyphens are normalized to underscores when generating setting_name (e.g., azure-openaiconnectors_ai_azure_openai_api_key).

Developed in https://github.com/WordPress/wordpress-develop/pull/11285.

Props pers, gziolo, jorgefilipecosta, westonruter, flixos90, mukesh27.
Fixes #64861.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-connector-registry.php

    r62032 r62056  
    6868     * Validates the provided arguments and stores the connector in the registry.
    6969     * For connectors with `api_key` authentication, a `setting_name` is automatically
    70      * generated using the pattern `connectors_ai_{$id}_api_key` (e.g., connector ID
    71      * `openai` produces `connectors_ai_openai_api_key`). This setting name is used
    72      * for the Settings API registration and REST API exposure.
     70     * generated using the pattern `connectors_ai_{$id}_api_key`, with hyphens in the ID
     71     * normalized to underscores (e.g., connector ID `openai` produces
     72     * `connectors_ai_openai_api_key`, and `azure-openai` produces
     73     * `connectors_ai_azure_openai_api_key`). This setting name is used for the Settings
     74     * API registration and REST API exposure.
    7375     *
    7476     * Registering a connector with an ID that is already registered will trigger a
     
    8183     *
    8284     * @param string $id   The unique connector identifier. Must match the pattern
    83      *                     `/^[a-z0-9_]+$/` (lowercase alphanumeric and underscores only).
     85     *                     `/^[a-z0-9_-]+$/` (lowercase alphanumeric, hyphens, and underscores only).
    8486     * @param array  $args {
    8587     *     An associative array of arguments for the connector.
     
    107109     */
    108110    public function register( string $id, array $args ): ?array {
    109         if ( ! preg_match( '/^[a-z0-9_]+$/', $id ) ) {
     111        if ( ! preg_match( '/^[a-z0-9_-]+$/', $id ) ) {
    110112            _doing_it_wrong(
    111113                __METHOD__,
    112114                __(
    113                     'Connector ID must contain only lowercase alphanumeric characters and underscores.'
     115                    'Connector ID must contain only lowercase alphanumeric characters, hyphens, and underscores.'
    114116                ),
    115117                '7.0.0'
     
    186188                $connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'];
    187189            }
    188             $connector['authentication']['setting_name'] = "connectors_ai_{$id}_api_key";
     190            $connector['authentication']['setting_name'] = 'connectors_ai_' . str_replace( '-', '_', $id ) . '_api_key';
    189191        }
    190192
  • trunk/tests/phpunit/includes/wp-ai-client-mock-provider-trait.php

    r61943 r62056  
    9797    protected static function createProviderMetadata(): ProviderMetadata {
    9898        return new ProviderMetadata(
    99             'mock_connectors_test',
     99            'mock-connectors-test',
    100100            'Mock Connectors Test',
    101101            ProviderTypeEnum::cloud(),
     
    157157    private static function register_mock_connectors_provider(): void {
    158158        $ai_registry = AiClient::defaultRegistry();
    159         if ( ! $ai_registry->hasProvider( 'mock_connectors_test' ) ) {
     159        if ( ! $ai_registry->hasProvider( 'mock-connectors-test' ) ) {
    160160            $ai_registry->registerProvider( Mock_Connectors_Test_Provider::class );
    161161        }
     
    163163        // Also register in the WP connector registry if not already present.
    164164        $connector_registry = WP_Connector_Registry::get_instance();
    165         if ( null !== $connector_registry && ! $connector_registry->is_registered( 'mock_connectors_test' ) ) {
     165        if ( null !== $connector_registry && ! $connector_registry->is_registered( 'mock-connectors-test' ) ) {
    166166            $connector_registry->register(
    167                 'mock_connectors_test',
     167                'mock-connectors-test',
    168168                array(
    169169                    'name'           => 'Mock Connectors Test',
  • trunk/tests/phpunit/tests/connectors/wpConnectorRegistry.php

    r61943 r62056  
    4747     */
    4848    public function test_register_returns_connector_data() {
    49         $result = $this->registry->register( 'test_provider', self::$default_args );
     49        $result = $this->registry->register( 'test-provider', self::$default_args );
    5050
    5151        $this->assertIsArray( $result );
     
    6262     */
    6363    public function test_register_generates_setting_name_for_api_key() {
    64         $result = $this->registry->register( 'my_ai', self::$default_args );
     64        $result = $this->registry->register( 'myai', self::$default_args );
     65
     66        $this->assertSame( 'connectors_ai_myai_api_key', $result['authentication']['setting_name'] );
     67    }
     68
     69    /**
     70     * @ticket 64861
     71     */
     72    public function test_register_generates_setting_name_normalizes_hyphens() {
     73        $result = $this->registry->register( 'my-ai', self::$default_args );
    6574
    6675        $this->assertSame( 'connectors_ai_my_ai_api_key', $result['authentication']['setting_name'] );
     
    7685            'authentication' => array( 'method' => 'none' ),
    7786        );
    78         $result = $this->registry->register( 'no_auth', $args );
     87        $result = $this->registry->register( 'no-auth', $args );
    7988
    8089        $this->assertIsArray( $result );
     
    92101        );
    93102
    94         $result = $this->registry->register( 'minimal', $args );
     103        $result = $this->registry->register( 'minimal-provider', $args );
    95104
    96105        $this->assertSame( '', $result['description'] );
     
    104113        $args['logo_url'] = 'https://example.com/logo.png';
    105114
    106         $result = $this->registry->register( 'with_logo', $args );
     115        $result = $this->registry->register( 'with-logo', $args );
    107116
    108117        $this->assertArrayHasKey( 'logo_url', $result );
     
    114123     */
    115124    public function test_register_omits_logo_url_when_not_provided() {
    116         $result = $this->registry->register( 'no_logo', self::$default_args );
     125        $result = $this->registry->register( 'no-logo', self::$default_args );
    117126
    118127        $this->assertArrayNotHasKey( 'logo_url', $result );
     
    126135        $args['logo_url'] = '';
    127136
    128         $result = $this->registry->register( 'empty_logo', $args );
     137        $result = $this->registry->register( 'empty-logo', $args );
    129138
    130139        $this->assertArrayNotHasKey( 'logo_url', $result );
     
    138147        $args['plugin'] = array( 'slug' => 'my-plugin' );
    139148
    140         $result = $this->registry->register( 'with_plugin', $args );
     149        $result = $this->registry->register( 'with-plugin', $args );
    141150
    142151        $this->assertArrayHasKey( 'plugin', $result );
     
    148157     */
    149158    public function test_register_omits_plugin_when_not_provided() {
    150         $result = $this->registry->register( 'no_plugin', self::$default_args );
     159        $result = $this->registry->register( 'no-plugin', self::$default_args );
    151160
    152161        $this->assertArrayNotHasKey( 'plugin', $result );
     
    165174
    166175    /**
    167      * @ticket 64791
    168      */
    169     public function test_register_rejects_invalid_id_with_dashes() {
    170         $this->setExpectedIncorrectUsage( 'WP_Connector_Registry::register' );
    171 
     176     * @ticket 64861
     177     */
     178    public function test_register_accepts_id_with_hyphens() {
    172179        $result = $this->registry->register( 'my-provider', self::$default_args );
    173180
    174         $this->assertNull( $result );
     181        $this->assertIsArray( $result );
     182    }
     183
     184    /**
     185     * @ticket 64861
     186     */
     187    public function test_register_accepts_id_with_underscores() {
     188        $result = $this->registry->register( 'my_provider', self::$default_args );
     189
     190        $this->assertIsArray( $result );
    175191    }
    176192
     
    192208        $this->setExpectedIncorrectUsage( 'WP_Connector_Registry::register' );
    193209
    194         $this->registry->register( 'duplicate', self::$default_args );
    195         $result = $this->registry->register( 'duplicate', self::$default_args );
     210        $this->registry->register( 'test-duplicate', self::$default_args );
     211        $result = $this->registry->register( 'test-duplicate', self::$default_args );
    196212
    197213        $this->assertNull( $result );
     
    207223        unset( $args['name'] );
    208224
    209         $result = $this->registry->register( 'no_name', $args );
     225        $result = $this->registry->register( 'no-name', $args );
    210226
    211227        $this->assertNull( $result );
     
    221237        $args['name'] = '';
    222238
    223         $result = $this->registry->register( 'empty_name', $args );
     239        $result = $this->registry->register( 'empty-name', $args );
    224240
    225241        $this->assertNull( $result );
     
    235251        unset( $args['type'] );
    236252
    237         $result = $this->registry->register( 'no_type', $args );
     253        $result = $this->registry->register( 'no-type', $args );
    238254
    239255        $this->assertNull( $result );
     
    249265        unset( $args['authentication'] );
    250266
    251         $result = $this->registry->register( 'no_auth', $args );
     267        $result = $this->registry->register( 'no-auth', $args );
    252268
    253269        $this->assertNull( $result );
     
    263279        $args['authentication']['method'] = 'oauth';
    264280
    265         $result = $this->registry->register( 'bad_auth', $args );
     281        $result = $this->registry->register( 'bad-auth', $args );
    266282
    267283        $this->assertNull( $result );
     
    288304     */
    289305    public function test_get_registered_returns_connector_data() {
    290         $this->registry->register( 'my_connector', self::$default_args );
    291 
    292         $result = $this->registry->get_registered( 'my_connector' );
     306        $this->registry->register( 'my-connector', self::$default_args );
     307
     308        $result = $this->registry->get_registered( 'my-connector' );
    293309
    294310        $this->assertIsArray( $result );
     
    335351     */
    336352    public function test_unregister_removes_connector() {
    337         $this->registry->register( 'to_remove', self::$default_args );
    338 
    339         $result = $this->registry->unregister( 'to_remove' );
     353        $this->registry->register( 'to-remove', self::$default_args );
     354
     355        $result = $this->registry->unregister( 'to-remove' );
    340356
    341357        $this->assertIsArray( $result );
    342358        $this->assertSame( 'Test Provider', $result['name'] );
    343         $this->assertFalse( $this->registry->is_registered( 'to_remove' ) );
     359        $this->assertFalse( $this->registry->is_registered( 'to-remove' ) );
    344360    }
    345361
  • trunk/tests/phpunit/tests/connectors/wpConnectorsGetConnectorSettings.php

    r61983 r62056  
    3838        $this->assertArrayHasKey( 'openai', $connectors );
    3939        $this->assertArrayHasKey( 'anthropic', $connectors );
    40         $this->assertArrayHasKey( 'mock_connectors_test', $connectors );
     40        $this->assertArrayHasKey( 'mock-connectors-test', $connectors );
    4141        $this->assertCount( 4, $connectors );
    4242    }
     
    8181            $this->assertArrayHasKey( 'setting_name', $connector_data['authentication'], "Connector '{$connector_id}' authentication is missing 'setting_name'." );
    8282            $this->assertSame(
    83                 "connectors_ai_{$connector_id}_api_key",
     83                'connectors_ai_' . str_replace( '-', '_', $connector_id ) . '_api_key',
    8484                $connector_data['authentication']['setting_name'] ?? null,
    8585                "Connector '{$connector_id}' setting_name does not match expected format."
     
    106106    public function test_includes_registered_provider_from_registry(): void {
    107107        $connectors = wp_get_connectors();
    108         $mock       = $connectors['mock_connectors_test'];
     108        $mock       = $connectors['mock-connectors-test'];
    109109
    110110        $this->assertSame( 'Mock Connectors Test', $mock['name'] );
  • trunk/tests/phpunit/tests/connectors/wpConnectorsIsApiKeyValid.php

    r61824 r62056  
    5050        self::set_mock_provider_configured( true );
    5151
    52         $result = _wp_connectors_is_ai_api_key_valid( 'test-key', 'mock_connectors_test' );
     52        $result = _wp_connectors_is_ai_api_key_valid( 'test-key', 'mock-connectors-test' );
    5353
    5454        $this->assertTrue( $result );
     
    6363        self::set_mock_provider_configured( false );
    6464
    65         $result = _wp_connectors_is_ai_api_key_valid( 'test-key', 'mock_connectors_test' );
     65        $result = _wp_connectors_is_ai_api_key_valid( 'test-key', 'mock-connectors-test' );
    6666
    6767        $this->assertFalse( $result );
Note: See TracChangeset for help on using the changeset viewer.