Skip to content

Commit 04a2e5a

Browse files
authored
Merge pull request #1646 from b1ink0/update/use-ajax-for-activate-plugin
Use AJAX for activating features / plugins in Performance Lab
2 parents 7b57489 + e0691bf commit 04a2e5a

File tree

5 files changed

+284
-42
lines changed

5 files changed

+284
-42
lines changed

plugins/performance-lab/includes/admin/load.php

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ function perflab_load_features_page(): void {
4949

5050
// Handle style for settings page.
5151
add_action( 'admin_head', 'perflab_print_features_page_style' );
52-
53-
// Handle script for settings page.
54-
add_action( 'admin_footer', 'perflab_print_plugin_progress_indicator_script' );
5552
}
5653

5754
/**
@@ -228,8 +225,14 @@ function perflab_enqueue_features_page_scripts(): void {
228225
wp_enqueue_style( 'thickbox' );
229226
wp_enqueue_script( 'plugin-install' );
230227

231-
// Enqueue the a11y script.
232-
wp_enqueue_script( 'wp-a11y' );
228+
// Enqueue plugin activate AJAX script and localize script data.
229+
wp_enqueue_script(
230+
'perflab-plugin-activate-ajax',
231+
plugin_dir_url( PERFLAB_MAIN_FILE ) . 'includes/admin/plugin-activate-ajax.js',
232+
array( 'wp-i18n', 'wp-a11y', 'wp-api-fetch' ),
233+
PERFLAB_VERSION,
234+
true
235+
);
233236
}
234237

235238
/**
@@ -396,42 +399,6 @@ static function ( $name ) {
396399
}
397400
}
398401

399-
/**
400-
* Callback function that print plugin progress indicator script.
401-
*
402-
* @since 3.1.0
403-
*/
404-
function perflab_print_plugin_progress_indicator_script(): void {
405-
$js_function = <<<JS
406-
function addPluginProgressIndicator( message ) {
407-
document.addEventListener( 'DOMContentLoaded', function () {
408-
document.addEventListener( 'click', function ( event ) {
409-
if (
410-
event.target.classList.contains(
411-
'perflab-install-active-plugin'
412-
)
413-
) {
414-
const target = event.target;
415-
target.classList.add( 'updating-message' );
416-
target.textContent = message;
417-
418-
wp.a11y.speak( message );
419-
}
420-
} );
421-
} );
422-
}
423-
JS;
424-
425-
wp_print_inline_script_tag(
426-
sprintf(
427-
'( %s )( %s );',
428-
$js_function,
429-
wp_json_encode( __( 'Activating...', 'default' ) )
430-
),
431-
array( 'type' => 'module' )
432-
);
433-
}
434-
435402
/**
436403
* Gets the URL to the plugin settings screen if one exists.
437404
*
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Handles activation of Performance Features (Plugins) using AJAX.
3+
*/
4+
5+
( function () {
6+
// @ts-ignore
7+
const { i18n, a11y, apiFetch } = wp;
8+
const { __ } = i18n;
9+
10+
/**
11+
* Handles click events on elements with the class 'perflab-install-active-plugin'.
12+
*
13+
* This asynchronous function listens for click events on the document and executes
14+
* the provided callback function if triggered.
15+
*
16+
* @param {MouseEvent} event - The click event object that is triggered when the user clicks on the document.
17+
*
18+
* @return {Promise<void>} The asynchronous function returns a promise that resolves to void.
19+
*/
20+
async function handlePluginActivationClick( event ) {
21+
const target = /** @type {HTMLElement} */ ( event.target );
22+
23+
// Prevent the default link behavior.
24+
event.preventDefault();
25+
26+
if (
27+
target.classList.contains( 'updating-message' ) ||
28+
target.classList.contains( 'disabled' )
29+
) {
30+
return;
31+
}
32+
33+
target.classList.add( 'updating-message' );
34+
target.textContent = __( 'Activating…', 'performance-lab' );
35+
36+
a11y.speak( __( 'Activating…', 'performance-lab' ) );
37+
38+
const pluginSlug = target.dataset.pluginSlug;
39+
40+
try {
41+
// Activate the plugin/feature via the REST API.
42+
await apiFetch( {
43+
path: `/performance-lab/v1/features/${ pluginSlug }:activate`,
44+
method: 'POST',
45+
} );
46+
47+
// Fetch the plugin/feature information via the REST API.
48+
/** @type {{settingsUrl: string|null}} */
49+
const featureInfo = await apiFetch( {
50+
path: `/performance-lab/v1/features/${ pluginSlug }`,
51+
method: 'GET',
52+
} );
53+
54+
if ( featureInfo.settingsUrl ) {
55+
const actionButtonList = document.querySelector(
56+
`.plugin-card-${ pluginSlug } .plugin-action-buttons`
57+
);
58+
59+
const listItem = document.createElement( 'li' );
60+
const anchor = document.createElement( 'a' );
61+
62+
anchor.href = featureInfo.settingsUrl;
63+
anchor.textContent = __( 'Settings', 'performance-lab' );
64+
65+
listItem.appendChild( anchor );
66+
actionButtonList.appendChild( listItem );
67+
}
68+
69+
a11y.speak( __( 'Plugin activated.', 'performance-lab' ) );
70+
71+
target.textContent = __( 'Active', 'performance-lab' );
72+
target.classList.remove( 'updating-message' );
73+
target.classList.add( 'disabled' );
74+
} catch ( error ) {
75+
a11y.speak( __( 'Plugin failed to activate.', 'performance-lab' ) );
76+
77+
target.classList.remove( 'updating-message' );
78+
target.textContent = __( 'Activate', 'performance-lab' );
79+
}
80+
}
81+
82+
// Attach the event listeners.
83+
document
84+
.querySelectorAll( '.perflab-install-active-plugin' )
85+
.forEach( ( item ) => {
86+
item.addEventListener( 'click', handlePluginActivationClick );
87+
} );
88+
} )();

plugins/performance-lab/includes/admin/plugins.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,9 @@ function perflab_render_plugin_card( array $plugin_data ): void {
440440
);
441441

442442
$action_links[] = sprintf(
443-
'<a class="button perflab-install-active-plugin" href="%s">%s</a>',
443+
'<a class="button perflab-install-active-plugin" href="%s" data-plugin-slug="%s">%s</a>',
444444
esc_url( $url ),
445+
esc_attr( $plugin_data['slug'] ),
445446
esc_html__( 'Activate', 'default' )
446447
);
447448
} else {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
/**
3+
* REST API integration for the plugin.
4+
*
5+
* @package performance-lab
6+
* @since n.e.x.t
7+
*/
8+
9+
if ( ! defined( 'ABSPATH' ) ) {
10+
exit; // Exit if accessed directly.
11+
}
12+
13+
/**
14+
* Namespace for performance-lab REST API.
15+
*
16+
* @since n.e.x.t
17+
* @var string
18+
*/
19+
const PERFLAB_REST_API_NAMESPACE = 'performance-lab/v1';
20+
21+
/**
22+
* Route for activating plugin/feature.
23+
*
24+
* Note the `:activate` art of the endpoint follows Google's guidance in AIP-136 for the use of the POST method in a way
25+
* that does not strictly follow the standard usage.
26+
*
27+
* @since n.e.x.t
28+
* @link https://google.aip.dev/136
29+
* @var string
30+
*/
31+
const PERFLAB_FEATURES_ACTIVATE_ROUTE = '/features/(?P<slug>[a-z0-9_-]+):activate';
32+
33+
/**
34+
* Route for fetching plugin/feature information.
35+
*
36+
* @since n.e.x.t
37+
* @var string
38+
*/
39+
const PERFLAB_FEATURES_INFORMATION_ROUTE = '/features/(?P<slug>[a-z0-9_-]+)';
40+
41+
/**
42+
* Registers endpoint for performance-lab REST API.
43+
*
44+
* @since n.e.x.t
45+
* @access private
46+
*/
47+
function perflab_register_endpoint(): void {
48+
register_rest_route(
49+
PERFLAB_REST_API_NAMESPACE,
50+
PERFLAB_FEATURES_ACTIVATE_ROUTE,
51+
array(
52+
'methods' => 'POST',
53+
'args' => array(
54+
'slug' => array(
55+
'type' => 'string',
56+
'description' => __( 'Plugin slug of the Performance Lab feature to be activated.', 'performance-lab' ),
57+
'required' => true,
58+
'validate_callback' => 'perflab_validate_slug_endpoint_arg',
59+
),
60+
),
61+
'callback' => 'perflab_handle_feature_activation',
62+
'permission_callback' => static function () {
63+
// Important: The endpoint calls perflab_install_and_activate_plugin() which does more granular capability checks.
64+
if ( current_user_can( 'activate_plugins' ) ) {
65+
return true;
66+
}
67+
68+
return new WP_Error( 'cannot_activate', __( 'Sorry, you are not allowed to activate this feature.', 'performance-lab' ) );
69+
},
70+
)
71+
);
72+
73+
register_rest_route(
74+
PERFLAB_REST_API_NAMESPACE,
75+
PERFLAB_FEATURES_INFORMATION_ROUTE,
76+
array(
77+
'methods' => 'GET',
78+
'args' => array(
79+
'slug' => array(
80+
'type' => 'string',
81+
'description' => __( 'Plugin slug of plugin/feature whose information is needed.', 'performance-lab' ),
82+
'required' => true,
83+
'validate_callback' => 'perflab_validate_slug_endpoint_arg',
84+
),
85+
),
86+
'callback' => 'perflab_handle_get_feature_information',
87+
'permission_callback' => static function () {
88+
if ( current_user_can( 'manage_options' ) ) {
89+
return true;
90+
}
91+
92+
return new WP_Error( 'cannot_access_plugin_settings_url', __( 'Sorry, you are not allowed to access plugin/feature information on this site.', 'performance-lab' ) );
93+
},
94+
)
95+
);
96+
}
97+
add_action( 'rest_api_init', 'perflab_register_endpoint' );
98+
99+
/**
100+
* Validates whether the provided plugin slug is a valid Performance Lab plugin.
101+
*
102+
* Note that an enum is not being used because additional PHP files have to be required to access the necessary functions,
103+
* and this would not be ideal to do at rest_api_init.
104+
*
105+
* @since n.e.x.t
106+
* @access private
107+
*
108+
* @param string $slug Plugin slug.
109+
* @return bool Whether valid.
110+
*/
111+
function perflab_validate_slug_endpoint_arg( string $slug ): bool {
112+
require_once ABSPATH . 'wp-admin/includes/plugin.php';
113+
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/load.php';
114+
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/plugins.php';
115+
return in_array( $slug, perflab_get_standalone_plugins(), true );
116+
}
117+
118+
/**
119+
* Handles REST API request to activate plugin/feature.
120+
*
121+
* @since n.e.x.t
122+
* @access private
123+
*
124+
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
125+
*
126+
* @param WP_REST_Request $request Request.
127+
* @return WP_REST_Response|WP_Error Response.
128+
*/
129+
function perflab_handle_feature_activation( WP_REST_Request $request ) {
130+
require_once ABSPATH . 'wp-admin/includes/file.php';
131+
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
132+
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
133+
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
134+
135+
// Install and activate the plugin/feature and its dependencies.
136+
$result = perflab_install_and_activate_plugin( $request['slug'] );
137+
if ( is_wp_error( $result ) ) {
138+
switch ( $result->get_error_code() ) {
139+
case 'cannot_install_plugin':
140+
case 'cannot_activate_plugin':
141+
$response_code = rest_authorization_required_code();
142+
break;
143+
case 'plugin_not_found':
144+
$response_code = 404;
145+
break;
146+
default:
147+
$response_code = 500;
148+
}
149+
return new WP_Error(
150+
$result->get_error_code(),
151+
$result->get_error_message(),
152+
array( 'status' => $response_code )
153+
);
154+
}
155+
156+
return new WP_REST_Response(
157+
array(
158+
'success' => true,
159+
)
160+
);
161+
}
162+
163+
/**
164+
* Handles REST API request to get plugin/feature information.
165+
*
166+
* @since n.e.x.t
167+
* @access private
168+
*
169+
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
170+
*
171+
* @param WP_REST_Request $request Request.
172+
* @return WP_REST_Response Response.
173+
*/
174+
function perflab_handle_get_feature_information( WP_REST_Request $request ): WP_REST_Response {
175+
$plugin_settings_url = perflab_get_plugin_settings_url( $request['slug'] );
176+
177+
return new WP_REST_Response(
178+
array(
179+
'slug' => $request['slug'],
180+
'settingsUrl' => $plugin_settings_url,
181+
)
182+
);
183+
}

plugins/performance-lab/load.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,6 @@ function perflab_cleanup_option(): void {
339339
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/server-timing.php';
340340
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/plugins.php';
341341
}
342+
343+
// Load REST API.
344+
require_once PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/rest-api.php';

0 commit comments

Comments
 (0)