Skip to content

Commit c85ee6b

Browse files
gzioloclaude
andcommitted
Connectors: Allow hyphens in connector IDs and normalize them in setting names.
Changes the connector ID validation regex from `/^[a-z0-9_]+$/` to `/^[a-z0-9-]+$/`, aligning with the naming conventions used by the Abilities API, and WordPress.org plugin slugs (all kebab-case, no underscores). Hyphens in connector IDs are normalized to underscores when generating the `setting_name` (e.g. `azure-openai` produces `connectors_ai_azure_openai_api_key`), matching the convention already used by the WP AI Client library when building env var names. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 4d83a5b commit c85ee6b

2 files changed

Lines changed: 51 additions & 31 deletions

File tree

src/wp-includes/class-wp-connector-registry.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ final class WP_Connector_Registry {
6767
*
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
7577
* `_doing_it_wrong()` notice and return `null`. To override an existing connector,
@@ -80,7 +82,7 @@ final class WP_Connector_Registry {
8082
* @see WP_Connector_Registry::unregister()
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 and hyphens only).
8486
* @param array $args {
8587
* An associative array of arguments for the connector.
8688
*
@@ -106,11 +108,11 @@ final class WP_Connector_Registry {
106108
* @phpstan-return Connector|null
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 and hyphens.'
114116
),
115117
'7.0.0'
116118
);
@@ -185,7 +187,7 @@ public function register( string $id, array $args ): ?array {
185187
if ( ! empty( $args['authentication']['credentials_url'] ) && is_string( $args['authentication']['credentials_url'] ) ) {
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

191193
if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {

tests/phpunit/tests/connectors/wpConnectorRegistry.php

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function set_up(): void {
4646
* @ticket 64791
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 );
5252
$this->assertSame( 'Test Provider', $result['name'] );
@@ -61,7 +61,16 @@ public function test_register_returns_connector_data() {
6161
* @ticket 64791
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'] );
6776
}
@@ -75,7 +84,7 @@ public function test_register_no_setting_name_for_none_auth() {
7584
'type' => 'ai_provider',
7685
'authentication' => array( 'method' => 'none' ),
7786
);
78-
$result = $this->registry->register( 'no_auth', $args );
87+
$result = $this->registry->register( 'noauth', $args );
7988

8089
$this->assertIsArray( $result );
8190
$this->assertArrayNotHasKey( 'setting_name', $result['authentication'] );
@@ -91,7 +100,7 @@ public function test_register_defaults_description_to_empty_string() {
91100
'authentication' => array( 'method' => 'none' ),
92101
);
93102

94-
$result = $this->registry->register( 'minimal', $args );
103+
$result = $this->registry->register( 'minimal-provider', $args );
95104

96105
$this->assertSame( '', $result['description'] );
97106
}
@@ -103,7 +112,7 @@ public function test_register_includes_logo_url() {
103112
$args = self::$default_args;
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 );
109118
$this->assertSame( 'https://example.com/logo.png', $result['logo_url'] );
@@ -113,7 +122,7 @@ public function test_register_includes_logo_url() {
113122
* @ticket 64791
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 );
119128
}
@@ -125,7 +134,7 @@ public function test_register_omits_logo_url_when_empty() {
125134
$args = self::$default_args;
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 );
131140
}
@@ -137,7 +146,7 @@ public function test_register_includes_plugin_data() {
137146
$args = self::$default_args;
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 );
143152
$this->assertSame( array( 'slug' => 'my-plugin' ), $result['plugin'] );
@@ -147,7 +156,7 @@ public function test_register_includes_plugin_data() {
147156
* @ticket 64791
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 );
153162
}
@@ -164,12 +173,21 @@ public function test_register_rejects_invalid_id_with_uppercase() {
164173
}
165174

166175
/**
167-
* @ticket 64791
176+
* @ticket 64861
177+
*/
178+
public function test_register_accepts_id_with_hyphens() {
179+
$result = $this->registry->register( 'my-provider', self::$default_args );
180+
181+
$this->assertIsArray( $result );
182+
}
183+
184+
/**
185+
* @ticket 64861
168186
*/
169-
public function test_register_rejects_invalid_id_with_dashes() {
187+
public function test_register_rejects_invalid_id_with_underscores() {
170188
$this->setExpectedIncorrectUsage( 'WP_Connector_Registry::register' );
171189

172-
$result = $this->registry->register( 'my-provider', self::$default_args );
190+
$result = $this->registry->register( 'my_provider', self::$default_args );
173191

174192
$this->assertNull( $result );
175193
}
@@ -191,8 +209,8 @@ public function test_register_rejects_empty_id() {
191209
public function test_register_rejects_duplicate_id() {
192210
$this->setExpectedIncorrectUsage( 'WP_Connector_Registry::register' );
193211

194-
$this->registry->register( 'duplicate', self::$default_args );
195-
$result = $this->registry->register( 'duplicate', self::$default_args );
212+
$this->registry->register( 'test-duplicate', self::$default_args );
213+
$result = $this->registry->register( 'test-duplicate', self::$default_args );
196214

197215
$this->assertNull( $result );
198216
}
@@ -206,7 +224,7 @@ public function test_register_rejects_missing_name() {
206224
$args = self::$default_args;
207225
unset( $args['name'] );
208226

209-
$result = $this->registry->register( 'no_name', $args );
227+
$result = $this->registry->register( 'no-name', $args );
210228

211229
$this->assertNull( $result );
212230
}
@@ -220,7 +238,7 @@ public function test_register_rejects_empty_name() {
220238
$args = self::$default_args;
221239
$args['name'] = '';
222240

223-
$result = $this->registry->register( 'empty_name', $args );
241+
$result = $this->registry->register( 'empty-name', $args );
224242

225243
$this->assertNull( $result );
226244
}
@@ -234,7 +252,7 @@ public function test_register_rejects_missing_type() {
234252
$args = self::$default_args;
235253
unset( $args['type'] );
236254

237-
$result = $this->registry->register( 'no_type', $args );
255+
$result = $this->registry->register( 'no-type', $args );
238256

239257
$this->assertNull( $result );
240258
}
@@ -248,7 +266,7 @@ public function test_register_rejects_missing_authentication() {
248266
$args = self::$default_args;
249267
unset( $args['authentication'] );
250268

251-
$result = $this->registry->register( 'no_auth', $args );
269+
$result = $this->registry->register( 'no-auth', $args );
252270

253271
$this->assertNull( $result );
254272
}
@@ -262,7 +280,7 @@ public function test_register_rejects_invalid_auth_method() {
262280
$args = self::$default_args;
263281
$args['authentication']['method'] = 'oauth';
264282

265-
$result = $this->registry->register( 'bad_auth', $args );
283+
$result = $this->registry->register( 'bad-auth', $args );
266284

267285
$this->assertNull( $result );
268286
}
@@ -287,9 +305,9 @@ public function test_is_registered_returns_false_for_unregistered() {
287305
* @ticket 64791
288306
*/
289307
public function test_get_registered_returns_connector_data() {
290-
$this->registry->register( 'my_connector', self::$default_args );
308+
$this->registry->register( 'my-connector', self::$default_args );
291309

292-
$result = $this->registry->get_registered( 'my_connector' );
310+
$result = $this->registry->get_registered( 'my-connector' );
293311

294312
$this->assertIsArray( $result );
295313
$this->assertSame( 'Test Provider', $result['name'] );
@@ -334,13 +352,13 @@ public function test_get_all_registered_returns_empty_when_none() {
334352
* @ticket 64791
335353
*/
336354
public function test_unregister_removes_connector() {
337-
$this->registry->register( 'to_remove', self::$default_args );
355+
$this->registry->register( 'to-remove', self::$default_args );
338356

339-
$result = $this->registry->unregister( 'to_remove' );
357+
$result = $this->registry->unregister( 'to-remove' );
340358

341359
$this->assertIsArray( $result );
342360
$this->assertSame( 'Test Provider', $result['name'] );
343-
$this->assertFalse( $this->registry->is_registered( 'to_remove' ) );
361+
$this->assertFalse( $this->registry->is_registered( 'to-remove' ) );
344362
}
345363

346364
/**

0 commit comments

Comments
 (0)