-
Notifications
You must be signed in to change notification settings - Fork 139
Use AJAX for activating features / plugins in Performance Lab #1646
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
Changes from all commits
47f3938
9d97f59
6f172ad
337da6c
58c37d9
a59909d
f9faaea
770d462
e6710a3
c3d3588
fdf7e0b
b3ecc98
c713e47
1ebe323
c134fe2
369487f
d84e9bd
9bee27e
ce217ba
fa5d869
3a333ae
6250658
a8d89a8
c593ae6
e0691bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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' ); | ||
| } | ||
| } | ||
|
|
||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Attach the event listeners. | ||
| document | ||
| .querySelectorAll( '.perflab-install-active-plugin' ) | ||
| .forEach( ( item ) => { | ||
| item.addEventListener( 'click', handlePluginActivationClick ); | ||
| } ); | ||
| } )(); | ||
| 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. | ||
| * | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @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. | ||
| * | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @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. | ||
| * | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @since n.e.x.t | ||
| * @var string | ||
| */ | ||
| const PERFLAB_FEATURES_INFORMATION_ROUTE = '/features/(?P<slug>[a-z0-9_-]+)'; | ||
|
|
||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /** | ||
| * 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', | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this validates against the list of PL features/plugins, should we include an If that feels like too much, I think we should at least mention it in the description, e.g. "Must be one of the Performance Lab feature slugs."
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initially proposed using an
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SG. In that case let's just mention it in the description.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ), | ||
| ), | ||
| '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', | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ), | ||
| ), | ||
| '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'] ); | ||
felixarntz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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, | ||
| ) | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.