File "Installer.php"
Full Path: /var/www/html/wordpress/wp-content/plugins/wp-optimize/vendor/team-updraft/lib-onboarding-wizard/Wizard/Installer/Installer.php
File size: 15.81 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace Updraftplus\Wp_Optimize\Wizard\Installer;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! function_exists( 'is_plugin_active' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
/**
* Install suggested plugins
*/
class Installer {
private string $slug = '';
/**
* The slug of the plugin that is calling this class.
* It is used to remove the calling plugin from the recommended list.
*
* @var string
*/
private string $caller_slug = '';
private array $plugins = [];
public $all_installed = true;
/**
* Constructor
*/
public function __construct( string $caller_slug, string $slug = '' ) {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
$this->caller_slug = $this->sanitize_slug( $caller_slug );
$this->slug = $this->sanitize_slug( $slug );
}
/**
* Sanitize the slug against our list of accepted plugins.
*/
private function sanitize_slug( string $slug ): string {
// get array of slugs from plugins array.
$plugins = $this->get_plugins_raw();
$slugs = array_map(
function ( $plugin ) {
return $plugin['slug'];
},
$plugins
);
// check if slug is in array.
if ( in_array( $slug, $slugs, true ) ) {
return $slug;
}
return '';
}
/**
* Check if plugin is downloaded
*/
public function plugin_is_downloaded( string $slug ): bool {
return file_exists( trailingslashit( WP_PLUGIN_DIR ) . $this->get_activation_slug( $slug ) );
}
/**
* Get a single plugin by slug
*
* @param string $slug The slug of the plugin to get. This is actually the folder name, but used as 'slug' in the WP Api.
* @param bool $raw Optional, default false. If true, return the raw array of plugins. If false, return the cleaned and enriched array. Normally only used within this class to prevent infinite loops.
* @return array<string, mixed>|null
*/
public function get_plugin( string $slug, bool $raw = false ): ?array {
if ( empty( $slug ) ) {
return null;
}
$plugins = $this->get_plugins_raw();
$found_plugins = array_filter(
$plugins,
function ( $plugin ) use ( $slug ) {
return $plugin['slug'] === $slug;
}
);
if ( empty( $found_plugins ) ) {
return null;
}
if ( $raw ) {
// If raw is true, return the first found plugin without enrichment.
return reset( $found_plugins );
}
$found_plugin = $this->enrich_plugin_data( $found_plugins );
return reset( $found_plugin );
}
/**
* Get plugins array, filtered by conditions, enriched with additional information, and limited to a maximum number of plugins.
* 1) Remove plugins with conditions that do not meet the requirements (e.g. WooCommerce but no WooCommerce).
* 2) If still over 3 plugins, drop plugins where a similar possibly conflicting plugin already is installed. E.g. WordFence is installed => drop AIOS
* 3) Still over 3? Remove plugins that are already installed
* 4) Still over 3? grab a random set of 3, keep this stable for one week.
*
* @param bool $include_niche_plugins If '$include_niche_plugins' = true, plugins that are only useful in a specific niche are not shown. E.g. specific niche WooCommerce plugins where we cannot determine if
* the user has any use for them.
* @param int $max Optional, default 3. Determines how many plugins to return. If more than $max plugins are available, only the first $max will be returned.
* @return array<int, array<string, mixed>>
* Returns a list of plugin arrays, a single plugin array, or null if not found.
*/
public function get_plugins( bool $include_niche_plugins = true, int $max = 3 ): array {
$plugins = $this->get_plugins_raw();
// Remove the calling plugin from the list.
foreach ( $plugins as $index => $plugin ) {
if ( $plugin['slug'] === $this->caller_slug ) {
unset( $plugins[ $index ] );
}
}
$plugins = $this->enrich_plugin_data( $plugins );
// If we don't want niche plugins, remove them from the list.
if ( ! $include_niche_plugins ) {
foreach ( $plugins as $index => $plugin ) {
if ( isset( $plugin['niche'] ) && $plugin['niche'] === true ) {
unset( $plugins[ $index ] );
}
}
}
// check php and wp version requirements
global $wp_version;
foreach ( $plugins as $index => $plugin ) {
$required_php_version = $plugin['php'] ?? '5.6';
$required_wp_version = $plugin['wp'] ?? '4.0';
if (
version_compare( PHP_VERSION, $required_php_version, '<' ) ||
version_compare( $wp_version, $required_wp_version, '<' ) ) {
unset( $plugins[ $index ] );
}
}
// Remove plugins where the conditions regarding required plugins or other are not met.
foreach ( $plugins as $index => $plugin ) {
if ( ! isset( $plugin['conditions'] ) ) {
continue;
}
foreach ( $plugin['conditions'] as $condition_type => $condition_value ) {
if ( $condition_type === 'plugin' && ! defined( $condition_value ) ) {
unset( $plugins[ $index ] );
// Skip to the next plugin.
continue 2;
}
}
}
// still more than $max plugins? Check if similar plugins are already installed.
if ( count( $plugins ) > $max ) {
foreach ( $plugins as $index => $plugin ) {
$conflicting_plugins = $plugin['conflicting_plugins'] ?? [];
foreach ( $conflicting_plugins as $conflicting_plugin_constant ) {
if ( defined( $conflicting_plugin_constant ) ) {
unset( $plugins[ $index ] );
}
if ( count( $plugins ) <= $max ) {
break;
}
}
}
}
// if more than 3 plugins in the list, remove installed plugins until we're down to 3.
if ( count( $plugins ) > $max ) {
foreach ( $plugins as $index => $plugin ) {
if ( $plugin['action'] === 'installed' || $plugin['action'] === 'upgrade-to-pro' ) {
unset( $plugins[ $index ] );
}
if ( count( $plugins ) <= $max ) {
break;
}
}
}
$plugins = array_values( $plugins );
foreach ( $plugins as $index => $plugin ) {
if ( !in_array($plugin['action'],array('installed', 'upgrade-to-pro'))) {
$this->all_installed = false;
}
}
// if still more than $max plugins, return $max plugins.
// in other plugins, we randomize, showing 3 different each week.
// in onboarding we always show the top 3.
if ( count( $plugins ) > $max ) {
if ( $include_niche_plugins ) {
$plugins = $this->get_random_selection( $plugins, $max );
} else {
// just take the first $max plugins.
$plugins = array_slice( $plugins, 0, $max );
}
}
// reset array keys again to avoid gaps in the array.
$plugins = array_values( $plugins );
return $plugins;
}
/**
* Enrich plugin data with additional information.
*
* @param array<int, array<string, mixed>> $plugins
* @return array<int, array<string, mixed>>
*/
private function enrich_plugin_data( array $plugins ): array {
$icon_url = plugin_dir_url(__FILE__) . 'icons/';
$admin_url = is_multisite() ? network_admin_url( 'plugin-install.php?s=' ) : admin_url( 'plugin-install.php?s=' );
foreach ( $plugins as $index => $plugin ) {
$action = $this->get_plugin_action( $plugin['slug'] );
$plugins[ $index ]['action'] = $action;
$plugins[ $index ] ['id'] = $plugin['slug'];
$plugins[ $index ]['icon'] = isset( $plugin['icon'] ) ? $icon_url . $plugin['icon'] : '';
$search_url = '#';
if ( $action !== 'installed' && $action !== 'upgrade-to-pro' ) {
$search_url = isset( $plugin['search_url'] ) ? $admin_url . $plugin['search_url'] : '';
}
// add utm parameters to the upgrade_url.
if ( isset( $plugin['upgrade_url'] ) ) {
$plugins[ $index ]['upgrade_url'] = add_query_arg(
[
'utm_source' => 'teamupdraft',
'utm_plugin' => $this->caller_slug,
],
$plugin['upgrade_url']
);
}
$plugins[ $index ]['search_url'] = $search_url;
}
return $plugins;
}
/**
* Get raw plugins array, cached in the class variable.
*
* @return array<int, array<string, mixed>>
* Returns a list of plugin arrays.
*/
private function get_plugins_raw(): array {
if ( ! empty( $this->plugins ) ) {
return $this->plugins;
}
$json_path = __DIR__ . '/plugins.json';
// phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$json = file_get_contents( $json_path );
// decode the json file.
$this->plugins = json_decode( $json, true );
// add slug to each plugin array.
foreach ( $this->plugins as $index => $plugin ) {
// the slug as used in this class is actually the foldername of the plugin, so we can use the file path to get it.
$this->plugins[ $index ]['slug'] = explode( '/', $plugin['file'] )[0];
}
$defaults = [
'file' => '',
'slug' => '',
'icon' => '',
'search_url' => '',
'constant_free' => '',
'premium' => [
'type' => '',
'value' => '',
],
'wordpress_url' => '',
'upgrade_url' => '',
'title' => '',
'description' => '',
'conflicting_plugins' => [],
];
$this->plugins = array_map(
function ( $plugin ) use ( $defaults ) {
return wp_parse_args( $plugin, $defaults );
},
$this->plugins
);
return $this->plugins;
}
/**
* Get random selection of plugins.
*
* @return array<int, array<string, mixed>>|array<string, mixed>
* Returns a list of plugin arrays, a single plugin array
*/
private function get_random_selection( array $plugins, int $max ): array {
$indexes = get_site_transient( 'teamupdraft_random_plugin_indexes' );
if ( false === $indexes ) {
$indexes = array_rand( $plugins, $max );
$indexes = is_array( $indexes ) ? $indexes : [ $indexes ];
set_site_transient( 'teamupdraft_random_plugin_indexes', $indexes, WEEK_IN_SECONDS );
}
$new_plugins = [];
foreach ( $indexes as $index ) {
if ( isset( $plugins[ $index ] ) ) {
$new_plugins[] = $plugins[ $index ];
}
}
return $new_plugins;
}
/**
* Check if a plugin is installed based on the detection type and value.
*/
public function premium_is_installed( string $slug ): bool {
$plugin = $this->get_plugin( $slug, true );
if ( empty( $plugin ) ) {
return true;
}
if ( ! isset( $plugin['premium'] ) ) {
return true;
}
$detection_type = $plugin['premium']['type'] ?? '';
$detection_value = $plugin['premium']['value'] ?? '';
if ( $detection_type === 'constant' ) {
return defined( $detection_value );
} elseif ( $detection_type === 'slug' ) {
return file_exists( WP_PLUGIN_DIR . '/' . $detection_value );
} elseif ( $detection_type === 'function' ) {
return function_exists( $detection_value );
} elseif ( $detection_type === 'class' ) {
return class_exists( $detection_value );
}
return false;
}
/**
* Get the next action that can be completed for this plugin.
*/
public function get_plugin_action( string $slug ): string {
$action = 'installed';
$plugin = $this->get_plugin( $slug, true );
if ( $this->premium_is_installed( $slug ) ) {
$action = 'installed';
} elseif ( ! $this->plugin_is_downloaded( $slug ) && ! $this->plugin_is_activated( $slug ) ) {
$action = 'download';
} elseif ( $this->plugin_is_downloaded( $slug ) && ! $this->plugin_is_activated( $slug ) ) {
$action = 'activate';
} elseif ( isset( $plugin['premium'] ) ) {
$action = 'upgrade-to-pro';
}
return $action;
}
/**
* Check if plugin is activated
*/
public function plugin_is_activated( string $slug ): bool {
$plugin = $this->get_plugin( $slug, true );
if ( isset( $plugin['constant_free'] ) &&
( defined( $plugin['constant_free'] ) || class_exists( $plugin['constant_free'] ) )
) {
return true;
}
return is_plugin_active( $this->get_activation_slug( $slug ) );
}
/**
* Install plugin
*/
public function install( string $step ): void {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
if ( $step === 'download' ) {
$this->download_plugin();
}
if ( $step === 'activate' ) {
$this->activate_plugin();
}
}
/**
* Get slug to activate plugin with
*/
public function get_activation_slug( string $slug ): ?string {
$plugin = $this->get_plugin( $slug, true );
if ( empty( $plugin ) ) {
return null;
}
if ( ! isset( $plugin['file'] ) ) {
return null;
}
return $plugin['file'];
}
/**
* Download the plugin
*/
public function download_plugin(): bool {
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
if ( get_site_transient( 'teamupdraft_plugin_download_active' ) !== $this->slug ) {
set_site_transient( 'teamupdraft_plugin_download_active', $this->slug, MINUTE_IN_SECONDS );
$download_url = $this->get_plugin_info( $this->slug, 'download_url' );
if ( ! empty( $download_url ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
$skin = new \WP_Ajax_Upgrader_Skin();
$upgrader = new \Plugin_Upgrader( $skin );
$result = $upgrader->install( $download_url );
delete_site_transient( 'teamupdraft_plugin_download_active' );
if ( is_wp_error( $result ) ) {
return false;
}
} else {
delete_site_transient( 'teamupdraft_plugin_download_active' );
return false;
}
}
return true;
}
/**
* Activate the plugin
*/
public function activate_plugin(): bool {
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
$file = $this->get_activation_slug( $this->slug );
$networkwide = is_multisite();
$result = activate_plugin( $file, '', $networkwide );
if ( is_wp_error( $result ) ) {
return false;
}
// plugin successfully activated. We save an option so the just installed plugin knows how it was installed.
update_site_option( 'teamupdraft_installation_source_' . $this->slug, $this->caller_slug );
return true;
}
/**
* Retrieve plugin info for rating or download.
*
* @uses plugins_api() Get the plugin data.
* @param string $slug The WP.org directory repo slug of the plugin.
* @param string $type The type of info we need, download_url, rating, or num_ratings.
* @version 1.0
*/
public function get_plugin_info( string $slug, string $type ): string {
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
$slug = $this->sanitize_slug( $slug );
$plugin_info = get_site_transient( 'teamupdraft_' . $slug . '_plugin_info' );
if ( empty( $plugin_info ) ) {
$plugin_info_total = plugins_api( 'plugin_information', [ 'slug' => $slug ] );
if ( ! is_wp_error( $plugin_info_total ) ) {
$download_link = '';
if ( isset( $plugin_info_total->versions['trunk'] ) ) {
$download_link = $plugin_info_total->versions['trunk'];
} elseif ( isset( $plugin_info_total->download_link ) ) {
$download_link = $plugin_info_total->download_link;
} elseif ( isset( $plugin_info_total->slug ) && ! empty( $plugin_info_total->slug ) ) {
// Fallback: Construct plugin page link on WordPress.org
$download_link = 'https://wordpress.org/plugins/' . sanitize_title($plugin_info_total->slug) . '/';
}
$plugin_info = [
// the plugin_info properties are not described, but do exist.
// @phpstan-ignore-next-line.
'download_url' => esc_url_raw($download_link),
// @phpstan-ignore-next-line.
'rating' => $plugin_info_total->rating,
// @phpstan-ignore-next-line.
'num_ratings' => $plugin_info_total->num_ratings,
];
set_site_transient( 'teamupdraft_' . $slug . '_plugin_info', $plugin_info, WEEK_IN_SECONDS );
}
}
if ( isset( $plugin_info[ $type ] ) ) {
return $plugin_info[ $type ];
}
return '';
}
}