| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Connectors API. 4 * 5 * @package WordPress 6 * @subpackage Connectors 7 * @since 7.0.0 8 */ 9 10 use WordPress\AiClient\AiClient; 11 use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication; 12 13 /** 14 * Checks if a connector is registered. 15 * 16 * @since 7.0.0 17 * 18 * @see WP_Connector_Registry::is_registered() 19 * 20 * @param string $id The connector identifier. 21 * @return bool True if the connector is registered, false otherwise. 22 */ 23 function wp_is_connector_registered( string $id ): bool { 24 $registry = WP_Connector_Registry::get_instance(); 25 if ( null === $registry ) { 26 return false; 27 } 28 29 return $registry->is_registered( $id ); 30 } 31 32 /** 33 * Retrieves a registered connector. 34 * 35 * @since 7.0.0 36 * 37 * @see WP_Connector_Registry::get_registered() 38 * 39 * @param string $id The connector identifier. 40 * @return array|null { 41 * Connector data, or null if not registered. 42 * 43 * @type string $name The connector's display name. 44 * @type string $description The connector's description. 45 * @type string $logo_url Optional. URL to the connector's logo image. 46 * @type string $type The connector type, e.g. 'ai_provider'. 47 * @type array $authentication { 48 * Authentication configuration. When method is 'api_key', includes 49 * credentials_url, setting_name, and optionally constant_name and 50 * env_var_name. When 'none', only method is present. 51 * 52 * @type string $method The authentication method: 'api_key' or 'none'. 53 * @type string $credentials_url Optional. URL where users can obtain API credentials. 54 * @type string $setting_name Optional. The setting name for the API key. 55 * @type string $constant_name Optional. PHP constant name for the API key. 56 * @type string $env_var_name Optional. Environment variable name for the API key. 57 * } 58 * @type array $plugin { 59 * Optional. Plugin data for install/activate UI. 60 * 61 * @type string $file The plugin's main file path relative to the plugins 62 * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). 63 * @type callable $is_active Callback to determine whether the plugin is active. Receives no arguments and must return bool. 64 * Defaults to `__return_true`. 65 * } 66 * } 67 * @phpstan-return ?array{ 68 * name: non-empty-string, 69 * description: string, 70 * logo_url?: non-empty-string, 71 * type: non-empty-string, 72 * authentication: array{ 73 * method: 'api_key'|'none', 74 * credentials_url?: non-empty-string, 75 * setting_name?: non-empty-string, 76 * constant_name?: non-empty-string, 77 * env_var_name?: non-empty-string 78 * }, 79 * plugin: array{ 80 * file?: non-empty-string, 81 * is_active: callable(): bool, 82 * } 83 * } 84 */ 85 function wp_get_connector( string $id ): ?array { 86 $registry = WP_Connector_Registry::get_instance(); 87 if ( null === $registry ) { 88 return null; 89 } 90 91 return $registry->get_registered( $id ); 92 } 93 94 /** 95 * Retrieves all registered connectors. 96 * 97 * @since 7.0.0 98 * 99 * @see WP_Connector_Registry::get_all_registered() 100 * 101 * @return array { 102 * Connector settings keyed by connector ID. 103 * 104 * @type array ...$0 { 105 * Data for a single connector. 106 * 107 * @type string $name The connector's display name. 108 * @type string $description The connector's description. 109 * @type string $logo_url Optional. URL to the connector's logo image. 110 * @type string $type The connector type, e.g. 'ai_provider'. 111 * @type array $authentication { 112 * Authentication configuration. When method is 'api_key', includes 113 * credentials_url, setting_name, and optionally constant_name and 114 * env_var_name. When 'none', only method is present. 115 * 116 * @type string $method The authentication method: 'api_key' or 'none'. 117 * @type string $credentials_url Optional. URL where users can obtain API credentials. 118 * @type string $setting_name Optional. The setting name for the API key. 119 * @type string $constant_name Optional. PHP constant name for the API key. 120 * @type string $env_var_name Optional. Environment variable name for the API key. 121 * } 122 * @type array $plugin { 123 * Optional. Plugin data for install/activate UI. 124 * 125 * @type string $file The plugin's main file path relative to the plugins 126 * directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php'). 127 * @type callable $is_active Callback to determine whether the plugin is active. Receives no arguments and must return bool. 128 * Defaults to `__return_true`. 129 * } 130 * } 131 * } 132 * @phpstan-return array<string, array{ 133 * name: non-empty-string, 134 * description: string, 135 * logo_url?: non-empty-string, 136 * type: non-empty-string, 137 * authentication: array{ 138 * method: 'api_key'|'none', 139 * credentials_url?: non-empty-string, 140 * setting_name?: non-empty-string, 141 * constant_name?: non-empty-string, 142 * env_var_name?: non-empty-string 143 * }, 144 * plugin: array{ 145 * file?: non-empty-string, 146 * is_active: callable(): bool, 147 * } 148 * }> 149 */ 150 function wp_get_connectors(): array { 151 $registry = WP_Connector_Registry::get_instance(); 152 if ( null === $registry ) { 153 return array(); 154 } 155 156 return $registry->get_all_registered(); 157 } 158 159 /** 160 * Resolves an AI provider logo file path to a URL. 161 * 162 * Converts an absolute file path to a plugin URL. The path must reside within 163 * the plugins or must-use plugins directory. 164 * 165 * @since 7.0.0 166 * @access private 167 * 168 * @param string $path Absolute path to the logo file. 169 * @return non-empty-string|null The URL to the logo file, or null if the path is invalid. 170 */ 171 function _wp_connectors_resolve_ai_provider_logo_url( string $path ): ?string { 172 if ( ! $path ) { 173 return null; 174 } 175 176 $path = wp_normalize_path( $path ); 177 178 if ( ! file_exists( $path ) ) { 179 return null; 180 } 181 182 $mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR ); 183 if ( str_starts_with( $path, $mu_plugin_dir . '/' ) ) { 184 $logo_url = plugins_url( substr( $path, strlen( $mu_plugin_dir ) ), WPMU_PLUGIN_DIR . '/.' ); 185 return $logo_url ? $logo_url : null; 186 } 187 188 $plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ); 189 if ( str_starts_with( $path, $plugin_dir . '/' ) ) { 190 $logo_url = plugins_url( substr( $path, strlen( $plugin_dir ) ) ); 191 return $logo_url ? $logo_url : null; 192 } 193 194 _doing_it_wrong( 195 __FUNCTION__, 196 __( 'Provider logo path must be located within the plugins or must-use plugins directory.' ), 197 '7.0.0' 198 ); 199 200 return null; 201 } 202 203 /** 204 * Initializes the connector registry with default connectors and fires the registration action. 205 * 206 * Creates the registry instance, registers built-in connectors (which cannot be unhooked), 207 * and then fires the `wp_connectors_init` action for plugins to register their own connectors. 208 * 209 * @since 7.0.0 210 * @access private 211 */ 212 function _wp_connectors_init(): void { 213 $registry = new WP_Connector_Registry(); 214 WP_Connector_Registry::set_instance( $registry ); 215 216 // Only register default AI providers if AI support is enabled. 217 if ( wp_supports_ai() ) { 218 _wp_connectors_register_default_ai_providers( $registry ); 219 } 220 221 // Non-AI default connectors. 222 $registry->register( 223 'akismet', 224 array( 225 'name' => __( 'Akismet Anti-spam' ), 226 'description' => __( 'Protect your site from spam.' ), 227 'type' => 'spam_filtering', 228 'plugin' => array( 229 'file' => 'akismet/akismet.php', 230 'is_active' => static function () { 231 return defined( 'AKISMET_VERSION' ); 232 }, 233 ), 234 'authentication' => array( 235 'method' => 'api_key', 236 'credentials_url' => 'https://akismet.com/get/', 237 'setting_name' => 'wordpress_api_key', 238 'constant_name' => 'WPCOM_API_KEY', 239 ), 240 ) 241 ); 242 243 /** 244 * Fires when the connector registry is ready for plugins to register connectors. 245 * 246 * Built-in connectors and any AI providers auto-discovered from the WP AI Client 247 * registry have already been registered at this point and cannot be unhooked. 248 * 249 * AI provider plugins that register with the WP AI Client do not need to use 250 * this action — their connectors are created automatically. This action is 251 * primarily for registering non-AI-provider connectors or overriding metadata 252 * on existing connectors. 253 * 254 * Use `$registry->register()` within this action to add new connectors. 255 * To override an existing connector, unregister it first, then re-register 256 * with updated data. 257 * 258 * Example — overriding metadata on an auto-discovered connector: 259 * 260 * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) { 261 * if ( $registry->is_registered( 'anthropic' ) ) { 262 * $connector = $registry->unregister( 'anthropic' ); 263 * $connector['description'] = __( 'Custom description for Anthropic.', 'my-plugin' ); 264 * $registry->register( 'anthropic', $connector ); 265 * } 266 * } ); 267 * 268 * @since 7.0.0 269 * 270 * @param WP_Connector_Registry $registry Connector registry instance. 271 */ 272 do_action( 'wp_connectors_init', $registry ); 273 } 274 275 /** 276 * Registers connectors for the built-in AI providers. 277 * 278 * @since 7.0.0 279 * @access private 280 * 281 * @param WP_Connector_Registry $registry The connector registry instance. 282 */ 283 function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $registry ): void { 284 // Built-in connectors. 285 $defaults = array( 286 'anthropic' => array( 287 'name' => 'Anthropic', 288 'description' => __( 'Text generation with Claude.' ), 289 'type' => 'ai_provider', 290 'plugin' => array( 291 'file' => 'ai-provider-for-anthropic/plugin.php', 292 ), 293 'authentication' => array( 294 'method' => 'api_key', 295 'credentials_url' => 'https://platform.claude.com/settings/keys', 296 ), 297 ), 298 'google' => array( 299 'name' => 'Google', 300 'description' => __( 'Text and image generation with Gemini and Imagen.' ), 301 'type' => 'ai_provider', 302 'plugin' => array( 303 'file' => 'ai-provider-for-google/plugin.php', 304 ), 305 'authentication' => array( 306 'method' => 'api_key', 307 'credentials_url' => 'https://aistudio.google.com/api-keys', 308 ), 309 ), 310 'openai' => array( 311 'name' => 'OpenAI', 312 'description' => __( 'Text and image generation with GPT and Dall-E.' ), 313 'type' => 'ai_provider', 314 'plugin' => array( 315 'file' => 'ai-provider-for-openai/plugin.php', 316 ), 317 'authentication' => array( 318 'method' => 'api_key', 319 'credentials_url' => 'https://platform.openai.com/api-keys', 320 ), 321 ), 322 ); 323 324 // Merge AI Client registry data on top of defaults. 325 // Registry values (from provider plugins) take precedence over hardcoded fallbacks. 326 $ai_registry = AiClient::defaultRegistry(); 327 328 foreach ( array_filter( $ai_registry->getRegisteredProviderIds() ) as $connector_id ) { 329 $provider_class_name = $ai_registry->getProviderClassName( $connector_id ); 330 $provider_metadata = $provider_class_name::metadata(); 331 332 $auth_method = $provider_metadata->getAuthenticationMethod(); 333 $is_api_key = null !== $auth_method && $auth_method->isApiKey(); 334 335 if ( $is_api_key ) { 336 $credentials_url = $provider_metadata->getCredentialsUrl(); 337 $authentication = array( 338 'method' => 'api_key', 339 ); 340 if ( $credentials_url ) { 341 $authentication['credentials_url'] = $credentials_url; 342 } 343 } else { 344 $authentication = array( 'method' => 'none' ); 345 } 346 347 $name = $provider_metadata->getName(); 348 $description = $provider_metadata->getDescription(); 349 $logo_url = $provider_metadata->getLogoPath() 350 ? _wp_connectors_resolve_ai_provider_logo_url( $provider_metadata->getLogoPath() ) 351 : null; 352 353 if ( isset( $defaults[ $connector_id ] ) ) { 354 // Override fields with non-empty registry values. 355 if ( $name ) { 356 $defaults[ $connector_id ]['name'] = $name; 357 } 358 if ( $description ) { 359 $defaults[ $connector_id ]['description'] = $description; 360 } 361 if ( $logo_url ) { 362 $defaults[ $connector_id ]['logo_url'] = $logo_url; 363 } 364 // Always update auth method; keep existing credentials_url as fallback. 365 $defaults[ $connector_id ]['authentication']['method'] = $authentication['method']; 366 if ( ! empty( $authentication['credentials_url'] ) ) { 367 $defaults[ $connector_id ]['authentication']['credentials_url'] = $authentication['credentials_url']; 368 } 369 } else { 370 $defaults[ $connector_id ] = array( 371 'name' => $name ? $name : ucwords( $connector_id ), 372 'description' => $description ? $description : '', 373 'type' => 'ai_provider', 374 'authentication' => $authentication, 375 ); 376 if ( $logo_url ) { 377 $defaults[ $connector_id ]['logo_url'] = $logo_url; 378 } 379 } 380 } 381 382 // Register all default connectors directly on the registry. 383 foreach ( $defaults as $id => $args ) { 384 if ( 'api_key' === $args['authentication']['method'] ) { 385 $sanitized_id = str_replace( '-', '_', $id ); 386 387 $args['authentication']['setting_name'] = "connectors_ai_{$sanitized_id}_api_key"; 388 389 // All AI providers use the {CONSTANT_CASE_ID}_API_KEY naming convention. 390 $constant_case_key = strtoupper( (string) preg_replace( '/([a-z])([A-Z])/', '$1_$2', $sanitized_id ) ) . '_API_KEY'; 391 392 $args['authentication']['constant_name'] = $constant_case_key; 393 $args['authentication']['env_var_name'] = $constant_case_key; 394 } 395 396 $args['plugin']['is_active'] = static function () use ( $ai_registry, $id ): bool { 397 try { 398 return $ai_registry->hasProvider( $id ); 399 } catch ( Exception $e ) { 400 return false; 401 } 402 }; 403 404 $registry->register( $id, $args ); 405 } 406 } 407 408 /** 409 * Masks an API key, showing only the last 4 characters. 410 * 411 * @since 7.0.0 412 * @access private 413 * 414 * @param string $key The API key to mask. 415 * @return string The masked key, e.g. "************fj39". 416 */ 417 function _wp_connectors_mask_api_key( string $key ): string { 418 if ( strlen( $key ) <= 4 ) { 419 return $key; 420 } 421 422 return str_repeat( "\u{2022}", min( strlen( $key ) - 4, 16 ) ) . substr( $key, -4 ); 423 } 424 425 /** 426 * Determines the source of an API key for a given connector. 427 * 428 * Checks in order: environment variable, PHP constant, database. 429 * Environment variable and constant are only checked when their 430 * respective names are provided. 431 * 432 * @since 7.0.0 433 * @access private 434 * 435 * @param string $setting_name The option name for the API key (e.g., 'connectors_spam_filtering_my_plugin_api_key'). 436 * @param string $env_var_name Optional. Environment variable name to check (e.g., 'MY_PLUGIN_API_KEY'). 437 * @param string $constant_name Optional. PHP constant name to check (e.g., 'MY_PLUGIN_API_KEY'). 438 * @return string The key source: 'env', 'constant', 'database', or 'none'. 439 */ 440 function _wp_connectors_get_api_key_source( string $setting_name, string $env_var_name = '', string $constant_name = '' ): string { 441 // Check environment variable first. 442 if ( '' !== $env_var_name ) { 443 $env_value = getenv( $env_var_name ); 444 if ( false !== $env_value && '' !== $env_value ) { 445 return 'env'; 446 } 447 } 448 449 // Check PHP constant. 450 if ( '' !== $constant_name && defined( $constant_name ) ) { 451 $const_value = constant( $constant_name ); 452 if ( is_string( $const_value ) && '' !== $const_value ) { 453 return 'constant'; 454 } 455 } 456 457 // Check database. 458 $db_value = get_option( $setting_name, '' ); 459 if ( '' !== $db_value ) { 460 return 'database'; 461 } 462 463 return 'none'; 464 } 465 466 /** 467 * Checks whether an API key is valid for a given provider. 468 * 469 * @since 7.0.0 470 * @access private 471 * 472 * @param string $key The API key to check. 473 * @param string $provider_id The WP AI client provider ID. 474 * @return bool|null True if valid, false if invalid, null if unable to determine. 475 */ 476 function _wp_connectors_is_ai_api_key_valid( string $key, string $provider_id ): ?bool { 477 try { 478 $registry = AiClient::defaultRegistry(); 479 480 if ( ! $registry->hasProvider( $provider_id ) ) { 481 _doing_it_wrong( 482 __FUNCTION__, 483 sprintf( 484 /* translators: %s: AI provider ID. */ 485 __( 'The provider "%s" is not registered in the AI client registry.' ), 486 $provider_id 487 ), 488 '7.0.0' 489 ); 490 return null; 491 } 492 493 $registry->setProviderRequestAuthentication( 494 $provider_id, 495 new ApiKeyRequestAuthentication( $key ) 496 ); 497 498 return $registry->isProviderConfigured( $provider_id ); 499 } catch ( Exception $e ) { 500 wp_trigger_error( __FUNCTION__, $e->getMessage() ); 501 return null; 502 } 503 } 504 505 /** 506 * Masks and validates connector API keys in REST responses. 507 * 508 * On every `/wp/v2/settings` response, masks connector API key values so raw 509 * keys are never exposed via the REST API. 510 * 511 * On POST or PUT requests, validates each updated key against the provider 512 * before masking. If validation fails, the key is reverted to an empty string. 513 * 514 * @since 7.0.0 515 * @access private 516 * 517 * @param WP_REST_Response $response The response object. 518 * @param WP_REST_Server $server The server instance. 519 * @param WP_REST_Request $request The request object. 520 * @return WP_REST_Response The modified response with masked/validated keys. 521 */ 522 function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_REST_Server $server, WP_REST_Request $request ): WP_REST_Response { 523 if ( '/wp/v2/settings' !== $request->get_route() ) { 524 return $response; 525 } 526 527 $data = $response->get_data(); 528 if ( ! is_array( $data ) ) { 529 return $response; 530 } 531 532 $is_update = 'POST' === $request->get_method() || 'PUT' === $request->get_method(); 533 534 foreach ( wp_get_connectors() as $connector_id => $connector_data ) { 535 $auth = $connector_data['authentication']; 536 if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) { 537 continue; 538 } 539 540 $setting_name = $auth['setting_name']; 541 if ( ! array_key_exists( $setting_name, $data ) ) { 542 continue; 543 } 544 545 $value = $data[ $setting_name ]; 546 547 // On update, validate AI provider keys before masking. 548 // Non-AI connectors accept keys as-is; the service plugin handles its own validation. 549 if ( $is_update && is_string( $value ) && '' !== $value && 'ai_provider' === $connector_data['type'] ) { 550 if ( true !== _wp_connectors_is_ai_api_key_valid( $value, $connector_id ) ) { 551 update_option( $setting_name, '' ); 552 $data[ $setting_name ] = ''; 553 continue; 554 } 555 } 556 557 // Mask the key in the response. 558 if ( is_string( $value ) && '' !== $value ) { 559 $data[ $setting_name ] = _wp_connectors_mask_api_key( $value ); 560 } 561 } 562 563 $response->set_data( $data ); 564 return $response; 565 } 566 add_filter( 'rest_post_dispatch', '_wp_connectors_rest_settings_dispatch', 10, 3 ); 567 568 /** 569 * Registers default connector settings. 570 * 571 * @since 7.0.0 572 * @access private 573 */ 574 function _wp_register_default_connector_settings(): void { 575 $registered_settings = get_registered_settings(); 576 577 foreach ( wp_get_connectors() as $connector_data ) { 578 $auth = $connector_data['authentication']; 579 if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) { 580 continue; 581 } 582 583 // Skip if the setting is already registered (e.g. by an owning plugin). 584 if ( isset( $registered_settings[ $auth['setting_name'] ] ) ) { 585 continue; 586 } 587 588 if ( ! isset( $connector_data['plugin']['is_active'] ) || ! is_callable( $connector_data['plugin']['is_active'] ) ) { 589 continue; 590 } 591 592 if ( ! call_user_func( $connector_data['plugin']['is_active'] ) ) { 593 continue; 594 } 595 596 register_setting( 597 'connectors', 598 $auth['setting_name'], 599 array( 600 'type' => 'string', 601 'label' => sprintf( 602 /* translators: %s: Connector name. */ 603 __( '%s API Key' ), 604 $connector_data['name'] 605 ), 606 'description' => sprintf( 607 /* translators: %s: Connector name. */ 608 __( 'API key for the %s connector.' ), 609 $connector_data['name'] 610 ), 611 'default' => '', 612 'show_in_rest' => true, 613 'sanitize_callback' => 'sanitize_text_field', 614 ) 615 ); 616 } 617 } 618 add_action( 'init', '_wp_register_default_connector_settings', 20 ); 619 620 /** 621 * Passes stored connector API keys to the WP AI client. 622 * 623 * @since 7.0.0 624 * @access private 625 */ 626 function _wp_connectors_pass_default_keys_to_ai_client(): void { 627 try { 628 $ai_registry = AiClient::defaultRegistry(); 629 foreach ( wp_get_connectors() as $connector_id => $connector_data ) { 630 if ( 'ai_provider' !== $connector_data['type'] ) { 631 continue; 632 } 633 634 $auth = $connector_data['authentication']; 635 if ( 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) { 636 continue; 637 } 638 639 if ( ! $ai_registry->hasProvider( $connector_id ) ) { 640 continue; 641 } 642 643 // Skip if the key is already provided via env var or constant. 644 $key_source = _wp_connectors_get_api_key_source( $auth['setting_name'], $auth['env_var_name'] ?? '', $auth['constant_name'] ?? '' ); 645 if ( 'env' === $key_source || 'constant' === $key_source ) { 646 continue; 647 } 648 649 $api_key = get_option( $auth['setting_name'], '' ); 650 if ( ! is_string( $api_key ) || '' === $api_key ) { 651 continue; 652 } 653 654 $ai_registry->setProviderRequestAuthentication( 655 $connector_id, 656 new ApiKeyRequestAuthentication( $api_key ) 657 ); 658 } 659 } catch ( Exception $e ) { 660 wp_trigger_error( __FUNCTION__, $e->getMessage() ); 661 } 662 } 663 add_action( 'init', '_wp_connectors_pass_default_keys_to_ai_client', 20 ); 664 665 /** 666 * Exposes connector settings to the connectors-wp-admin script module. 667 * 668 * @since 7.0.0 669 * @access private 670 * 671 * @param array<string, mixed> $data Existing script module data. 672 * @return array<string, mixed> Script module data with connectors added. 673 */ 674 function _wp_connectors_get_connector_script_module_data( array $data ): array { 675 $registry = AiClient::defaultRegistry(); 676 677 if ( ! function_exists( 'validate_plugin' ) ) { 678 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 679 } 680 681 $connectors = array(); 682 foreach ( wp_get_connectors() as $connector_id => $connector_data ) { 683 $auth = $connector_data['authentication']; 684 $auth_out = array( 'method' => $auth['method'] ); 685 686 if ( 'api_key' === $auth['method'] ) { 687 $auth_out['settingName'] = $auth['setting_name'] ?? ''; 688 $auth_out['credentialsUrl'] = $auth['credentials_url'] ?? null; 689 $key_source = _wp_connectors_get_api_key_source( $auth['setting_name'] ?? '', $auth['env_var_name'] ?? '', $auth['constant_name'] ?? '' ); 690 $auth_out['keySource'] = $key_source; 691 692 if ( 'ai_provider' === $connector_data['type'] ) { 693 try { 694 $auth_out['isConnected'] = $registry->hasProvider( $connector_id ) && $registry->isProviderConfigured( $connector_id ); 695 } catch ( Exception $e ) { 696 $auth_out['isConnected'] = false; 697 } 698 } else { 699 $auth_out['isConnected'] = 'none' !== $key_source; 700 } 701 } 702 703 $connector_out = array( 704 'name' => $connector_data['name'], 705 'description' => $connector_data['description'], 706 'logoUrl' => ! empty( $connector_data['logo_url'] ) ? $connector_data['logo_url'] : null, 707 'type' => $connector_data['type'], 708 'authentication' => $auth_out, 709 ); 710 711 if ( ! empty( $connector_data['plugin']['file'] ) ) { 712 $file = $connector_data['plugin']['file']; 713 $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] ); 714 $is_installed = $is_activated || 0 === validate_plugin( $file ); 715 716 $connector_out['plugin'] = array( 717 'file' => $file, 718 'isInstalled' => $is_installed, 719 'isActivated' => $is_activated, 720 ); 721 } 722 723 $connectors[ $connector_id ] = $connector_out; 724 } 725 ksort( $connectors ); 726 $data['connectors'] = $connectors; 727 $data['isFileModDisabled'] = ! wp_is_file_mod_allowed( 'install_plugins' ); 728 return $data; 729 } 730 add_filter( 'script_module_data_options-connectors-wp-admin', '_wp_connectors_get_connector_script_module_data' );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Jun 13 09:38:55 2026 | Cross-referenced by PHPXref |