Automattic\WooCommerce\Internal\Utilities
PluginInstaller{} │ WC 1.0
This class allows installing a plugin programmatically.
Information about plugins installed in that way will be stored in a 'woocommerce_autoinstalled_plugins' option, and a notice will be shown under the plugin name in the plugins list indicating that it was automatically installed (these notices can be disabled with the woocommerce_show_autoinstalled_plugin_notices
Currently it's only possible to install new plugins, not to upgrade or reinstall already installed plugins.
The upgrader_process_complete is used to remove the autoinstall information from any plugin that is later upgraded or reinstalled by any means other than the usage of this class.
Хуки из класса
Использование
$PluginInstaller = new PluginInstaller(); // use class methods
Методы
- public handle_plugin_list_rows( $plugin_file, $plugin_data )
- public handle_upgrader_process_complete( \WP_Upgrader $upgrader, array $hook_extra )
- public install_plugin( string $plugin_url, array $metadata = array() )
- private install_plugin_core( string $plugin_url, array $metadata )
- public register()
- private static woocommerce_is_active_in_current_site()
Код PluginInstaller{} PluginInstaller{} WC 9.7.1
<?php class PluginInstaller implements RegisterHooksInterface { /** * Flag indicating that a plugin install is in progress, so the upgrader_process_complete hook must be ignored. * * @var bool */ private bool $installing_plugin = false; /** * Attach hooks used by the class. */ public function register() { add_action( 'after_plugin_row', array( $this, 'handle_plugin_list_rows' ), 10, 2 ); add_action( 'upgrader_process_complete', array( $this, 'handle_upgrader_process_complete' ), 10, 2 ); } /** * Programmatically installs a plugin. Upgrade/reinstall of already existing plugins is not supported. * The plugin source must be the WordPress.org plugins directory. * * $metadata can contain anything, but the following keys are recognized by the code that renders the notice * in the plugins list: * * - 'installed_by': defaults to 'WooCommerce' if not present. * - 'info_link': if present, a "More information" link will be included in the notice. * * If 'installed_by' is supplied and it's not 'WooCommerce' (case-insensitive), an exception will be thrown * if the code calling this method is not in a WooCommerce core file (in 'includes' or in 'src'). * * Information about plugins successfully installed with this method will be kept in an option named * 'woocommerce_autoinstalled_plugins'. Keys will be the plugin name and values will be associative arrays * with these keys: 'plugin_name', 'version', 'date' and 'metadata' (same meaning as in the returned array). * * A log entry will be created with the result of the process and all the installer messages * (source: 'plugin_auto_installs'). In multisite this log entry will be created on each site. * * The returned array will contain the following (only 'install_ok' and 'messages' if the installation fails): * * - 'install_ok', a boolean. * - 'messages', all the messages generated by the installer. * - 'plugin_name', in the form of 'directory/file.php' (taken from the instance of PluginInstaller used). * - 'version', of the plugin that has been installed. * - 'date', ISO-formatted installation date. * - 'metadata', as supplied (except the 'plugin_name' key) and only if not empty. * * If the plugin is already in the process of being installed (can happen in multisite), the returned array * will contain only one key: 'already_installing', with a value of true. * * @param string $plugin_url URL or file path of the plugin to install. * @param array $metadata Metadata to store if the installation succeeds. * @return array Information about the installation result. * @throws \InvalidArgumentException Source doesn't start with 'https://downloads.wordpress.org/', or installer name is 'WooCommerce' but caller is not WooCommerce core code. */ public function install_plugin( string $plugin_url, array $metadata = array() ): array { $this->installing_plugin = true; $plugins_being_installed = get_site_option( 'woocommerce_autoinstalling_plugins', array() ); if ( in_array( $plugin_url, $plugins_being_installed, true ) ) { return array( 'already_installing' => true ); } $plugins_being_installed[] = $plugin_url; update_site_option( 'woocommerce_autoinstalling_plugins', $plugins_being_installed ); try { return $this->install_plugin_core( $plugin_url, $metadata ); } finally { $plugins_being_installed = array_diff( $plugins_being_installed, array( $plugin_url ) ); if ( empty( $plugins_being_installed ) ) { delete_site_option( 'woocommerce_autoinstalling_plugins' ); } else { update_site_option( 'woocommerce_autoinstalling_plugins', $plugins_being_installed ); } $this->installing_plugin = false; } } /** * Core version of 'install_plugin' (it doesn't handle the $installing_plugin flag). * * @param string $plugin_url URL or file path of the plugin to install. * @param array $metadata Metadata to store if the installation succeeds. * @return array Information about the installation result. * @throws \InvalidArgumentException Source doesn't start with 'https://downloads.wordpress.org/', or installer name is 'WooCommerce' but caller is not WooCommerce core code. */ private function install_plugin_core( string $plugin_url, array $metadata ): array { if ( ! StringUtil::starts_with( $plugin_url, 'https://downloads.wordpress.org/', false ) ) { throw new \InvalidArgumentException( "Only installs from the WordPress.org plugins directory (plugin URL starting with 'https://downloads.wordpress.org/') are allowed." ); } $installed_by = $metadata['installed_by'] ?? 'WooCommerce'; if ( 0 === strcasecmp( 'WooCommerce', $installed_by ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace $calling_file = StringUtil::normalize_local_path_slashes( debug_backtrace()[1]['file'] ?? '' ); // [1], not [0], because the immediate caller is the install_plugin method. if ( ! StringUtil::starts_with( $calling_file, StringUtil::normalize_local_path_slashes( WC_ABSPATH . 'includes/' ) ) && ! StringUtil::starts_with( $calling_file, StringUtil::normalize_local_path_slashes( WC_ABSPATH . 'src/' ) ) ) { throw new \InvalidArgumentException( "If the value of 'installed_by' is 'WooCommerce', the caller of the method must be a WooCommerce core class or function." ); } } if ( ! class_exists( \Automatic_Upgrader_Skin::class ) ) { include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php'; include_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php'; } $skin = new \Automatic_Upgrader_Skin(); if ( ! class_exists( \Plugin_Upgrader::class ) ) { include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } $upgrader = new \Plugin_Upgrader( $skin ); $install_ok = $upgrader->install( $plugin_url ); $result = array( 'messages' => $skin->get_upgrade_messages() ); if ( $install_ok ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_name = $upgrader->plugin_info(); $plugin_version = get_plugins()[ $plugin_name ]['Version']; $result['plugin_name'] = $plugin_name; $plugin_data = array( 'version' => $plugin_version, 'date' => current_time( 'mysql' ), ); if ( ! empty( $metadata ) ) { $plugin_data['metadata'] = $metadata; } $auto_installed_plugins = get_site_option( 'woocommerce_autoinstalled_plugins', array() ); $auto_installed_plugins[ $plugin_name ] = $plugin_data; update_site_option( 'woocommerce_autoinstalled_plugins', $auto_installed_plugins ); $auto_installed_plugins_history = get_site_option( 'woocommerce_history_of_autoinstalled_plugins', array() ); if ( ! isset( $auto_installed_plugins_history[ $plugin_name ] ) ) { $auto_installed_plugins_history[ $plugin_name ] = $plugin_data; update_site_option( 'woocommerce_history_of_autoinstalled_plugins', $auto_installed_plugins_history ); } $post_install = function () use ( $plugin_name, $plugin_version, $installed_by, $plugin_url, $plugin_data ) { $log_context = array( 'source' => 'plugin_auto_installs', 'recorded_data' => $plugin_data, ); wc_get_logger()->info( "Plugin $plugin_name v{$plugin_version} installed by $installed_by, source: $plugin_url", $log_context ); }; } else { $messages = $skin->get_upgrade_messages(); $post_install = function () use ( $plugin_url, $installed_by, $messages ) { $log_context = array( 'source' => 'plugin_auto_installs', 'installer_messages' => $messages, ); wc_get_logger()->error( "$installed_by failed to install plugin from source: $plugin_url", $log_context ); }; } if ( is_multisite() ) { // We log the install in the main site, unless the main site doesn't have WooCommerce installed; // in that case we fallback to logging in the current site. switch_to_blog( get_main_site_id() ); if ( self::woocommerce_is_active_in_current_site() ) { $post_install(); restore_current_blog(); } else { restore_current_blog(); $post_install(); } } else { $post_install(); } $result['install_ok'] = $install_ok ?? false; return $result; } /** * Check if WooCommerce is installed and active in the current blog. * This is useful for multisite installs when a blog other than the one running this code is selected with 'switch_to_blog'. * * @return bool True if WooCommerce is installed and active in the current blog, false otherwise. */ private static function woocommerce_is_active_in_current_site(): bool { $active_valid_plugins = wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins(); return ! empty( array_filter( $active_valid_plugins, fn( $plugin ) => substr_compare( $plugin, '/woocommerce.php', -strlen( '/woocommerce.php' ) ) === 0 ) ); } /** * Handler for the 'plugin_list_rows' hook, it will display a notice under the name of the plugins * that have been installed using this class (unless the 'woocommerce_show_autoinstalled_plugin_notices' filter * returns false) in the plugins list page. * * @param string $plugin_file Name of the plugin. * @param array $plugin_data Plugin data. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function handle_plugin_list_rows( $plugin_file, $plugin_data ) { global $wp_list_table; if ( is_null( $wp_list_table ) ) { return; } /** * Filter to suppress the notice about autoinstalled plugins in the plugins list page. * * @since 8.8.0 * * @param bool $display_notice Whether notices should be displayed or not. * @returns bool */ if ( ! apply_filters( 'woocommerce_show_autoinstalled_plugin_notices', '__return_true' ) ) { return; } $auto_installed_plugins_info = get_site_option( 'woocommerce_autoinstalled_plugins', array() ); $current_plugin_info = $auto_installed_plugins_info[ $plugin_file ] ?? null; if ( is_null( $current_plugin_info ) || $current_plugin_info['version'] !== $plugin_data['Version'] ) { return; } $installed_by = $current_plugin_info['metadata']['installed_by'] ?? 'WooCommerce'; $info_link = $current_plugin_info['metadata']['info_link'] ?? null; if ( $info_link ) { /* translators: 1 = who installed the plugin, 2 = ISO-formatted date and time, 3 = URL */ $message = sprintf( __( 'Plugin installed by %1$s on %2$s. <a target="_blank" href="%3$s">More information</a>', 'woocommerce' ), $installed_by, $current_plugin_info['date'], $info_link ); } else { /* translators: 1 = who installed the plugin, 2 = ISO-formatted date and time */ $message = sprintf( __( 'Plugin installed by %1$s on %2$s.', 'woocommerce' ), $installed_by, $current_plugin_info['date'] ); } $columns_count = $wp_list_table->get_column_count(); $is_active = is_plugin_active( $plugin_file ); $is_active_class = $is_active ? 'active' : 'inactive'; $is_active_td_style = $is_active ? "style='border-left: 4px solid #72aee6;'" : ''; // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped ?> <tr class='plugin-update-tr update <?php echo $is_active_class; ?>' data-plugin='<?php echo $plugin_file; ?>' data-plugin-row-type='feature-incomp-warn'> <td colspan='<?php echo $columns_count; ?>' class='plugin-update'<?php echo $is_active_td_style; ?>> <div class='notice inline notice-success notice-alt'> <p> ℹ️ <?php echo $message; ?> </p> </div> </td> </tr> <?php // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Handler for the 'upgrader_process_complete' hook. It's used to remove the autoinstalled plugin information * for plugins that are upgraded or reinstalled manually (or more generally, by using any install method * other than this class). * * @param \WP_Upgrader $upgrader The upgrader class that has performed the plugin upgrade/reinstall. * @param array $hook_extra Extra information about the upgrade process. * * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed. */ public function handle_upgrader_process_complete( \WP_Upgrader $upgrader, array $hook_extra ) { if ( $this->installing_plugin || ! ( $upgrader instanceof \Plugin_Upgrader ) || ( 'plugin' !== ( $hook_extra['type'] ?? null ) ) ) { return; } $auto_installed_plugins = get_site_option( 'woocommerce_autoinstalled_plugins' ); if ( ! $auto_installed_plugins ) { return; } if ( $hook_extra['bulk'] ?? false ) { $updated_plugin_names = $hook_extra['plugins'] ?? array(); } else { $updated_plugin_names = array( $upgrader->plugin_info() ); } $auto_installed_plugin_names = array_keys( $auto_installed_plugins ); $updated_auto_installed_plugin_names = array_intersect( $auto_installed_plugin_names, $updated_plugin_names ); if ( empty( $updated_auto_installed_plugin_names ) ) { return; } $new_auto_installed_plugins = array_diff_key( $auto_installed_plugins, array_flip( $updated_auto_installed_plugin_names ) ); if ( empty( $new_auto_installed_plugins ) ) { delete_site_option( 'woocommerce_autoinstalled_plugins' ); } else { update_site_option( 'woocommerce_autoinstalled_plugins', $new_auto_installed_plugins ); } } }