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. */ private 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. More information', '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 ?>
ℹ️