Skip to content

Commit

Permalink
Merge pull request #1646 from b1ink0/update/use-ajax-for-activate-plugin
Browse files Browse the repository at this point in the history
Use AJAX for activating features / plugins in Performance Lab
  • Loading branch information
felixarntz authored Nov 15, 2024
2 parents 7b57489 + e0691bf commit 04a2e5a
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 42 deletions.
49 changes: 8 additions & 41 deletions plugins/performance-lab/includes/admin/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ function perflab_load_features_page(): void {

// Handle style for settings page.
add_action( 'admin_head', 'perflab_print_features_page_style' );

// Handle script for settings page.
add_action( 'admin_footer', 'perflab_print_plugin_progress_indicator_script' );
}

/**
Expand Down Expand Up @@ -228,8 +225,14 @@ function perflab_enqueue_features_page_scripts(): void {
wp_enqueue_style( 'thickbox' );
wp_enqueue_script( 'plugin-install' );

// Enqueue the a11y script.
wp_enqueue_script( 'wp-a11y' );
// Enqueue plugin activate AJAX script and localize script data.
wp_enqueue_script(
'perflab-plugin-activate-ajax',
plugin_dir_url( PERFLAB_MAIN_FILE ) . 'includes/admin/plugin-activate-ajax.js',
array( 'wp-i18n', 'wp-a11y', 'wp-api-fetch' ),
PERFLAB_VERSION,
true
);
}

/**
Expand Down Expand Up @@ -396,42 +399,6 @@ static function ( $name ) {
}
}

/**
* Callback function that print plugin progress indicator script.
*
* @since 3.1.0
*/
function perflab_print_plugin_progress_indicator_script(): void {
$js_function = <<<JS
function addPluginProgressIndicator( message ) {
document.addEventListener( 'DOMContentLoaded', function () {
document.addEventListener( 'click', function ( event ) {
if (
event.target.classList.contains(
'perflab-install-active-plugin'
)
) {
const target = event.target;
target.classList.add( 'updating-message' );
target.textContent = message;
wp.a11y.speak( message );
}
} );
} );
}
JS;

wp_print_inline_script_tag(
sprintf(
'( %s )( %s );',
$js_function,
wp_json_encode( __( 'Activating...', 'default' ) )
),
array( 'type' => 'module' )
);
}

/**
* Gets the URL to the plugin settings screen if one exists.
*
Expand Down
88 changes: 88 additions & 0 deletions plugins/performance-lab/includes/admin/plugin-activate-ajax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Handles activation of Performance Features (Plugins) using AJAX.
*/

( function () {
// @ts-ignore
const { i18n, a11y, apiFetch } = wp;
const { __ } = i18n;

/**
* Handles click events on elements with the class 'perflab-install-active-plugin'.
*
* This asynchronous function listens for click events on the document and executes
* the provided callback function if triggered.
*
* @param {MouseEvent} event - The click event object that is triggered when the user clicks on the document.
*
* @return {Promise<void>} The asynchronous function returns a promise that resolves to void.
*/
async function handlePluginActivationClick( event ) {
const target = /** @type {HTMLElement} */ ( event.target );

// Prevent the default link behavior.
event.preventDefault();

if (
target.classList.contains( 'updating-message' ) ||
target.classList.contains( 'disabled' )
) {
return;
}

target.classList.add( 'updating-message' );
target.textContent = __( 'Activating…', 'performance-lab' );

a11y.speak( __( 'Activating…', 'performance-lab' ) );

const pluginSlug = target.dataset.pluginSlug;

try {
// Activate the plugin/feature via the REST API.
await apiFetch( {
path: `/performance-lab/v1/features/${ pluginSlug }:activate`,
method: 'POST',
} );

// Fetch the plugin/feature information via the REST API.
/** @type {{settingsUrl: string|null}} */
const featureInfo = await apiFetch( {
path: `/performance-lab/v1/features/${ pluginSlug }`,
method: 'GET',
} );

if ( featureInfo.settingsUrl ) {
const actionButtonList = document.querySelector(
`.plugin-card-${ pluginSlug } .plugin-action-buttons`
);

const listItem = document.createElement( 'li' );
const anchor = document.createElement( 'a' );

anchor.href = featureInfo.settingsUrl;
anchor.textContent = __( 'Settings', 'performance-lab' );

listItem.appendChild( anchor );
actionButtonList.appendChild( listItem );
}

a11y.speak( __( 'Plugin activated.', 'performance-lab' ) );

target.textContent = __( 'Active', 'performance-lab' );
target.classList.remove( 'updating-message' );
target.classList.add( 'disabled' );
} catch ( error ) {
a11y.speak( __( 'Plugin failed to activate.', 'performance-lab' ) );

target.classList.remove( 'updating-message' );
target.textContent = __( 'Activate', 'performance-lab' );
}
}

// Attach the event listeners.
document
.querySelectorAll( '.perflab-install-active-plugin' )
.forEach( ( item ) => {
item.addEventListener( 'click', handlePluginActivationClick );
} );
} )();
3 changes: 2 additions & 1 deletion plugins/performance-lab/includes/admin/plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,9 @@ function perflab_render_plugin_card( array $plugin_data ): void {
);

$action_links[] = sprintf(
'<a class="button perflab-install-active-plugin" href="%s">%s</a>',
'<a class="button perflab-install-active-plugin" href="%s" data-plugin-slug="%s">%s</a>',
esc_url( $url ),
esc_attr( $plugin_data['slug'] ),
esc_html__( 'Activate', 'default' )
);
} else {
Expand Down
183 changes: 183 additions & 0 deletions plugins/performance-lab/includes/admin/rest-api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php
/**
* REST API integration for the plugin.
*
* @package performance-lab
* @since n.e.x.t
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Namespace for performance-lab REST API.
*
* @since n.e.x.t
* @var string
*/
const PERFLAB_REST_API_NAMESPACE = 'performance-lab/v1';

/**
* Route for activating plugin/feature.
*
* Note the `:activate` art of the endpoint follows Google's guidance in AIP-136 for the use of the POST method in a way
* that does not strictly follow the standard usage.
*
* @since n.e.x.t
* @link https://google.aip.dev/136
* @var string
*/
const PERFLAB_FEATURES_ACTIVATE_ROUTE = '/features/(?P<slug>[a-z0-9_-]+):activate';

/**
* Route for fetching plugin/feature information.
*
* @since n.e.x.t
* @var string
*/
const PERFLAB_FEATURES_INFORMATION_ROUTE = '/features/(?P<slug>[a-z0-9_-]+)';

/**
* Registers endpoint for performance-lab REST API.
*
* @since n.e.x.t
* @access private
*/
function perflab_register_endpoint(): void {
register_rest_route(
PERFLAB_REST_API_NAMESPACE,
PERFLAB_FEATURES_ACTIVATE_ROUTE,
array(
'methods' => 'POST',
'args' => array(
'slug' => array(
'type' => 'string',
'description' => __( 'Plugin slug of the Performance Lab feature to be activated.', 'performance-lab' ),
'required' => true,
'validate_callback' => 'perflab_validate_slug_endpoint_arg',
),
),
'callback' => 'perflab_handle_feature_activation',
'permission_callback' => static function () {
// Important: The endpoint calls perflab_install_and_activate_plugin() which does more granular capability checks.
if ( current_user_can( 'activate_plugins' ) ) {
return true;
}

return new WP_Error( 'cannot_activate', __( 'Sorry, you are not allowed to activate this feature.', 'performance-lab' ) );
},
)
);

register_rest_route(
PERFLAB_REST_API_NAMESPACE,
PERFLAB_FEATURES_INFORMATION_ROUTE,
array(
'methods' => 'GET',
'args' => array(
'slug' => array(
'type' => 'string',
'description' => __( 'Plugin slug of plugin/feature whose information is needed.', 'performance-lab' ),
'required' => true,
'validate_callback' => 'perflab_validate_slug_endpoint_arg',
),
),
'callback' => 'perflab_handle_get_feature_information',
'permission_callback' => static function () {
if ( current_user_can( 'manage_options' ) ) {
return true;
}

return new WP_Error( 'cannot_access_plugin_settings_url', __( 'Sorry, you are not allowed to access plugin/feature information on this site.', 'performance-lab' ) );
},
)
);
}
add_action( 'rest_api_init', 'perflab_register_endpoint' );

/**
* Validates whether the provided plugin slug is a valid Performance Lab plugin.
*
* Note that an enum is not being used because additional PHP files have to be required to access the necessary functions,
* and this would not be ideal to do at rest_api_init.
*
* @since n.e.x.t
* @access private
*
* @param string $slug Plugin slug.
* @return bool Whether valid.
*/
function perflab_validate_slug_endpoint_arg( string $slug ): bool {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/load.php';
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/plugins.php';
return in_array( $slug, perflab_get_standalone_plugins(), true );
}

/**
* Handles REST API request to activate plugin/feature.
*
* @since n.e.x.t
* @access private
*
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
*
* @param WP_REST_Request $request Request.
* @return WP_REST_Response|WP_Error Response.
*/
function perflab_handle_feature_activation( WP_REST_Request $request ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';

// Install and activate the plugin/feature and its dependencies.
$result = perflab_install_and_activate_plugin( $request['slug'] );
if ( is_wp_error( $result ) ) {
switch ( $result->get_error_code() ) {
case 'cannot_install_plugin':
case 'cannot_activate_plugin':
$response_code = rest_authorization_required_code();
break;
case 'plugin_not_found':
$response_code = 404;
break;
default:
$response_code = 500;
}
return new WP_Error(
$result->get_error_code(),
$result->get_error_message(),
array( 'status' => $response_code )
);
}

return new WP_REST_Response(
array(
'success' => true,
)
);
}

/**
* Handles REST API request to get plugin/feature information.
*
* @since n.e.x.t
* @access private
*
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
*
* @param WP_REST_Request $request Request.
* @return WP_REST_Response Response.
*/
function perflab_handle_get_feature_information( WP_REST_Request $request ): WP_REST_Response {
$plugin_settings_url = perflab_get_plugin_settings_url( $request['slug'] );

return new WP_REST_Response(
array(
'slug' => $request['slug'],
'settingsUrl' => $plugin_settings_url,
)
);
}
3 changes: 3 additions & 0 deletions plugins/performance-lab/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,6 @@ function perflab_cleanup_option(): void {
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/server-timing.php';
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/plugins.php';
}

// Load REST API.
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/rest-api.php';

0 comments on commit 04a2e5a

Please sign in to comment.