diff --git a/admin/js/perflab-module-migration-notice.js b/admin/js/perflab-module-migration-notice.js new file mode 100644 index 0000000000..12844e801f --- /dev/null +++ b/admin/js/perflab-module-migration-notice.js @@ -0,0 +1,50 @@ +( function ( document ) { + document.addEventListener( 'DOMContentLoaded', function () { + document.addEventListener( 'click', function ( event ) { + if ( + event.target.classList.contains( + 'perflab-install-active-plugin' + ) + ) { + const target = event.target; + target.parentElement + .querySelector( 'span' ) + .classList.remove( 'hidden' ); + + const data = new FormData(); + data.append( + 'action', + 'perflab_install_activate_standalone_plugins' + ); + data.append( 'nonce', perflab_module_migration_notice.nonce ); + + fetch( perflab_module_migration_notice.ajaxurl, { + method: 'POST', + credentials: 'same-origin', + body: data, + } ) + .then( function ( response ) { + if ( ! response.ok ) { + throw new Error( + wp.i18n.__( 'Network response was not ok.', 'performance-lab' ) + ); + } + return response.json(); + } ) + .then( function ( result ) { + target.parentElement + .querySelector( 'span' ) + .classList.add( 'hidden' ); + if ( ! result.success ) { + alert( result.data.errorMessage ); + } + window.location.reload(); + } ) + .catch( function ( error ) { + alert( error.errorMessage ); + window.location.reload(); + } ); + } + } ); + } ); +} )( document ); diff --git a/admin/js/perflab-plugin-management.js b/admin/js/perflab-plugin-management.js new file mode 100644 index 0000000000..f36626204a --- /dev/null +++ b/admin/js/perflab-plugin-management.js @@ -0,0 +1,38 @@ +( function ( $, document ) { + $( document ).ajaxComplete( function( event, xhr, settings ) { + // Check if this is the 'install-plugin' request. + if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=install-plugin' ) ) { + var params = new URLSearchParams( settings.data ); + var slug = params.get('slug'); + + // Check if 'slug' was found and output the value. + if ( ! slug ) { + return; + } + + var target_element = $( '.wpp-standalone-plugins a[data-slug="' + slug + '"]' ); + if ( ! target_element ) { + return; + } + + /* + * WordPress core uses a 1s timeout for updating the activation link, + * so we set a 1.5 timeout here to ensure our changes get updated after + * the core changes have taken place. + */ + setTimeout( function() { + var plugin_url = target_element.attr( 'href' ); + if ( ! plugin_url ) { + return; + } + var nonce = target_element.attr( 'data-plugin-activation-nonce' ); + var plugin_slug = target_element.attr( 'data-slug' ); + var url = new URL( plugin_url ); + url.searchParams.set( 'action', 'perflab_activate_plugin' ); + url.searchParams.set( '_wpnonce', nonce ); + url.searchParams.set( 'plugin', plugin_slug ); + target_element.attr( 'href', url.href ); + }, 1500 ); + } + } ); +} )( jQuery, document ); diff --git a/admin/load.php b/admin/load.php index 36bf0ade8b..2e04628959 100644 --- a/admin/load.php +++ b/admin/load.php @@ -53,6 +53,15 @@ function perflab_add_modules_page() { function perflab_load_modules_page( $modules = null, $focus_areas = null ) { global $wp_settings_sections; + // Handle script enqueuing for settings page. + add_action( 'admin_enqueue_scripts', 'perflab_enqueue_modules_page_scripts' ); + + // Handle style for settings page. + add_action( 'admin_head', 'perflab_print_modules_page_style' ); + + // Handle admin notices for settings page. + add_action( 'admin_notices', 'perflab_plugin_admin_notices' ); + // Register sections for all focus areas, plus 'Other'. if ( ! is_array( $focus_areas ) ) { $focus_areas = perflab_get_focus_areas(); @@ -107,9 +116,11 @@ static function () use ( $module_slug, $module_data, $module_settings ) { function perflab_render_modules_page() { ?>
-

+ + +

-

+
@@ -378,8 +389,9 @@ function perflab_get_module_data( $module_file ) { * Handles the bootstrapping of the admin pointer. * Mainly jQuery code that is self-initialising. * - * @param string $hook_suffix The current admin page. * @since 1.0.0 + * + * @param string $hook_suffix The current admin page. */ function perflab_admin_pointer( $hook_suffix ) { if ( ! in_array( $hook_suffix, array( 'index.php', 'plugins.php' ), true ) ) { @@ -394,6 +406,40 @@ function perflab_admin_pointer( $hook_suffix ) { $current_user = get_current_user_id(); $dismissed = explode( ',', (string) get_user_meta( $current_user, 'dismissed_wp_pointers', true ) ); + /* + * If there are any active modules with inactive standalone plugins, + * show an admin pointer to prompt the user to migrate. + */ + $active_modules_with_inactive_plugins = perflab_get_active_module_data_with_inactive_standalone_plugins(); + if ( + ! empty( $active_modules_with_inactive_plugins ) + && current_user_can( 'install_plugins' ) + && current_user_can( 'activate_plugins' ) + && ! in_array( 'perflab-module-migration-pointer', $dismissed, true ) + ) { + // Enqueue the pointer logic and return early. + wp_enqueue_style( 'wp-pointer' ); + wp_enqueue_script( 'wp-pointer' ); + add_action( + 'admin_print_footer_scripts', + static function () { + $content = sprintf( + /* translators: %s: settings page link */ + esc_html__( 'Your site is using modules which will be removed in the future in favor of their equivalent standalone plugins. Open %s to learn more about next steps to keep the functionality available.', 'performance-lab' ), + '' . esc_html__( 'Settings > Performance', 'performance-lab' ) . '' + ); + perflab_render_pointer( + 'perflab-module-migration-pointer', + array( + 'heading' => __( 'Performance Lab: Action required', 'performance-lab' ), + 'content' => $content, + ) + ); + } + ); + return; + } + if ( in_array( 'perflab-admin-pointer', $dismissed, true ) ) { return; } @@ -424,8 +470,8 @@ function perflab_render_pointer( $pointer_id = 'perflab-admin-pointer', $args = if ( ! isset( $args['content'] ) ) { $args['content'] = sprintf( /* translators: %s: settings page link */ - __( 'You can now test upcoming WordPress performance features. Open %s to individually toggle the performance features included in the plugin.', 'performance-lab' ), - '' . __( 'Settings > Performance', 'performance-lab' ) . '' + esc_html__( 'You can now test upcoming WordPress performance features. Open %s to individually toggle the performance features included in the plugin.', 'performance-lab' ), + '' . esc_html__( 'Settings > Performance', 'performance-lab' ) . '' ); } @@ -471,6 +517,7 @@ function perflab_render_pointer( $pointer_id = 'perflab-admin-pointer', $args = * This function is only used if the modules page exists and is accessible. * * @since 1.0.0 + * * @see perflab_add_modules_page() * * @param array $links List of plugin action links HTML. @@ -495,6 +542,7 @@ function perflab_plugin_action_links_add_settings( $links ) { * It runs before the dismiss-wp-pointer AJAX action is performed. * * @since 2.3.0 + * * @see perflab_render_modules_pointer() */ function perflab_dismiss_wp_pointer_wrapper() { @@ -505,3 +553,341 @@ function perflab_dismiss_wp_pointer_wrapper() { check_ajax_referer( 'dismiss_pointer' ); } add_action( 'wp_ajax_dismiss-wp-pointer', 'perflab_dismiss_wp_pointer_wrapper', 0 ); + +/** + * Callback function to handle admin scripts. + * + * @since n.e.x.t + */ +function perflab_enqueue_modules_page_scripts() { + wp_enqueue_script( 'updates' ); + + wp_localize_script( + 'updates', + '_wpUpdatesItemCounts', + array( + 'settings' => array( + 'totals' => wp_get_update_data(), + ), + ) + ); + + wp_enqueue_script( 'thickbox' ); + wp_enqueue_style( 'thickbox' ); + + wp_enqueue_script( 'plugin-install' ); + + wp_enqueue_script( + 'perflab-plugin-management', + plugin_dir_url( __FILE__ ) . 'js/perflab-plugin-management.js', + array(), + '1.0.0', + array( + 'in_footer' => true, + 'strategy' => 'defer', + ) + ); + + // Bail early if module is not active. + $get_active_modules_with_standalone_plugins = perflab_get_active_modules_with_standalone_plugins(); + if ( empty( $get_active_modules_with_standalone_plugins ) ) { + return; + } + + wp_enqueue_script( + 'perflab-module-migration-notice', + plugin_dir_url( __FILE__ ) . 'js/perflab-module-migration-notice.js', + array( 'wp-i18n' ), + '1.0.0', + array( + 'strategy' => 'defer', + ) + ); + + wp_localize_script( + 'perflab-module-migration-notice', + 'perflab_module_migration_notice', + array( + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'perflab-install-activate-plugins' ), + ) + ); +} + +/** + * Callback function hooked to admin_action_perflab_activate_plugin to handle plugin activation. + * + * @since n.e.x.t + */ +function perflab_activate_plugin() { + // Do not proceed if plugin query arg is not present. + if ( empty( $_GET['plugin'] ) ) { + return; + } + + $plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) ); + + check_admin_referer( "perflab_activate_plugin_{$plugin}" ); + + // If `$plugin` is a plugin slug rather than a plugin basename, determine the full plugin basename. + if ( ! str_contains( $plugin, '/' ) ) { + $plugins = get_plugins( '/' . $plugin ); + + if ( empty( $plugins ) ) { + wp_die( esc_html__( 'Plugin not found.', 'default' ) ); + } + + $plugin_file_names = array_keys( $plugins ); + $plugin = $plugin . '/' . $plugin_file_names[0]; + } + + if ( ! current_user_can( 'activate_plugin', $plugin ) ) { + wp_die( esc_html__( 'Sorry, you are not allowed to activate this plugin.', 'default' ) ); + } + + // Activate the plugin in question and return to prior screen. + $do_plugin_activation = activate_plugins( $plugin ); + $referer = wp_get_referer(); + if ( ! is_wp_error( $do_plugin_activation ) ) { + $referer = add_query_arg( + array( + 'activate' => true, + ), + $referer + ); + } + + if ( wp_safe_redirect( $referer ) ) { + exit; + } +} +add_action( 'admin_action_perflab_activate_plugin', 'perflab_activate_plugin' ); + +/** + * Callback function hooked to admin_action_perflab_deactivate_plugin to handle plugin deactivation. + * + * @since n.e.x.t + */ +function perflab_deactivate_plugin() { + // Do not proceed if plugin query arg is not present. + if ( empty( $_GET['plugin'] ) ) { + return; + } + + // The plugin being deactivated. + $plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) ); + + check_admin_referer( "perflab_deactivate_plugin_{$plugin}" ); + + if ( ! current_user_can( 'deactivate_plugin', $plugin ) ) { + wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this plugin.', 'default' ) ); + } + + // Deactivate the plugin in question and return to prior screen. + $do_plugin_deactivation = deactivate_plugins( $plugin ); + $referer = wp_get_referer(); + if ( ! is_wp_error( $do_plugin_deactivation ) ) { + $referer = add_query_arg( + array( + 'deactivate' => true, + ), + $referer + ); + } + + if ( wp_safe_redirect( $referer ) ) { + exit; + } +} +add_action( 'admin_action_perflab_deactivate_plugin', 'perflab_deactivate_plugin' ); + +// WordPress AJAX action to handle the button click event. +add_action( 'wp_ajax_perflab_install_activate_standalone_plugins', 'perflab_install_activate_standalone_plugins_callback' ); + +/** + * Handles the standalone plugin install and activation via AJAX. + * + * @since n.e.x.t + */ +function perflab_install_activate_standalone_plugins_callback() { + if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'perflab-install-activate-plugins' ) ) { + $status['errorMessage'] = __( 'Invalid nonce: Please refresh and try again.', 'performance-lab' ); + wp_send_json_error( $status ); + } + + if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site. Please contact the administrator.', 'performance-lab' ); + wp_send_json_error( $status ); + } + + 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'; + + $plugins_to_activate = perflab_get_active_modules_with_standalone_plugins(); + $modules = perflab_get_module_settings(); + $plugins = get_plugins(); + $status = array(); + + foreach ( $plugins_to_activate as $module_slug ) { + + // Skip checking for already activated plugin. + if ( perflab_is_standalone_plugin_loaded( $module_slug ) ) { + continue; + } + + $plugin_slug = basename( $module_slug ); + $plugin_basename = $plugin_slug . '/load.php'; + $api = perflab_query_plugin_info( $plugin_slug ); + + // Return early if plugin API return an error. + if ( ! $api ) { + $status['errorMessage'] = html_entity_decode( __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration.', 'performance-lab' ), ENT_QUOTES ); + wp_send_json_error( $status ); + } + + if ( ! $plugin_slug ) { + $status['errorMessage'] = __( 'Invalid plugin.', 'performance-lab' ); + wp_send_json_error( $status ); + } + + // Install the plugin if it is not installed yet. + if ( ! isset( $plugins[ $plugin_basename ] ) ) { + // 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 ) ) { + $status['errorMessage'] = $result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( is_wp_error( $skin->result ) ) { + $status['errorMessage'] = $skin->result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( $skin->get_errors()->has_errors() ) { + $status['errorMessage'] = $skin->get_error_messages(); + wp_send_json_error( $status ); + } + } + + $result = activate_plugin( $plugin_basename ); + if ( is_wp_error( $result ) ) { + $status['errorMessage'] = $result->get_error_message(); + wp_send_json_error( $status ); + } + + // Deactivate legacy modules. + unset( $modules[ $module_slug ] ); + + update_option( PERFLAB_MODULES_SETTING, $modules ); + } + wp_send_json_success( $status ); +} + +/** + * Callback function hooked to admin_notices to render admin notices on the plugin's screen. + * + * @since n.e.x.t + */ +function perflab_plugin_admin_notices() { + if ( isset( $_GET['activate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ?> +
+

+
+ +
+

+
+ '; + $message .= sprintf( + /* translators: Module name */ + esc_html__( 'Your site is using the "%s" module which will be removed in the future in favor of its equivalent standalone plugin.', 'performance-lab' ), + esc_attr( $available_module_names[0] ) + ); + $message .= ' '; + $message .= esc_html__( 'Please click the following button to install and activate the relevant plugin in favor of the module. This will not impact any of the underlying functionality.', 'performance-lab' ); + $message .= '

'; + } else { + $message = '

'; + $message .= esc_html__( 'Your site is using modules which will be removed in the future in favor of their equivalent standalone plugins.', 'performance-lab' ); + $message .= ' '; + $message .= esc_html__( 'Please click the following button to install and activate the relevant plugins in favor of the modules. This will not impact any of the underlying functionality.', 'performance-lab' ); + $message .= '

'; + $message .= '' . esc_html__( 'Available standalone plugins:', 'performance-lab' ) . ''; + $message .= '
    '; + foreach ( $available_module_names as $module_name ) { + $message .= sprintf( '
  1. %s
  2. ', esc_html( $module_name ) ); + } + $message .= '
'; + } + + ?> +
+ +

+ + +

+
+ + + $plugin_slug, + 'fields' => array( + 'short_description' => true, + 'icons' => true, + ), + ) + ); + + if ( is_wp_error( $plugin ) ) { + return array(); + } + + if ( is_object( $plugin ) ) { + $plugin = (array) $plugin; + } + + return $plugin; +} + +/** + * Returns an array of WPP standalone plugins. + * + * @since n.e.x.t + * + * @return array List of WPP standalone plugins as slugs. + */ +function perflab_get_standalone_plugins() { + return array( + 'webp-uploads', + 'performant-translations', + 'dominant-color-images', + ); +} + +/** + * Returns an array of standalone plugins with currently active modules. + * + * @since n.e.x.t + * + * @return string[] + */ +function perflab_get_active_modules_with_standalone_plugins() { + $modules = perflab_get_module_settings(); + return array_filter( + array_keys( perflab_get_standalone_plugins_constants() ), + static function ( $module ) use ( $modules ) { + return ! empty( $modules[ $module ] ) && $modules[ $module ]['enabled']; + } + ); +} + +/** + * Renders plugin UI for managing standalone plugins within PL Settings screen. + * + * @since n.e.x.t + */ +function perflab_render_plugins_ui() { + require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + + $standalone_plugins = array(); + foreach ( perflab_get_standalone_plugins() as $managed_standalone_plugin_slug ) { + $standalone_plugins[ $managed_standalone_plugin_slug ] = array( + 'plugin_data' => perflab_query_plugin_info( $managed_standalone_plugin_slug ), + ); + } + + if ( empty( $standalone_plugins ) ) { + return; + } + ?> +
+

+

+
+ +
+

+
+ +
+
+ +
+
+
+ ' . sprintf( __( 'By %s', 'default' ), $author ) . ''; + } + + $requires_php = isset( $plugin_data['requires_php'] ) ? $plugin_data['requires_php'] : null; + $requires_wp = isset( $plugin_data['requires'] ) ? $plugin_data['requires'] : null; + + $compatible_php = is_php_version_compatible( $requires_php ); + $compatible_wp = is_wp_version_compatible( $requires_wp ); + $tested_wp = ( empty( $plugin_data['tested'] ) || version_compare( get_bloginfo( 'version' ), $plugin_data['tested'], '<=' ) ); + $action_links = array(); + + $status = install_plugin_install_status( $plugin_data ); + + switch ( $status['status'] ) { + case 'install': + if ( $status['url'] ) { + if ( $compatible_php && $compatible_wp && current_user_can( 'install_plugins' ) ) { + $action_links[] = sprintf( + '%s', + esc_attr( $plugin_data['slug'] ), + esc_url( $status['url'] ), + /* translators: %s: Plugin name and version. */ + esc_attr( sprintf( _x( 'Install %s now', 'plugin', 'default' ), $name ) ), + esc_attr( $name ), + esc_attr( wp_create_nonce( 'perflab_activate_plugin_' . $plugin_data['slug'] ) ), + esc_html__( 'Install Now', 'default' ) + ); + } else { + $action_links[] = sprintf( + '', + esc_html( _x( 'Cannot Install', 'plugin', 'default' ) ) + ); + } + } + break; + + case 'update_available': + case 'latest_installed': + case 'newer_installed': + if ( is_plugin_active( $status['file'] ) ) { + $action_links[] = sprintf( + '', + esc_html( _x( 'Active', 'plugin', 'default' ) ) + ); + if ( current_user_can( 'deactivate_plugin', $status['file'] ) ) { + global $page; + $s = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $context = $status['status']; + + $action_links[] = sprintf( + '%s', + add_query_arg( + array( + '_wpnonce' => wp_create_nonce( 'perflab_deactivate_plugin_' . $status['file'] ), + 'action' => 'perflab_deactivate_plugin', + 'plugin' => $status['file'], + ), + network_admin_url( 'plugins.php' ) + ), + esc_attr( $plugin_data['slug'] ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Deactivate %s', 'plugin', 'default' ), $plugin_data['slug'] ) ), + esc_html__( 'Deactivate', 'default' ) + ); + } + } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) { + if ( $compatible_php && $compatible_wp ) { + $button_text = __( 'Activate', 'default' ); + /* translators: %s: Plugin name. */ + $button_label = _x( 'Activate %s', 'plugin', 'default' ); + $activate_url = add_query_arg( + array( + '_wpnonce' => wp_create_nonce( 'perflab_activate_plugin_' . $status['file'] ), + 'action' => 'perflab_activate_plugin', + 'plugin' => $status['file'], + ), + network_admin_url( 'plugins.php' ) + ); + + $action_links[] = sprintf( + '%3$s', + esc_url( $activate_url ), + esc_attr( sprintf( $button_label, $plugin_data['name'] ) ), + esc_html( $button_text ) + ); + } else { + $action_links[] = sprintf( + '', + esc_html( _x( 'Cannot Activate', 'plugin', 'default' ) ) + ); + } + } else { + $action_links[] = sprintf( + '', + esc_html( _x( 'Installed', 'plugin', 'default' ) ) + ); + } + break; + } + + $details_link = esc_url_raw( + add_query_arg( + array( + 'tab' => 'plugin-information', + 'plugin' => $plugin_data['slug'], + 'TB_iframe' => 'true', + 'width' => 600, + 'height' => 550, + ), + admin_url( 'plugin-install.php' ) + ) + ); + + $action_links[] = sprintf( + '%s', + esc_url( $details_link ), + /* translators: %s: Plugin name and version. */ + esc_attr( sprintf( __( 'More information about %s', 'default' ), $name ) ), + esc_attr( $name ), + esc_html__( 'More Details', 'default' ) + ); + + if ( ! empty( $plugin_data['icons']['svg'] ) ) { + $plugin_icon_url = $plugin_data['icons']['svg']; + } elseif ( ! empty( $plugin_data['icons']['2x'] ) ) { + $plugin_icon_url = $plugin_data['icons']['2x']; + } elseif ( ! empty( $plugin_data['icons']['1x'] ) ) { + $plugin_icon_url = $plugin_data['icons']['1x']; + } else { + $plugin_icon_url = $plugin_data['icons']['default']; + } + + /** This filter is documented in wp-admin/includes/class-wp-plugin-install-list-table.php */ + $action_links = apply_filters( 'plugin_install_action_links', $action_links, $plugin_data ); + + $last_updated_timestamp = strtotime( $plugin_data['last_updated'] ); + ?> +
+ '; + if ( ! $compatible_php && ! $compatible_wp ) { + echo '

' . esc_html_e( 'This plugin does not work with your versions of WordPress and PHP.', 'default' ) . '

'; + if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { + echo wp_kses_post( + /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ + ' ' . __( 'Please update WordPress, and then learn more about updating PHP.', 'default' ), + esc_url( self_admin_url( 'update-core.php' ) ), + esc_url( wp_get_update_php_url() ) + ); + wp_update_php_annotation( '

', '

' ); + } elseif ( current_user_can( 'update_core' ) ) { + echo wp_kses_post( + /* translators: %s: URL to WordPress Updates screen. */ + ' ' . __( 'Please update WordPress.', 'default' ), + esc_url( self_admin_url( 'update-core.php' ) ) + ); + } elseif ( current_user_can( 'update_php' ) ) { + echo wp_kses_post( + /* translators: %s: URL to Update PHP page. */ + ' ' . __( 'Learn more about updating PHP.', 'default' ), + esc_url( wp_get_update_php_url() ) + ); + wp_update_php_annotation( '

', '

' ); + } + } elseif ( ! $compatible_wp ) { + esc_html_e( 'This plugin does not work with your version of WordPress.', 'default' ); + if ( current_user_can( 'update_core' ) ) { + echo wp_kses_post( + /* translators: %s: URL to WordPress Updates screen. */ + ' ' . __( 'Please update WordPress.', 'default' ), + esc_url( self_admin_url( 'update-core.php' ) ) + ); + } + } elseif ( ! $compatible_php ) { + esc_html_e( 'This plugin does not work with your version of PHP.', 'default' ); + if ( current_user_can( 'update_php' ) ) { + echo wp_kses_post( + /* translators: %s: URL to Update PHP page. */ + ' ' . __( 'Learn more about updating PHP.', 'default' ), + esc_url( wp_get_update_php_url() ) + ); + wp_update_php_annotation( '

', '

' ); + } + } + echo '
'; + } + ?> +
+
+

+ + + + +

+
+ +
+

+

+
+
+
+
+ $plugin_data['rating'], + 'type' => 'percent', + 'number' => $plugin_data['num_ratings'], + ) + ); + ?> + +
+
+ + +
+
+ = 1000000 ) { + $active_installs_millions = floor( $plugin_data['active_installs'] / 1000000 ); + $active_installs_text = sprintf( + /* translators: %s: Number of millions. */ + _nx( '%s+ Million', '%s+ Million', $active_installs_millions, 'Active plugin installations', 'default' ), + number_format_i18n( $active_installs_millions ) + ); + } elseif ( 0 === $plugin_data['active_installs'] ) { + $active_installs_text = _x( 'Less Than 10', 'Active plugin installations', 'default' ); + } else { + $active_installs_text = number_format_i18n( $plugin_data['active_installs'] ) . '+'; + } + /* translators: %s: Number of installations. */ + printf( esc_html__( '%s Active Installations', 'default' ), esc_html( $active_installs_text ) ); + ?> +
+
+ ' . esc_html__( 'Untested with your version of WordPress', 'default' ) . ''; + } elseif ( ! $compatible_wp ) { + echo '' . wp_kses_post( __( 'Incompatible with your version of WordPress', 'default' ) ) . ''; + } else { + echo '' . wp_kses_post( __( 'Compatible with your version of WordPress', 'default' ) ) . ''; + } + ?> +
+
+
+ $module_settings ) { if ( ! empty( $module_settings['enabled'] ) && ( empty( $old_value[ $module ] ) || empty( $old_value[ $module ]['enabled'] ) ) ) { perflab_activate_module( PERFLAB_PLUGIN_DIR_PATH . 'modules/' . $module ); + $reset_migration_pointer_dismissals = true; + } + } + if ( $reset_migration_pointer_dismissals ) { + // Retrieve a list of active modules with associated standalone plugins. + $active_modules_with_plugins = perflab_get_active_modules_with_standalone_plugins(); + + /* + * Check if there are any active modules with compatible standalone plugins. + * If no such modules are found bail early. + */ + if ( empty( $active_modules_with_plugins ) ) { + return; + } + + $current_user = wp_get_current_user(); + + /* + * Disable WordPress pointers for specific users based on conditions. + * + * Checks if there is a large user count on the site. If true, + * disables pointers for the current user only. Otherwise, disables + * pointers for users with the same role as the current user. + */ + if ( wp_is_large_user_count() ) { + perflab_undismiss_module_migration_pointer( $current_user ); + } else { + $current_user_roles = $current_user->roles; + $current_user_role = array_shift( $current_user_roles ); + + $args = array( + 'role' => $current_user_role, + 'meta_query' => array( + array( + 'key' => 'dismissed_wp_pointers', + 'value' => 'perflab-module-migration-pointer', + 'compare' => 'LIKE', + ), + ), + ); + + $users = get_users( $args ); + + foreach ( $users as $user ) { + perflab_undismiss_module_migration_pointer( $user ); + } } } } @@ -484,6 +532,27 @@ function perflab_run_module_activation_deactivation( $old_value, $value ) { return $value; } +/** + * Reverts the module migration pointer dismissal for the given user. + * + * @since n.e.x.t + * + * @param WP_User $user The WP_User object. + */ +function perflab_undismiss_module_migration_pointer( $user ) { + $dismissed = array_filter( explode( ',', (string) get_user_meta( $user->ID, 'dismissed_wp_pointers', true ) ) ); + + $pointer_index = array_search( 'perflab-module-migration-pointer', $dismissed, true ); + if ( false === $pointer_index ) { + return; + } + + unset( $dismissed[ $pointer_index ] ); + $dismissed = implode( ',', $dismissed ); + + update_user_meta( $user->ID, 'dismissed_wp_pointers', $dismissed ); +} + /** * Activate a module. * diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9a7fc87a18..36129a86c3 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -44,6 +44,7 @@ static function() { require_once TESTS_PLUGIN_DIR . '/admin/load.php'; require_once TESTS_PLUGIN_DIR . '/admin/server-timing.php'; + require_once TESTS_PLUGIN_DIR . '/admin/plugins.php'; $module_files = glob( TESTS_PLUGIN_DIR . '/modules/*/*/load.php' ); if ( $module_files ) { foreach ( $module_files as $module_file ) {