Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plugin dependency support for activating performance features #1184

Merged
merged 11 commits into from
May 3, 2024
65 changes: 3 additions & 62 deletions includes/admin/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,68 +254,9 @@ function perflab_install_activate_plugin_callback() {
wp_die( esc_html__( 'Invalid plugin.', 'performance-lab' ) );
}

// Check if plugin (by slug) is installed by obtaining the plugin file.
// Remember a plugin file typically looks like "{slug}/load.php" or "{slug}/{slug}.php".
$plugin_file = null;
foreach ( array_keys( get_plugins() ) as $installed_plugin_file ) {
if ( strtok( $installed_plugin_file, '/' ) === $plugin_slug ) {
$plugin_file = $installed_plugin_file;
break;
}
}

// Install the plugin if it is not installed yet (in which case the plugin file could not be discovered above).
if ( ! isset( $plugin_file ) ) {
// Check if the user have plugin installation capability.
if ( ! current_user_can( 'install_plugins' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to install plugins on this site.', 'default' ) );
}

$api = perflab_query_plugin_info( $plugin_slug );

// Return early if plugin API returns an error.
if ( $api instanceof WP_Error ) {
wp_die(
wp_kses(
sprintf(
/* translators: %s: Support forums URL. */
__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.', 'default' ),
__( 'https://wordpress.org/support/forums/', 'default' )
) . ' ' . $api->get_error_message(),
array( 'a' => array( 'href' => true ) )
)
);
}

// Replace new Plugin_Installer_Skin with new Quiet_Upgrader_Skin when output needs to be suppressed.
$skin = new WP_Ajax_Upgrader_Skin( array( 'api' => $api ) );
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->install( $api['download_link'] );

if ( is_wp_error( $result ) ) {
wp_die( esc_html( $result->get_error_message() ) );
} elseif ( is_wp_error( $skin->result ) ) {
wp_die( esc_html( $skin->result->get_error_message() ) );
} elseif ( $skin->get_errors()->has_errors() ) {
wp_die( esc_html( $skin->get_error_messages() ) );
}

$plugins = get_plugins( '/' . $plugin_slug );

if ( empty( $plugins ) ) {
wp_die( esc_html__( 'Plugin not found.', 'default' ) );
}

$plugin_file_names = array_keys( $plugins );
$plugin_file = $plugin_slug . '/' . $plugin_file_names[0];
}

if ( ! current_user_can( 'activate_plugin', $plugin_file ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to activate this plugin.', 'default' ) );
}

$result = activate_plugin( $plugin_file );
if ( is_wp_error( $result ) ) {
// Install and activate the plugin and its dependencies.
$result = perflab_install_and_activate_plugin( $plugin_slug );
if ( $result instanceof WP_Error ) {
wp_die( wp_kses_post( $result->get_error_message() ) );
}

Expand Down
170 changes: 154 additions & 16 deletions includes/admin/plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Admin settings helper functions.
*
* @package performance-lab
* @noinspection PhpRedundantOptionalArgumentInspection
*/

if ( ! defined( 'ABSPATH' ) ) {
Expand Down Expand Up @@ -150,6 +151,148 @@ function perflab_render_plugins_ui() {
<?php
}

/**
* Checks if a given plugin is available.
*
* @since n.e.x.t
* @see perflab_install_and_activate_plugin()
*
* @param array{name: string, slug: string, short_description: string, requires_php: string|false, requires: string|false, requires_plugins: string[], version: string} $plugin_data Plugin data from the WordPress.org API.
* @param array<string, array{compatible_php: bool, compatible_wp: bool, can_install: bool, can_activate: bool, activated: bool, installed: bool}> $processed_plugin_availabilities Plugin availabilities already processed. This param is only used by recursive calls.
* @return array{compatible_php: bool, compatible_wp: bool, can_install: bool, can_activate: bool, activated: bool, installed: bool} Availability.
*/
function perflab_get_plugin_availability( array $plugin_data, array &$processed_plugin_availabilities = array() ): array {
if ( array_key_exists( $plugin_data['slug'], $processed_plugin_availabilities ) ) {
// Prevent infinite recursion by returning the previously-computed value.
return $processed_plugin_availabilities[ $plugin_data['slug'] ];
}

$availability = array(
'compatible_php' => (
! $plugin_data['requires_php'] ||
is_php_version_compatible( $plugin_data['requires_php'] )
),
'compatible_wp' => (
! $plugin_data['requires'] ||
is_wp_version_compatible( $plugin_data['requires'] )
),
);

$plugin_status = install_plugin_install_status( $plugin_data );

$availability['installed'] = ( 'install' !== $plugin_status['status'] );
$availability['activated'] = $plugin_status['file'] && is_plugin_active( $plugin_status['file'] );

// The plugin is already installed or the user can install plugins.
$availability['can_install'] = (
$availability['installed'] ||
current_user_can( 'install_plugins' )
);

// The plugin is activated or the user can activate plugins.
$availability['can_activate'] = (
$availability['activated'] ||
$plugin_status['file'] // When not false, the plugin is installed.
? current_user_can( 'activate_plugin', $plugin_status['file'] )
: current_user_can( 'activate_plugins' )
);

// Store pending availability before recursing.
$processed_plugin_availabilities[ $plugin_data['slug'] ] = $availability;

foreach ( $plugin_data['requires_plugins'] as $requires_plugin ) {
$dependency_plugin_data = perflab_query_plugin_info( $requires_plugin );
if ( $dependency_plugin_data instanceof WP_Error ) {
continue;
}

$dependency_availability = perflab_get_plugin_availability( $dependency_plugin_data );
foreach ( array( 'compatible_php', 'compatible_wp', 'can_install', 'can_activate', 'installed', 'activated' ) as $key ) {
$availability[ $key ] = $availability[ $key ] && $dependency_availability[ $key ];
}
}

$processed_plugin_availabilities[ $plugin_data['slug'] ] = $availability;
return $availability;
}

/**
* Installs and activates a plugin by its slug.
*
* Dependencies are recursively installed and activated as well.
*
* @since n.e.x.t
* @see perflab_get_plugin_availability()
*
* @param string $plugin_slug Plugin slug.
* @param string[] $processed_plugins Slugs for plugins which have already been processed. This param is only used by recursive calls.
* @return WP_Error|null WP_Error on failure.
*/
function perflab_install_and_activate_plugin( string $plugin_slug, array &$processed_plugins = array() ): ?WP_Error {
if ( in_array( $plugin_slug, $processed_plugins, true ) ) {
// Prevent infinite recursion from possible circular dependency.
return null;
}
$processed_plugins[] = $plugin_slug;

$plugin_data = perflab_query_plugin_info( $plugin_slug );
if ( $plugin_data instanceof WP_Error ) {
return $plugin_data;
}

// Install and activate plugin dependencies first.
foreach ( $plugin_data['requires_plugins'] as $requires_plugin_slug ) {
$result = perflab_install_and_activate_plugin( $requires_plugin_slug );
if ( $result instanceof WP_Error ) {
return $result;
}
}

// Install the plugin.
$plugin_status = install_plugin_install_status( $plugin_data );
$plugin_file = $plugin_status['file'];
if ( 'install' === $plugin_status['status'] ) {
if ( ! current_user_can( 'install_plugins' ) ) {
return new WP_Error( 'cannot_install_plugin', __( 'Sorry, you are not allowed to install plugins on this site.', 'default' ) );
}

// Replace new Plugin_Installer_Skin with new Quiet_Upgrader_Skin when output needs to be suppressed.
$skin = new WP_Ajax_Upgrader_Skin( array( 'api' => $plugin_data ) );
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->install( $plugin_data['download_link'] );

if ( is_wp_error( $result ) ) {
return $result;
} elseif ( is_wp_error( $skin->result ) ) {
return $skin->result;
} elseif ( $skin->get_errors()->has_errors() ) {
return $skin->get_errors();
}

$plugins = get_plugins( '/' . $plugin_slug );
if ( empty( $plugins ) ) {
return new WP_Error( 'plugin_not_found', __( 'Plugin not found.', 'default' ) );
}

$plugin_file_names = array_keys( $plugins );
$plugin_file = $plugin_slug . '/' . $plugin_file_names[0];
}

// Activate the plugin.
if ( ! is_plugin_active( $plugin_file ) ) {
if ( ! current_user_can( 'activate_plugin', $plugin_file ) ) {
return new WP_Error( 'cannot_activate_plugin', __( 'Sorry, you are not allowed to activate this plugin.', 'default' ) );
}

$result = activate_plugin( $plugin_file );
if ( $result instanceof WP_Error ) {
return $result;
}
}

return null;
}

/**
* Renders individual plugin cards.
*
Expand All @@ -170,28 +313,23 @@ function perflab_render_plugin_card( array $plugin_data ) {
/** This filter is documented in wp-admin/includes/class-wp-plugin-install-list-table.php */
$description = apply_filters( 'plugin_install_description', $description, $plugin_data );

$compatible_php = ! $plugin_data['requires_php'] || is_php_version_compatible( $plugin_data['requires_php'] );
$compatible_wp = ! $plugin_data['requires'] || is_wp_version_compatible( $plugin_data['requires'] );
$action_links = array();
$availability = perflab_get_plugin_availability( $plugin_data );

$compatible_php = $availability['compatible_php'];
$compatible_wp = $availability['compatible_wp'];

$status = install_plugin_install_status( $plugin_data );
$action_links = array();

if ( is_plugin_active( $status['file'] ) ) {
if ( $availability['activated'] ) {
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
esc_html( _x( 'Active', 'plugin', 'default' ) )
);
} elseif (
$compatible_php &&
$compatible_wp &&
(
( $status['file'] && current_user_can( 'activate_plugin', $status['file'] ) ) ||
current_user_can( 'activate_plugins' )
) &&
(
'install' !== $status['status'] ||
current_user_can( 'install_plugins' )
)
$availability['compatible_php'] &&
$availability['compatible_wp'] &&
$availability['can_install'] &&
$availability['can_activate']
) {
$url = esc_url_raw(
add_query_arg(
Expand All @@ -210,7 +348,7 @@ function perflab_render_plugin_card( array $plugin_data ) {
esc_html__( 'Activate', 'default' )
);
} else {
$explanation = 'install' !== $status['status'] || current_user_can( 'install_plugins' ) ? _x( 'Cannot Activate', 'plugin', 'default' ) : _x( 'Cannot Install', 'plugin', 'default' );
$explanation = $availability['can_install'] ? _x( 'Cannot Activate', 'plugin', 'default' ) : _x( 'Cannot Install', 'plugin', 'default' );
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
esc_html( $explanation )
Expand Down
Loading