Make WordPress Core

Changeset 62332


Ignore:
Timestamp:
05/08/2026 10:38:56 AM (3 months ago)
Author:
wildworks
Message:

Connectors: Refine plugin install check and PHPStan types.

Replace file_exists() with validate_plugin() in the connectors script module data, and refine PHPStan type definitions in the connectors code to better reflect the actual shape of registered connector data.

Follow-up to [62288].

Props jorgefilipecosta, mukesh27, peterwilsoncc, westonruter, wildworks.
See #65020.

Location:
trunk/src/wp-includes
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-connector-registry.php

    r62288 r62332  
    3030 * @phpstan-type Connector array{
    3131 *     name: non-empty-string,
    32  *     description: non-empty-string,
     32 *     description: string,
    3333 *     logo_url?: non-empty-string,
    3434 *     type: non-empty-string,
     
    4040 *         env_var_name?: non-empty-string
    4141 *     },
    42  *     plugin?: array{
    43  *         file: non-empty-string,
    44  *         is_active?: callable(): bool
     42 *     plugin: array{
     43 *         file?: non-empty-string,
     44 *         is_active: callable(): bool
    4545 *     }
    4646 * }
     
    121121         * @return array|null The registered connector data on success, null on failure.
    122122         *
    123          * @phpstan-param Connector $args
     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
    124140         * @phpstan-return Connector|null
    125141         */
  • trunk/src/wp-includes/connectors.php

    r62310 r62332  
    5959 *         Optional. Plugin data for install/activate UI.
    6060 *
    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').
     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`.
    6365 *     }
    6466 * }
    6567 * @phpstan-return ?array{
    6668 *     name: non-empty-string,
    67  *     description: non-empty-string,
     69 *     description: string,
    6870 *     logo_url?: non-empty-string,
    6971 *     type: non-empty-string,
     
    7577 *         env_var_name?: non-empty-string
    7678 *     },
    77  *     plugin?: array{
    78  *         file: non-empty-string
     79 *     plugin: array{
     80 *         file?: non-empty-string,
     81 *         is_active: callable(): bool,
    7982 *     }
    8083 * }
     
    120123 *             Optional. Plugin data for install/activate UI.
    121124 *
    122  *             @type string $file The plugin's main file path relative to the plugins
    123  *                                directory (e.g. 'my-plugin/my-plugin.php' or 'hello.php').
     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`.
    124129 *         }
    125130 *     }
     
    127132 * @phpstan-return array<string, array{
    128133 *     name: non-empty-string,
    129  *     description: non-empty-string,
     134 *     description: string,
    130135 *     logo_url?: non-empty-string,
    131136 *     type: non-empty-string,
     
    137142 *         env_var_name?: non-empty-string
    138143 *     },
    139  *     plugin?: array{
    140  *         file: non-empty-string
     144 *     plugin: array{
     145 *         file?: non-empty-string,
     146 *         is_active: callable(): bool,
    141147 *     }
    142148 * }>
     
    161167 *
    162168 * @param string $path Absolute path to the logo file.
    163  * @return string|null The URL to the logo file, or null if the path is invalid.
     169 * @return non-empty-string|null The URL to the logo file, or null if the path is invalid.
    164170 */
    165171function _wp_connectors_resolve_ai_provider_logo_url( string $path ): ?string {
     
    176182        $mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
    177183        if ( str_starts_with( $path, $mu_plugin_dir . '/' ) ) {
    178                 return plugins_url( substr( $path, strlen( $mu_plugin_dir ) ), WPMU_PLUGIN_DIR . '/.' );
     184                $logo_url = plugins_url( substr( $path, strlen( $mu_plugin_dir ) ), WPMU_PLUGIN_DIR . '/.' );
     185                return $logo_url ? $logo_url : null;
    179186        }
    180187
    181188        $plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
    182189        if ( str_starts_with( $path, $plugin_dir . '/' ) ) {
    183                 return plugins_url( substr( $path, strlen( $plugin_dir ) ) );
     190                $logo_url = plugins_url( substr( $path, strlen( $plugin_dir ) ) );
     191                return $logo_url ? $logo_url : null;
    184192        }
    185193
     
    318326        $ai_registry = AiClient::defaultRegistry();
    319327
    320         foreach ( $ai_registry->getRegisteredProviderIds() as $connector_id ) {
     328        foreach ( array_filter( $ai_registry->getRegisteredProviderIds() ) as $connector_id ) {
    321329                $provider_class_name = $ai_registry->getProviderClassName( $connector_id );
    322330                $provider_metadata   = $provider_class_name::metadata();
     
    328336                        $credentials_url = $provider_metadata->getCredentialsUrl();
    329337                        $authentication  = array(
    330                                 'method'          => 'api_key',
    331                                 'credentials_url' => $credentials_url ? $credentials_url : null,
     338                                'method' => 'api_key',
    332339                        );
     340                        if ( $credentials_url ) {
     341                                $authentication['credentials_url'] = $credentials_url;
     342                        }
    333343                } else {
    334344                        $authentication = array( 'method' => 'none' );
     
    363373                                'type'           => 'ai_provider',
    364374                                'authentication' => $authentication,
    365                                 'logo_url'       => $logo_url,
    366375                        );
     376                        if ( $logo_url ) {
     377                                $defaults[ $connector_id ]['logo_url'] = $logo_url;
     378                        }
    367379                }
    368380        }
     
    373385                        $sanitized_id = str_replace( '-', '_', $id );
    374386
    375                         if ( ! isset( $args['authentication']['setting_name'] ) ) {
    376                                 $args['authentication']['setting_name'] = "connectors_ai_{$sanitized_id}_api_key";
    377                         }
     387                        $args['authentication']['setting_name'] = "connectors_ai_{$sanitized_id}_api_key";
    378388
    379389                        // All AI providers use the {CONSTANT_CASE_ID}_API_KEY naming convention.
    380                         if ( ! isset( $args['authentication']['constant_name'] ) || ! isset( $args['authentication']['env_var_name'] ) ) {
    381                                 $constant_case_key = strtoupper( preg_replace( '/([a-z])([A-Z])/', '$1_$2', $sanitized_id ) ) . '_API_KEY';
    382 
    383                                 if ( ! isset( $args['authentication']['constant_name'] ) ) {
    384                                         $args['authentication']['constant_name'] = $constant_case_key;
    385                                 }
    386 
    387                                 if ( ! isset( $args['authentication']['env_var_name'] ) ) {
    388                                         $args['authentication']['env_var_name'] = $constant_case_key;
    389                                 }
    390                         }
    391                 }
    392 
    393                 if ( ! isset( $args['plugin']['is_active'] ) ) {
    394                         $args['plugin']['is_active'] = static function () use ( $ai_registry, $id ): bool {
    395                                 try {
    396                                         return $ai_registry->hasProvider( $id );
    397                                 } catch ( Exception $e ) {
    398                                         return false;
    399                                 }
    400                         };
    401                 }
     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                };
    402403
    403404                $registry->register( $id, $args );
     
    647648
    648649                        $api_key = get_option( $auth['setting_name'], '' );
    649                         if ( '' === $api_key ) {
     650                        if ( ! is_string( $api_key ) || '' === $api_key ) {
    650651                                continue;
    651652                        }
     
    673674function _wp_connectors_get_connector_script_module_data( array $data ): array {
    674675        $registry = AiClient::defaultRegistry();
     676
     677        if ( ! function_exists( 'validate_plugin' ) ) {
     678                require_once ABSPATH . 'wp-admin/includes/plugin.php';
     679        }
    675680
    676681        $connectors = array();
     
    707712                        $file         = $connector_data['plugin']['file'];
    708713                        $is_activated = (bool) call_user_func( $connector_data['plugin']['is_active'] );
    709                         $is_installed = $is_activated || file_exists( wp_normalize_path( WP_PLUGIN_DIR . '/' . $file ) );
     714                        $is_installed = $is_activated || 0 === validate_plugin( $file );
    710715
    711716                        $connector_out['plugin'] = array(
Note: See TracChangeset for help on using the changeset viewer.