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


Generated : Sat Jun 13 09:38:55 2026 Cross-referenced by PHPXref