Skip to content

Commit ac2597e

Browse files
Merge branch 'trunk' into perf/10777
2 parents 391f5b7 + d141ecb commit ac2597e

4 files changed

Lines changed: 205 additions & 18 deletions

File tree

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
* env_var_name?: non-empty-string
4141
* },
4242
* plugin?: array{
43-
* file: non-empty-string
43+
* file: non-empty-string,
44+
* is_active?: callable(): bool
4445
* }
4546
* }
4647
*/
@@ -109,8 +110,12 @@ final class WP_Connector_Registry {
109110
* @type array $plugin {
110111
* Optional. Plugin data for install/activate UI.
111112
*
112-
* @type string $file The plugin's main file path relative to the plugins
113-
* directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php').
113+
* @type string $file Optional. The plugin's main file path relative to the
114+
* plugins directory (e.g. 'my-plugin/my-plugin.php' or
115+
* 'hello.php').
116+
* @type callable $is_active Optional callback to determine whether the plugin
117+
* is active. Receives no arguments and must return bool.
118+
* Defaults to `__return_true`.
114119
* }
115120
* }
116121
* @return array|null The registered connector data on success, null on failure.
@@ -243,8 +248,30 @@ public function register( string $id, array $args ): ?array {
243248
}
244249
}
245250

246-
if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) && ! empty( $args['plugin']['file'] ) ) {
247-
$connector['plugin'] = array( 'file' => $args['plugin']['file'] );
251+
$connector['plugin'] = array();
252+
253+
if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {
254+
if ( ! empty( $args['plugin']['file'] ) ) {
255+
$connector['plugin']['file'] = $args['plugin']['file'];
256+
}
257+
258+
if ( isset( $args['plugin']['is_active'] ) ) {
259+
if ( ! is_callable( $args['plugin']['is_active'] ) ) {
260+
_doing_it_wrong(
261+
__METHOD__,
262+
/* translators: %s: Connector ID. */
263+
sprintf( __( 'Connector "%s" plugin is_active must be callable.' ), esc_html( $id ) ),
264+
'7.0.0'
265+
);
266+
return null;
267+
}
268+
269+
$connector['plugin']['is_active'] = $args['plugin']['is_active'];
270+
}
271+
}
272+
273+
if ( ! isset( $connector['plugin']['is_active'] ) ) {
274+
$connector['plugin']['is_active'] = '__return_true';
248275
}
249276

250277
$this->registered_connectors[ $id ] = $connector;

src/wp-includes/connectors.php

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,17 @@ function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $re
367367
}
368368
}
369369
}
370+
371+
if ( ! isset( $args['plugin']['is_active'] ) ) {
372+
$args['plugin']['is_active'] = static function () use ( $ai_registry, $id ): bool {
373+
try {
374+
return $ai_registry->hasProvider( $id );
375+
} catch ( Exception $e ) {
376+
return false;
377+
}
378+
};
379+
}
380+
370381
$registry->register( $id, $args );
371382
}
372383
}
@@ -538,10 +549,9 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R
538549
* @access private
539550
*/
540551
function _wp_register_default_connector_settings(): void {
541-
$ai_registry = AiClient::defaultRegistry();
542552
$registered_settings = get_registered_settings();
543553

544-
foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
554+
foreach ( wp_get_connectors() as $connector_data ) {
545555
$auth = $connector_data['authentication'];
546556
if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
547557
continue;
@@ -552,8 +562,11 @@ function _wp_register_default_connector_settings(): void {
552562
continue;
553563
}
554564

555-
// For AI providers, skip if the provider is not in the AI Client registry.
556-
if ( 'ai_provider' === $connector_data['type'] && ! $ai_registry->hasProvider( $connector_id ) ) {
565+
if ( ! isset( $connector_data['plugin']['is_active'] ) || ! is_callable( $connector_data['plugin']['is_active'] ) ) {
566+
continue;
567+
}
568+
569+
if ( ! call_user_func( $connector_data['plugin']['is_active'] ) ) {
557570
continue;
558571
}
559572

@@ -638,10 +651,6 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
638651
function _wp_connectors_get_connector_script_module_data( array $data ): array {
639652
$registry = AiClient::defaultRegistry();
640653

641-
if ( ! function_exists( 'is_plugin_active' ) ) {
642-
require_once ABSPATH . 'wp-admin/includes/plugin.php';
643-
}
644-
645654
$connectors = array();
646655
foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
647656
$auth = $connector_data['authentication'];
@@ -674,8 +683,8 @@ function _wp_connectors_get_connector_script_module_data( array $data ): array {
674683

675684
if ( ! empty( $connector_data['plugin']['file'] ) ) {
676685
$file = $connector_data['plugin']['file'];
677-
$is_installed = file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) );
678-
$is_activated = $is_installed && is_plugin_active( $file );
686+
$is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] );
687+
$is_installed = $is_activated || file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) );
679688

680689
$connector_out['plugin'] = array(
681690
'file' => $file,

tests/phpunit/tests/connectors/wpConnectorRegistry.php

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,16 +299,66 @@ public function test_register_includes_plugin_data() {
299299
$result = $this->registry->register( 'with-plugin', $args );
300300

301301
$this->assertArrayHasKey( 'plugin', $result );
302-
$this->assertSame( array( 'file' => 'my-plugin/my-plugin.php' ), $result['plugin'] );
302+
$this->assertSame( 'my-plugin/my-plugin.php', $result['plugin']['file'] );
303+
}
304+
305+
/**
306+
* @ticket 65020
307+
*/
308+
public function test_register_stores_plugin_is_active_callback() {
309+
$args = self::$default_args;
310+
$args['plugin'] = array(
311+
'file' => 'my-plugin/my-plugin.php',
312+
'is_active' => '__return_true',
313+
);
314+
315+
$result = $this->registry->register( 'with-callback', $args );
316+
317+
$this->assertIsArray( $result );
318+
$this->assertArrayHasKey( 'is_active', $result['plugin'] );
319+
$this->assertIsCallable( $result['plugin']['is_active'] );
320+
}
321+
322+
/**
323+
* @ticket 65020
324+
*/
325+
public function test_register_rejects_non_callable_plugin_is_active() {
326+
$this->setExpectedIncorrectUsage( 'WP_Connector_Registry::register' );
327+
328+
$args = self::$default_args;
329+
$args['plugin'] = array(
330+
'file' => 'my-plugin/my-plugin.php',
331+
'is_active' => 'not_a_real_function_name',
332+
);
333+
334+
$result = $this->registry->register( 'bad-callback', $args );
335+
336+
$this->assertNull( $result );
337+
}
338+
339+
/**
340+
* @ticket 65020
341+
*/
342+
public function test_register_defaults_plugin_is_active_to_return_true() {
343+
$args = self::$default_args;
344+
$args['plugin'] = array( 'file' => 'my-plugin/my-plugin.php' );
345+
346+
$result = $this->registry->register( 'default-callback', $args );
347+
348+
$this->assertIsArray( $result );
349+
$this->assertArrayHasKey( 'is_active', $result['plugin'] );
350+
$this->assertSame( '__return_true', $result['plugin']['is_active'] );
303351
}
304352

305353
/**
306354
* @ticket 64791
307355
*/
308-
public function test_register_omits_plugin_when_not_provided() {
356+
public function test_register_defaults_plugin_when_not_provided() {
309357
$result = $this->registry->register( 'no-plugin', self::$default_args );
310358

311-
$this->assertArrayNotHasKey( 'plugin', $result );
359+
$this->assertArrayHasKey( 'plugin', $result );
360+
$this->assertArrayNotHasKey( 'file', $result['plugin'] );
361+
$this->assertSame( '__return_true', $result['plugin']['is_active'] );
312362
}
313363

314364
/**
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/**
4+
* Tests for _wp_register_default_connector_settings().
5+
*
6+
* @group connectors
7+
* @covers ::_wp_register_default_connector_settings
8+
*/
9+
class Tests_Connectors_WpRegisterDefaultConnectorSettings extends WP_UnitTestCase {
10+
11+
const CONNECTOR_ID = 'wp_test_non_ai_connector';
12+
const SETTING_NAME = 'connectors_test_non_ai_api_key';
13+
14+
/**
15+
* Snapshot of registered settings before each test.
16+
*
17+
* @var array
18+
*/
19+
private array $original_registered_settings = array();
20+
21+
/**
22+
* Snapshots the registered settings before each test.
23+
*/
24+
public function set_up(): void {
25+
parent::set_up();
26+
27+
global $wp_registered_settings;
28+
$this->original_registered_settings = $wp_registered_settings;
29+
}
30+
31+
/**
32+
* Removes the test connector and restores registered settings.
33+
*/
34+
public function tear_down(): void {
35+
$registry = WP_Connector_Registry::get_instance();
36+
if ( null !== $registry && $registry->is_registered( self::CONNECTOR_ID ) ) {
37+
$registry->unregister( self::CONNECTOR_ID );
38+
}
39+
40+
global $wp_registered_settings;
41+
$wp_registered_settings = $this->original_registered_settings;
42+
43+
parent::tear_down();
44+
}
45+
46+
/**
47+
* @ticket 65099
48+
*/
49+
public function test_non_ai_connector_skipped_when_is_active_returns_false(): void {
50+
WP_Connector_Registry::get_instance()->register(
51+
self::CONNECTOR_ID,
52+
array(
53+
'name' => 'Test Non-AI Connector',
54+
'description' => '',
55+
'type' => 'spam_filtering',
56+
'authentication' => array(
57+
'method' => 'api_key',
58+
'setting_name' => self::SETTING_NAME,
59+
),
60+
'plugin' => array(
61+
'file' => 'test/test.php',
62+
'is_active' => static function (): bool {
63+
return false;
64+
},
65+
),
66+
)
67+
);
68+
69+
_wp_register_default_connector_settings();
70+
71+
$this->assertArrayNotHasKey( self::SETTING_NAME, get_registered_settings() );
72+
}
73+
74+
/**
75+
* @ticket 65099
76+
*/
77+
public function test_non_ai_connector_registers_setting_when_is_active_returns_true(): void {
78+
WP_Connector_Registry::get_instance()->register(
79+
self::CONNECTOR_ID,
80+
array(
81+
'name' => 'Test Non-AI Connector',
82+
'description' => '',
83+
'type' => 'spam_filtering',
84+
'authentication' => array(
85+
'method' => 'api_key',
86+
'setting_name' => self::SETTING_NAME,
87+
),
88+
'plugin' => array(
89+
'file' => 'test/test.php',
90+
'is_active' => static function (): bool {
91+
return true;
92+
},
93+
),
94+
)
95+
);
96+
97+
_wp_register_default_connector_settings();
98+
99+
$this->assertArrayHasKey( self::SETTING_NAME, get_registered_settings() );
100+
}
101+
}

0 commit comments

Comments
 (0)