[ 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 array{
 131       *     name: non-empty-string,
 132       *     description?: string,
 133       *     logo_url?: non-empty-string,
 134       *     type: non-empty-string,
 135       *     authentication: array{
 136       *         method: 'api_key'|'application_password'|'none',
 137       *         credentials_url?: non-empty-string,
 138       *         setting_name?: non-empty-string,
 139       *         constant_name?: non-empty-string,
 140       *         env_var_name?: non-empty-string
 141       *     },
 142       *     plugin?: array{
 143       *         file?: non-empty-string,
 144       *         is_active?: callable(): bool
 145       *     }
 146       * } $args
 147       * @phpstan-return Connector|null
 148       */
 149  	public function register( string $id, array $args ): ?array {
 150          if ( ! preg_match( '/^[a-z0-9_-]+$/', $id ) ) {
 151              _doing_it_wrong(
 152                  __METHOD__,
 153                  __(
 154                      'Connector ID must contain only lowercase alphanumeric characters, hyphens, and underscores.'
 155                  ),
 156                  '7.0.0'
 157              );
 158              return null;
 159          }
 160  
 161          if ( $this->is_registered( $id ) ) {
 162              _doing_it_wrong(
 163                  __METHOD__,
 164                  /* translators: %s: Connector ID. */
 165                  sprintf( __( 'Connector "%s" is already registered.' ), esc_html( $id ) ),
 166                  '7.0.0'
 167              );
 168              return null;
 169          }
 170  
 171          // Validate required fields.
 172          if ( empty( $args['name'] ) || ! is_string( $args['name'] ) ) {
 173              _doing_it_wrong(
 174                  __METHOD__,
 175                  /* translators: %s: Connector ID. */
 176                  sprintf( __( 'Connector "%s" requires a non-empty "name" string.' ), esc_html( $id ) ),
 177                  '7.0.0'
 178              );
 179              return null;
 180          }
 181  
 182          if ( empty( $args['type'] ) || ! is_string( $args['type'] ) ) {
 183              _doing_it_wrong(
 184                  __METHOD__,
 185                  /* translators: %s: Connector ID. */
 186                  sprintf( __( 'Connector "%s" requires a non-empty "type" string.' ), esc_html( $id ) ),
 187                  '7.0.0'
 188              );
 189              return null;
 190          }
 191  
 192          if ( ! isset( $args['authentication'] ) || ! is_array( $args['authentication'] ) ) {
 193              _doing_it_wrong(
 194                  __METHOD__,
 195                  /* translators: %s: Connector ID. */
 196                  sprintf( __( 'Connector "%s" requires an "authentication" array.' ), esc_html( $id ) ),
 197                  '7.0.0'
 198              );
 199              return null;
 200          }
 201  
 202          if ( empty( $args['authentication']['method'] ) || ! in_array( $args['authentication']['method'], array( 'api_key', 'application_password', 'none' ), true ) ) {
 203              _doing_it_wrong(
 204                  __METHOD__,
 205                  /* translators: %s: Connector ID. */
 206                  sprintf( __( 'Connector "%s" authentication method must be "api_key", "application_password", or "none".' ), esc_html( $id ) ),
 207                  '7.0.0'
 208              );
 209              return null;
 210          }
 211  
 212          if ( 'ai_provider' === $args['type'] && ! wp_supports_ai() ) {
 213              // No need for a `doing_it_wrong` as AI support is disabled intentionally.
 214              return null;
 215          }
 216  
 217          $connector = array(
 218              'name'           => $args['name'],
 219              'description'    => isset( $args['description'] ) && is_string( $args['description'] ) ? $args['description'] : '',
 220              'type'           => $args['type'],
 221              'authentication' => array(
 222                  'method' => $args['authentication']['method'],
 223              ),
 224          );
 225  
 226          if ( ! empty( $args['logo_url'] ) && is_string( $args['logo_url'] ) ) {
 227              $connector['logo_url'] = $args['logo_url'];
 228          }
 229  
 230          $requires_credentials = in_array( $args['authentication']['method'], array( 'api_key', 'application_password' ), true );
 231  
 232          if ( $requires_credentials ) {
 233              if ( ! empty( $args['authentication']['credentials_url'] ) && is_string( $args['authentication']['credentials_url'] ) ) {
 234                  $connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'];
 235              }
 236  
 237              if ( isset( $args['authentication']['setting_name'] ) ) {
 238                  if ( ! is_string( $args['authentication']['setting_name'] ) || '' === $args['authentication']['setting_name'] ) {
 239                      _doing_it_wrong(
 240                          __METHOD__,
 241                          /* translators: %s: Connector ID. */
 242                          sprintf( __( 'Connector "%s" authentication setting_name must be a non-empty string.' ), esc_html( $id ) ),
 243                          '7.0.0'
 244                      );
 245                      return null;
 246                  }
 247                  $connector['authentication']['setting_name'] = $args['authentication']['setting_name'];
 248              } else {
 249                  $connector['authentication']['setting_name'] = str_replace( '-', '_', "connectors_{$connector['type']}_{$id}_{$args['authentication']['method']}" );
 250              }
 251  
 252              if ( isset( $args['authentication']['constant_name'] ) ) {
 253                  if ( ! is_string( $args['authentication']['constant_name'] ) || '' === $args['authentication']['constant_name'] ) {
 254                      _doing_it_wrong(
 255                          __METHOD__,
 256                          /* translators: %s: Connector ID. */
 257                          sprintf( __( 'Connector "%s" authentication constant_name must be a non-empty string.' ), esc_html( $id ) ),
 258                          '7.0.0'
 259                      );
 260                      return null;
 261                  }
 262                  $connector['authentication']['constant_name'] = $args['authentication']['constant_name'];
 263              }
 264              if ( isset( $args['authentication']['env_var_name'] ) ) {
 265                  if ( ! is_string( $args['authentication']['env_var_name'] ) || '' === $args['authentication']['env_var_name'] ) {
 266                      _doing_it_wrong(
 267                          __METHOD__,
 268                          /* translators: %s: Connector ID. */
 269                          sprintf( __( 'Connector "%s" authentication env_var_name must be a non-empty string.' ), esc_html( $id ) ),
 270                          '7.0.0'
 271                      );
 272                      return null;
 273                  }
 274                  $connector['authentication']['env_var_name'] = $args['authentication']['env_var_name'];
 275              }
 276          }
 277  
 278          $connector['plugin'] = array();
 279  
 280          if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {
 281              if ( ! empty( $args['plugin']['file'] ) ) {
 282                  $connector['plugin']['file'] = $args['plugin']['file'];
 283              }
 284  
 285              if ( isset( $args['plugin']['is_active'] ) ) {
 286                  if ( ! is_callable( $args['plugin']['is_active'] ) ) {
 287                      _doing_it_wrong(
 288                          __METHOD__,
 289                          /* translators: %s: Connector ID. */
 290                          sprintf( __( 'Connector "%s" plugin is_active must be callable.' ), esc_html( $id ) ),
 291                          '7.0.0'
 292                      );
 293                      return null;
 294                  }
 295  
 296                  $connector['plugin']['is_active'] = $args['plugin']['is_active'];
 297              }
 298          }
 299  
 300          if ( ! isset( $connector['plugin']['is_active'] ) ) {
 301              $connector['plugin']['is_active'] = '__return_true';
 302          }
 303  
 304          $this->registered_connectors[ $id ] = $connector;
 305          return $connector;
 306      }
 307  
 308      /**
 309       * Unregisters a connector.
 310       *
 311       * Returns the connector data on success, which can be modified and passed
 312       * back to `register()` to override a connector's metadata.
 313       *
 314       * Triggers a `_doing_it_wrong()` notice if the connector is not registered.
 315       * Use `is_registered()` to check first when the connector may not exist.
 316       *
 317       * @since 7.0.0
 318       *
 319       * @see WP_Connector_Registry::register()
 320       * @see WP_Connector_Registry::is_registered()
 321       *
 322       * @param string $id The connector identifier.
 323       * @return array|null The unregistered connector data on success, null on failure.
 324       *
 325       * @phpstan-return Connector|null
 326       */
 327  	public function unregister( string $id ): ?array {
 328          if ( ! $this->is_registered( $id ) ) {
 329              _doing_it_wrong(
 330                  __METHOD__,
 331                  /* translators: %s: Connector ID. */
 332                  sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
 333                  '7.0.0'
 334              );
 335              return null;
 336          }
 337  
 338          $unregistered = $this->registered_connectors[ $id ];
 339          unset( $this->registered_connectors[ $id ] );
 340  
 341          return $unregistered;
 342      }
 343  
 344      /**
 345       * Retrieves the list of all registered connectors.
 346       *
 347       * Do not use this method directly. Instead, use the `wp_get_connectors()` function.
 348       *
 349       * @since 7.0.0
 350       *
 351       * @see wp_get_connectors()
 352       *
 353       * @return array Connector settings keyed by connector ID.
 354       *
 355       * @phpstan-return array<string, Connector>
 356       */
 357  	public function get_all_registered(): array {
 358          return $this->registered_connectors;
 359      }
 360  
 361      /**
 362       * Checks if a connector is registered.
 363       *
 364       * Do not use this method directly. Instead, use the `wp_is_connector_registered()` function.
 365       *
 366       * @since 7.0.0
 367       *
 368       * @see wp_is_connector_registered()
 369       *
 370       * @param string $id The connector identifier.
 371       * @return bool True if the connector is registered, false otherwise.
 372       */
 373  	public function is_registered( string $id ): bool {
 374          return isset( $this->registered_connectors[ $id ] );
 375      }
 376  
 377      /**
 378       * Retrieves a registered connector.
 379       *
 380       * Do not use this method directly. Instead, use the `wp_get_connector()` function.
 381       *
 382       * Triggers a `_doing_it_wrong()` notice if the connector is not registered.
 383       * Use `is_registered()` to check first when the connector may not exist.
 384       *
 385       * @since 7.0.0
 386       *
 387       * @see wp_get_connector()
 388       *
 389       * @param string $id The connector identifier.
 390       * @return array|null The registered connector data, or null if it is not registered.
 391       * @phpstan-return Connector|null
 392       */
 393  	public function get_registered( string $id ): ?array {
 394          if ( ! $this->is_registered( $id ) ) {
 395              _doing_it_wrong(
 396                  __METHOD__,
 397                  /* translators: %s: Connector ID. */
 398                  sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
 399                  '7.0.0'
 400              );
 401              return null;
 402          }
 403          return $this->registered_connectors[ $id ];
 404      }
 405  
 406      /**
 407       * Retrieves the main instance of the registry class.
 408       *
 409       * @since 7.0.0
 410       *
 411       * @return WP_Connector_Registry|null The main registry instance, or null if not yet initialized.
 412       */
 413  	public static function get_instance(): ?self {
 414          return self::$instance;
 415      }
 416  
 417      /**
 418       * Sets the main instance of the registry class.
 419       *
 420       * Called by `_wp_connectors_init()` during the `init` action. Must not be
 421       * called outside of that context.
 422       *
 423       * @since 7.0.0
 424       * @access private
 425       *
 426       * @see _wp_connectors_init()
 427       *
 428       * @param WP_Connector_Registry $registry The registry instance.
 429       */
 430  	public static function set_instance( WP_Connector_Registry $registry ): void {
 431          if ( ! doing_action( 'init' ) ) {
 432              _doing_it_wrong(
 433                  __METHOD__,
 434                  __( 'The connector registry instance must be set during the <code>init</code> action.' ),
 435                  '7.0.0'
 436              );
 437              return;
 438          }
 439  
 440          self::$instance = $registry;
 441      }
 442  }


Generated : Fri Jul 24 08:20:19 2026 Cross-referenced by PHPXref