[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-connector-registry.php (source)

   1  <?php
   2  /**
   3   * Connectors API: WP_Connector_Registry class.
   4   *
   5   * @package WordPress
   6   * @subpackage Connectors
   7   * @since 7.0.0
   8   */
   9  
  10  /**
  11   * Manages the registration and lookup of connectors.
  12   *
  13   * This is an internal class. Use the public API functions to interact with connectors:
  14   *
  15   *  - `wp_is_connector_registered()` — check if a connector exists.
  16   *  - `wp_get_connector()`           — retrieve a single connector's data.
  17   *  - `wp_get_connectors()`          — retrieve all registered connectors.
  18   *
  19   * Plugins receive the registry instance via the `wp_connectors_init` action
  20   * to register or override connectors directly.
  21   *
  22   * @since 7.0.0
  23   * @access private
  24   *
  25   * @see wp_is_connector_registered()
  26   * @see wp_get_connector()
  27   * @see wp_get_connectors()
  28   * @see _wp_connectors_init()
  29   *
  30   * @phpstan-type Connector array{
  31   *     name: non-empty-string,
  32   *     description: string,
  33   *     logo_url?: non-empty-string,
  34   *     type: non-empty-string,
  35   *     authentication: array{
  36   *         method: 'api_key'|'application_password'|'none',
  37   *         credentials_url?: non-empty-string,
  38   *         setting_name?: non-empty-string,
  39   *         constant_name?: non-empty-string,
  40   *         env_var_name?: non-empty-string
  41   *     },
  42   *     plugin: array{
  43   *         file?: non-empty-string,
  44   *         is_active: callable(): bool
  45   *     }
  46   * }
  47   */
  48  final class WP_Connector_Registry {
  49      /**
  50       * The singleton instance of the registry.
  51       *
  52       * @since 7.0.0
  53       */
  54      private static ?WP_Connector_Registry $instance = null;
  55  
  56      /**
  57       * Holds the registered connectors.
  58       *
  59       * Each connector is stored as an associative array with keys:
  60       * name, description, type, authentication, and optionally plugin.
  61       *
  62       * @since 7.0.0
  63       * @var array<string, array>
  64       * @phpstan-var array<string, Connector>
  65       */
  66      private array $registered_connectors = array();
  67  
  68      /**
  69       * Registers a new connector.
  70       *
  71       * Validates the provided arguments and stores the connector in the registry.
  72       * For connectors with `api_key` or `application_password` authentication, a
  73       * `setting_name` can be provided explicitly. When omitted, setting names are
  74       * automatically generated using the pattern `connectors_{$type}_{$id}_{$method}`,
  75       * with hyphens in the type and ID normalized to underscores. These setting
  76       * names are used for Settings API registration and REST API exposure.
  77       *
  78       * Registering a connector with an ID that is already registered will trigger a
  79       * `_doing_it_wrong()` notice and return `null`. To override an existing connector,
  80       * call `unregister()` first.
  81       *
  82       * @since 7.0.0
  83       *
  84       * @see WP_Connector_Registry::unregister()
  85       *
  86       * @param string $id   The unique connector identifier. Must match the pattern
  87       *                     `/^[a-z0-9_-]+$/` (lowercase alphanumeric, hyphens, and underscores only).
  88       * @param array  $args {
  89       *     An associative array of arguments for the connector.
  90       *
  91       *     @type string $name           Required. The connector's display name.
  92       *     @type string $description    Optional. The connector's description. Default empty string.
  93       *     @type string $logo_url       Optional. URL to the connector's logo image.
  94       *     @type string $type           Required. The connector type, e.g. 'ai_provider'.
  95       *     @type array  $authentication {
  96       *         Required. Authentication configuration.
  97       *
  98       *         @type string $method          Required. The authentication method: 'api_key',
  99       *                                       'application_password', or 'none'.
 100       *         @type string $credentials_url Optional. URL where users can obtain API credentials.
 101       *         @type string $setting_name    Optional. The setting name for the API key
 102       *                                       or application-password credentials. When
 103       *                                       omitted, auto-generated as
 104       *                                       `connectors_{$type}_{$id}_api_key` for API
 105       *                                       keys and `connectors_{$type}_{$id}_application_password`
 106       *                                       for application passwords.
 107       *                                       Must be a non-empty string when provided.
 108       *         @type string $constant_name   Optional. PHP constant name for the API key
 109       *                                       (e.g. 'ANTHROPIC_API_KEY') or for application-password
 110       *                                       credentials in `username:password` format. Only checked
 111       *                                       when provided.
 112       *         @type string $env_var_name    Optional. Environment variable name for the API key
 113       *                                       (e.g. 'ANTHROPIC_API_KEY') or for application-password
 114       *                                       credentials in `username:password` format. Only checked
 115       *                                       when provided.
 116       *     }
 117       *     @type array  $plugin         {
 118       *         Optional. Plugin data for install/activate UI.
 119       *
 120       *         @type string   $file      Optional. The plugin's main file path relative to the
 121       *                                   plugins directory (e.g. 'my-plugin/my-plugin.php' or
 122       *                                   'hello.php').
 123       *         @type callable $is_active Optional callback to determine whether the plugin
 124       *                                   is active. Receives no arguments and must return bool.
 125       *                                   Defaults to `__return_true`.
 126       *     }
 127       * }
 128       * @return array|null The registered connector data on success, null on failure.
 129       *
 130       * @phpstan-param lowercase-string&non-empty-string $id
 131       * @phpstan-param array{
 132       *     name: non-empty-string,
 133       *     description?: string,
 134       *     logo_url?: non-empty-string,
 135       *     type: non-empty-string,
 136       *     authentication: array{
 137       *         method: 'api_key'|'application_password'|'none',
 138       *         credentials_url?: non-empty-string,
 139       *         setting_name?: non-empty-string,
 140       *         constant_name?: non-empty-string,
 141       *         env_var_name?: non-empty-string
 142       *     },
 143       *     plugin?: array{
 144       *         file?: non-empty-string,
 145       *         is_active?: callable(): bool
 146       *     }
 147       * } $args
 148       * @phpstan-return Connector|null
 149       */
 150  	public function register( string $id, array $args ): ?array {
 151          if ( ! preg_match( '/^[a-z0-9_-]+$/', $id ) ) {
 152              _doing_it_wrong(
 153                  __METHOD__,
 154                  __(
 155                      'Connector ID must contain only lowercase alphanumeric characters, hyphens, and underscores.'
 156                  ),
 157                  '7.0.0'
 158              );
 159              return null;
 160          }
 161  
 162          if ( $this->is_registered( $id ) ) {
 163              _doing_it_wrong(
 164                  __METHOD__,
 165                  /* translators: %s: Connector ID. */
 166                  sprintf( __( 'Connector "%s" is already registered.' ), esc_html( $id ) ),
 167                  '7.0.0'
 168              );
 169              return null;
 170          }
 171  
 172          // Validate required fields.
 173          if ( empty( $args['name'] ) || ! is_string( $args['name'] ) ) {
 174              _doing_it_wrong(
 175                  __METHOD__,
 176                  /* translators: %s: Connector ID. */
 177                  sprintf( __( 'Connector "%s" requires a non-empty "name" string.' ), esc_html( $id ) ),
 178                  '7.0.0'
 179              );
 180              return null;
 181          }
 182  
 183          if ( empty( $args['type'] ) || ! is_string( $args['type'] ) ) {
 184              _doing_it_wrong(
 185                  __METHOD__,
 186                  /* translators: %s: Connector ID. */
 187                  sprintf( __( 'Connector "%s" requires a non-empty "type" string.' ), esc_html( $id ) ),
 188                  '7.0.0'
 189              );
 190              return null;
 191          }
 192  
 193          if ( ! isset( $args['authentication'] ) || ! is_array( $args['authentication'] ) ) {
 194              _doing_it_wrong(
 195                  __METHOD__,
 196                  /* translators: %s: Connector ID. */
 197                  sprintf( __( 'Connector "%s" requires an "authentication" array.' ), esc_html( $id ) ),
 198                  '7.0.0'
 199              );
 200              return null;
 201          }
 202  
 203          if ( empty( $args['authentication']['method'] ) || ! in_array( $args['authentication']['method'], array( 'api_key', 'application_password', 'none' ), true ) ) {
 204              _doing_it_wrong(
 205                  __METHOD__,
 206                  /* translators: %s: Connector ID. */
 207                  sprintf( __( 'Connector "%s" authentication method must be "api_key", "application_password", or "none".' ), esc_html( $id ) ),
 208                  '7.0.0'
 209              );
 210              return null;
 211          }
 212  
 213          if ( 'ai_provider' === $args['type'] && ! wp_supports_ai() ) {
 214              // No need for a `doing_it_wrong` as AI support is disabled intentionally.
 215              return null;
 216          }
 217  
 218          $connector = array(
 219              'name'           => $args['name'],
 220              'description'    => isset( $args['description'] ) && is_string( $args['description'] ) ? $args['description'] : '',
 221              'type'           => $args['type'],
 222              'authentication' => array(
 223                  'method' => $args['authentication']['method'],
 224              ),
 225          );
 226  
 227          if ( ! empty( $args['logo_url'] ) && is_string( $args['logo_url'] ) ) {
 228              $connector['logo_url'] = $args['logo_url'];
 229          }
 230  
 231          $requires_credentials = in_array( $args['authentication']['method'], array( 'api_key', 'application_password' ), true );
 232  
 233          if ( $requires_credentials ) {
 234              if ( ! empty( $args['authentication']['credentials_url'] ) && is_string( $args['authentication']['credentials_url'] ) ) {
 235                  $connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'];
 236              }
 237  
 238              if ( isset( $args['authentication']['setting_name'] ) ) {
 239                  if ( ! is_string( $args['authentication']['setting_name'] ) || '' === $args['authentication']['setting_name'] ) {
 240                      _doing_it_wrong(
 241                          __METHOD__,
 242                          /* translators: %s: Connector ID. */
 243                          sprintf( __( 'Connector "%s" authentication setting_name must be a non-empty string.' ), esc_html( $id ) ),
 244                          '7.0.0'
 245                      );
 246                      return null;
 247                  }
 248                  $connector['authentication']['setting_name'] = $args['authentication']['setting_name'];
 249              } else {
 250                  $connector['authentication']['setting_name'] = str_replace( '-', '_', "connectors_{$connector['type']}_{$id}_{$args['authentication']['method']}" );
 251              }
 252  
 253              if ( isset( $args['authentication']['constant_name'] ) ) {
 254                  if ( ! is_string( $args['authentication']['constant_name'] ) || '' === $args['authentication']['constant_name'] ) {
 255                      _doing_it_wrong(
 256                          __METHOD__,
 257                          /* translators: %s: Connector ID. */
 258                          sprintf( __( 'Connector "%s" authentication constant_name must be a non-empty string.' ), esc_html( $id ) ),
 259                          '7.0.0'
 260                      );
 261                      return null;
 262                  }
 263                  $connector['authentication']['constant_name'] = $args['authentication']['constant_name'];
 264              }
 265              if ( isset( $args['authentication']['env_var_name'] ) ) {
 266                  if ( ! is_string( $args['authentication']['env_var_name'] ) || '' === $args['authentication']['env_var_name'] ) {
 267                      _doing_it_wrong(
 268                          __METHOD__,
 269                          /* translators: %s: Connector ID. */
 270                          sprintf( __( 'Connector "%s" authentication env_var_name must be a non-empty string.' ), esc_html( $id ) ),
 271                          '7.0.0'
 272                      );
 273                      return null;
 274                  }
 275                  $connector['authentication']['env_var_name'] = $args['authentication']['env_var_name'];
 276              }
 277          }
 278  
 279          $connector['plugin'] = array();
 280  
 281          if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {
 282              if ( ! empty( $args['plugin']['file'] ) ) {
 283                  $connector['plugin']['file'] = $args['plugin']['file'];
 284              }
 285  
 286              if ( isset( $args['plugin']['is_active'] ) ) {
 287                  if ( ! is_callable( $args['plugin']['is_active'] ) ) {
 288                      _doing_it_wrong(
 289                          __METHOD__,
 290                          /* translators: %s: Connector ID. */
 291                          sprintf( __( 'Connector "%s" plugin is_active must be callable.' ), esc_html( $id ) ),
 292                          '7.0.0'
 293                      );
 294                      return null;
 295                  }
 296  
 297                  $connector['plugin']['is_active'] = $args['plugin']['is_active'];
 298              }
 299          }
 300  
 301          if ( ! isset( $connector['plugin']['is_active'] ) ) {
 302              $connector['plugin']['is_active'] = '__return_true';
 303          }
 304  
 305          $this->registered_connectors[ $id ] = $connector;
 306          return $connector;
 307      }
 308  
 309      /**
 310       * Unregisters a connector.
 311       *
 312       * Returns the connector data on success, which can be modified and passed
 313       * back to `register()` to override a connector's metadata.
 314       *
 315       * Triggers a `_doing_it_wrong()` notice if the connector is not registered.
 316       * Use `is_registered()` to check first when the connector may not exist.
 317       *
 318       * @since 7.0.0
 319       *
 320       * @see WP_Connector_Registry::register()
 321       * @see WP_Connector_Registry::is_registered()
 322       *
 323       * @param string $id The connector identifier.
 324       * @return array|null The unregistered connector data on success, null on failure.
 325       *
 326       * @phpstan-return Connector|null
 327       */
 328  	public function unregister( string $id ): ?array {
 329          if ( ! $this->is_registered( $id ) ) {
 330              _doing_it_wrong(
 331                  __METHOD__,
 332                  /* translators: %s: Connector ID. */
 333                  sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
 334                  '7.0.0'
 335              );
 336              return null;
 337          }
 338  
 339          $unregistered = $this->registered_connectors[ $id ];
 340          unset( $this->registered_connectors[ $id ] );
 341  
 342          return $unregistered;
 343      }
 344  
 345      /**
 346       * Retrieves the list of all registered connectors.
 347       *
 348       * Do not use this method directly. Instead, use the `wp_get_connectors()` function.
 349       *
 350       * @since 7.0.0
 351       *
 352       * @see wp_get_connectors()
 353       *
 354       * @return array Connector settings keyed by connector ID.
 355       *
 356       * @phpstan-return array<string, Connector>
 357       */
 358  	public function get_all_registered(): array {
 359          return $this->registered_connectors;
 360      }
 361  
 362      /**
 363       * Checks if a connector is registered.
 364       *
 365       * Do not use this method directly. Instead, use the `wp_is_connector_registered()` function.
 366       *
 367       * @since 7.0.0
 368       *
 369       * @see wp_is_connector_registered()
 370       *
 371       * @param string $id The connector identifier.
 372       * @return bool True if the connector is registered, false otherwise.
 373       */
 374  	public function is_registered( string $id ): bool {
 375          return isset( $this->registered_connectors[ $id ] );
 376      }
 377  
 378      /**
 379       * Retrieves a registered connector.
 380       *
 381       * Do not use this method directly. Instead, use the `wp_get_connector()` function.
 382       *
 383       * Triggers a `_doing_it_wrong()` notice if the connector is not registered.
 384       * Use `is_registered()` to check first when the connector may not exist.
 385       *
 386       * @since 7.0.0
 387       *
 388       * @see wp_get_connector()
 389       *
 390       * @param string $id The connector identifier.
 391       * @return array|null The registered connector data, or null if it is not registered.
 392       * @phpstan-return Connector|null
 393       */
 394  	public function get_registered( string $id ): ?array {
 395          if ( ! $this->is_registered( $id ) ) {
 396              _doing_it_wrong(
 397                  __METHOD__,
 398                  /* translators: %s: Connector ID. */
 399                  sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
 400                  '7.0.0'
 401              );
 402              return null;
 403          }
 404          return $this->registered_connectors[ $id ];
 405      }
 406  
 407      /**
 408       * Retrieves the main instance of the registry class.
 409       *
 410       * @since 7.0.0
 411       *
 412       * @return WP_Connector_Registry|null The main registry instance, or null if not yet initialized.
 413       */
 414  	public static function get_instance(): ?self {
 415          return self::$instance;
 416      }
 417  
 418      /**
 419       * Sets the main instance of the registry class.
 420       *
 421       * Called by `_wp_connectors_init()` during the `init` action. Must not be
 422       * called outside of that context.
 423       *
 424       * @since 7.0.0
 425       * @access private
 426       *
 427       * @see _wp_connectors_init()
 428       *
 429       * @param WP_Connector_Registry $registry The registry instance.
 430       */
 431  	public static function set_instance( WP_Connector_Registry $registry ): void {
 432          if ( ! doing_action( 'init' ) ) {
 433              _doing_it_wrong(
 434                  __METHOD__,
 435                  __( 'The connector registry instance must be set during the <code>init</code> action.' ),
 436                  '7.0.0'
 437              );
 438              return;
 439          }
 440  
 441          self::$instance = $registry;
 442      }
 443  }


Generated : Wed Oct 14 08:20:33 2026 Cross-referenced by PHPXref