diff --git a/.github/workflows/php-test-standalone-plugins.yml b/.github/workflows/php-test-standalone-plugins.yml index 0b01cd9ec9..1f6757d430 100644 --- a/.github/workflows/php-test-standalone-plugins.yml +++ b/.github/workflows/php-test-standalone-plugins.yml @@ -76,8 +76,6 @@ jobs: node --version composer --version php -v - - name: Building standalone plugins - run: npm run build-plugins - name: Running single site standalone plugin integration tests run: npm run test-plugins - name: Running multisite standalone plugin integration tests diff --git a/README.md b/README.md index c8d8407273..34fe58479b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Performance Lab ![Performance Lab plugin banner with icon](https://user-images.githubusercontent.com/3531426/159084476-af352db4-192e-4927-a383-7f76bb3641df.png) -Monorepo for the [WordPress Performance Team](https://make.wordpress.org/performance/), primarily for the Performance Lab plugin, which is a collection of standalone performance modules. +Monorepo for the [WordPress Performance Team](https://make.wordpress.org/performance/), primarily for the Performance Lab plugin, which is a collection of standalone performance features. Details about the Performance Lab plugin, including instructions for getting started and contributing, are available in the [Performance Team Handbook here](https://make.wordpress.org/performance/handbook/performance-lab/). diff --git a/admin/js/perflab-module-migration-notice.js b/admin/js/perflab-module-migration-notice.js deleted file mode 100644 index bdebc98f26..0000000000 --- a/admin/js/perflab-module-migration-notice.js +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint camelcase: "off", no-alert: "off" */ -/* global perflab_module_migration_notice:false */ - -( 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/load.php b/admin/load.php index d29937498c..3b89a6db8d 100644 --- a/admin/load.php +++ b/admin/load.php @@ -1,6 +1,6 @@ __( 'Other', 'performance-lab' ) ); - foreach ( $sections as $section_slug => $section_data ) { - add_settings_section( - $section_slug, - $section_data['name'], - null, - PERFLAB_MODULES_SCREEN - ); - } - - // Register fields for all modules. - if ( ! is_array( $modules ) ) { - $modules = perflab_get_modules(); - } - $settings = perflab_get_module_settings(); - foreach ( $modules as $module_slug => $module_data ) { - $module_settings = isset( $settings[ $module_slug ] ) ? $settings[ $module_slug ] : array(); - $module_section = isset( $sections[ $module_data['focus'] ] ) ? $module_data['focus'] : 'other'; - - // Mark this module's section as added. - $sections[ $module_section ]['added'] = true; - - add_settings_field( - $module_slug, - $module_data['name'], - static function () use ( $module_slug, $module_data, $module_settings ) { - perflab_render_modules_page_field( $module_slug, $module_data, $module_settings ); - }, - PERFLAB_MODULES_SCREEN, - $module_section - ); - } - - // Remove all sections for which there are no modules. - foreach ( $sections as $section_slug => $section_data ) { - if ( empty( $section_data['added'] ) ) { - unset( $wp_settings_sections[ PERFLAB_MODULES_SCREEN ][ $section_slug ] ); - } - } } /** - * Renders the modules page. + * Renders the plugin page. * * @since 1.0.0 + * @since n.e.x.t Renamed to perflab_render_settings_page(). */ -function perflab_render_modules_page() { +function perflab_render_settings_page() { ?>
- -

- -

- -
- - - -
-
- - - - -

" class="description"> - -

-
- array( - 'name' => __( 'Images', 'performance-lab' ), - ), - 'js-and-css' => array( - 'name' => __( 'JS & CSS', 'performance-lab' ), - ), - 'database' => array( - 'name' => __( 'Database', 'performance-lab' ), - ), - 'measurement' => array( - 'name' => __( 'Measurement', 'performance-lab' ), - ), - 'object-cache' => array( - 'name' => __( 'Object Cache', 'performance-lab' ), - ), - ); -} - -/** - * Gets all available modules. - * - * This function iterates through the modules directory and therefore should only be called on the modules page. - * It searches all modules, similar to how plugins are searched in the WordPress core function `get_plugins()`. - * - * @since 1.0.0 - * - * @param string $modules_root Modules root directory to look for modules in. Default is the `/modules` directory - * in the plugin's root. - * @return array Associative array of parsed module data, keyed by module slug. Fields for every module include - * 'name', 'description', 'focus', and 'experimental'. - */ -function perflab_get_modules( $modules_root = null ) { - if ( null === $modules_root ) { - $modules_root = dirname( __DIR__ ) . '/modules'; - } - - $modules = array(); - $module_files = array(); - // PHPCS ignore reason: A modules directory is always present. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged - $modules_dir = @opendir( $modules_root ); - - // Modules are organized as {focus}/{module-slug} in the modules folder. - if ( $modules_dir ) { - // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - while ( ( $focus = readdir( $modules_dir ) ) !== false ) { - if ( '.' === substr( $focus, 0, 1 ) ) { - continue; - } - - // Each focus area must be a directory. - if ( ! is_dir( $modules_root . '/' . $focus ) ) { - continue; - } - - // PHPCS ignore reason: Only the focus area directory is allowed. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged - $focus_dir = @opendir( $modules_root . '/' . $focus ); - if ( $focus_dir ) { - // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - while ( ( $file = readdir( $focus_dir ) ) !== false ) { - // Unlike plugins, modules must be in a directory. - if ( ! is_dir( $modules_root . '/' . $focus . '/' . $file ) ) { - continue; - } - - // PHPCS ignore reason: Only the module directory is allowed. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged - $module_dir = @opendir( $modules_root . '/' . $focus . '/' . $file ); - if ( $module_dir ) { - // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - while ( ( $subfile = readdir( $module_dir ) ) !== false ) { - if ( '.' === substr( $subfile, 0, 1 ) ) { - continue; - } - - // Unlike plugins, module main files must be called `load.php`. - if ( 'load.php' !== $subfile ) { - continue; - } - - $module_files[] = "$focus/$file/$subfile"; - } - - closedir( $module_dir ); - } - } - - closedir( $focus_dir ); - } - } - - closedir( $modules_dir ); - } - - foreach ( $module_files as $module_file ) { - if ( ! is_readable( "$modules_root/$module_file" ) ) { - continue; - } - $module_dir = dirname( $module_file ); - $module_data = perflab_get_module_data( "$modules_root/$module_file" ); - if ( ! $module_data ) { - continue; - } - - $modules[ $module_dir ] = $module_data; - } - - uasort( - $modules, - static function ( $a, $b ) { - return strnatcasecmp( $a['name'], $b['name'] ); - } - ); - - return $modules; -} - -/** - * Parses the module main file to get the module's metadata. - * - * This is similar to how plugin data is parsed in the WordPress core function `get_plugin_data()`. - * The user-facing strings will be translated. - * - * @since 1.0.0 - * - * @param string $module_file Absolute path to the main module file. - * @return array|bool Associative array of parsed module data, or false on failure. Fields for every module include - * 'name', 'description', 'focus', and 'experimental'. - */ -function perflab_get_module_data( $module_file ) { - // Extract the module dir in the form {focus}/{module-slug}. - preg_match( '/.*\/(.*\/.*)\/load\.php$/i', $module_file, $matches ); - $module_dir = $matches[1]; - - $default_headers = array( - 'name' => 'Module Name', - 'description' => 'Description', - 'experimental' => 'Experimental', - ); - - $module_data = get_file_data( $module_file, $default_headers, 'perflab_module' ); - - // Module name and description are the minimum requirements. - if ( ! $module_data['name'] || ! $module_data['description'] ) { - return false; - } - - // Experimental should be a boolean. - if ( 'yes' === strtolower( trim( $module_data['experimental'] ) ) ) { - $module_data['experimental'] = true; - } else { - $module_data['experimental'] = false; - } - - // Extract the module focus from the module directory. - if ( strpos( $module_dir, '/' ) ) { - list( $focus, $slug ) = explode( '/', $module_dir ); - $module_data['focus'] = $focus; - $module_data['slug'] = $slug; - } - - // Translate fields using low-level function since they come from PHP comments, including the necessary context for - // `_x()`. This must match how these are translated in the generated `/module-i18n.php` file. - $translatable_fields = array( - 'name' => 'module name', - 'description' => 'module description', - ); - foreach ( $translatable_fields as $field => $context ) { - // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralText - $module_data[ $field ] = translate_with_gettext_context( $module_data[ $field ], $context, 'performance-lab' ); - } - - return $module_data; -} - /** * Initializes admin pointer. * @@ -406,39 +86,6 @@ 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; } @@ -469,8 +116,8 @@ function perflab_render_pointer( $pointer_id = 'perflab-admin-pointer', $args = if ( ! isset( $args['content'] ) ) { $args['content'] = sprintf( /* translators: %s: settings page link */ - 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' ) . '' + esc_html__( 'You can now test upcoming WordPress performance features. Open %s to individually toggle the performance features.', 'performance-lab' ), + '' . esc_html__( 'Settings > Performance', 'performance-lab' ) . '' ); } @@ -517,7 +164,7 @@ function perflab_render_pointer( $pointer_id = 'perflab-admin-pointer', $args = * * @since 1.0.0 * - * @see perflab_add_modules_page() + * @see perflab_add_features_page() * * @param array $links List of plugin action links HTML. * @return array Modified list of plugin action links HTML. @@ -526,7 +173,7 @@ function perflab_plugin_action_links_add_settings( $links ) { // Add link as the first plugin action link. $settings_link = sprintf( '%s', - esc_url( add_query_arg( 'page', PERFLAB_MODULES_SCREEN, admin_url( 'options-general.php' ) ) ), + esc_url( add_query_arg( 'page', PERFLAB_SCREEN, admin_url( 'options-general.php' ) ) ), esc_html__( 'Settings', 'performance-lab' ) ); array_unshift( $links, $settings_link ); @@ -586,31 +233,6 @@ function perflab_enqueue_modules_page_scripts() { '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' ), - ) - ); } /** @@ -700,89 +322,6 @@ function perflab_deactivate_plugin() { } 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 2.8.0 - */ -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. * @@ -802,113 +341,4 @@ function perflab_plugin_admin_notices() { '; - $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( $additional_message ); - $message .= ' '; - $message .= esc_html__( '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( $additional_message ); - $message .= ' '; - $message .= esc_html__( '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 .= '
'; - } - ?> -
- - -

- - -

- -
- - - } Promise resolving to module data list. - */ -exports.getModuleData = async ( modulesDir ) => { - const moduleFilePattern = path.join( modulesDir, '*/*/load.php' ); - const moduleFiles = await glob( path.resolve( '.', moduleFilePattern ) ); - - return moduleFiles - .map( ( moduleFile ) => { - let moduleFileContent = ''; - try { - moduleFileContent = fs.readFileSync( moduleFile, 'utf-8' ); - } catch ( error ) { - log( - formats.error( - `Error reading the file "${ moduleFile }": "${ error }"` - ) - ); - } - const moduleHeader = exports.getModuleHeader( moduleFileContent ); - - // Populate slug and focus based on file path. - const moduleDir = path.dirname( moduleFile ); - const moduleData = { - slug: path.basename( moduleDir ), - focus: path.basename( path.dirname( moduleDir ) ), - ...exports.getModuleDataFromHeader( moduleHeader ), - }; - return moduleData; - } ) - .filter( ( moduleData ) => { - const requiredProperties = []; - if ( ! moduleData.name ) { - requiredProperties.push( 'name' ); - } - - if ( ! moduleData.description ) { - requiredProperties.push( 'description' ); - } - - if ( typeof moduleData.experimental === 'undefined' ) { - requiredProperties.push( 'experimental' ); - } - - if ( requiredProperties.length >= 1 ) { - const properties = requiredProperties - .map( ( property ) => `'${ property }'` ) - .join( ', ' ); - log( - formats.warning( - `This module was not included because it misses required properties: ${ properties }.\nDetails: ${ JSON.stringify( - moduleData, - null, - 4 - ) }` - ) - ); - } - - return ( - moduleData.name && - moduleData.description && - typeof moduleData.experimental !== 'undefined' - ); - } ) - .sort( ( firstModule, secondModule ) => { - // Not the same focus group. - if ( firstModule.focus !== secondModule.focus ) { - return ( - FOCUS_AREAS[ firstModule.focus ] - - FOCUS_AREAS[ secondModule.focus ] - ); - } - - if ( firstModule.experimental !== secondModule.experimental ) { - return firstModule.experimental ? 1 : -1; - } - - // Lastly order alphabetically. - return firstModule.slug.localeCompare( secondModule.slug ); - } ); -}; - -/** - * Returns the list of module data for module. - * - * @param {string} moduleHeader Modules file header contetnt. - * @return {WPModuleData} Module data. - */ -exports.getModuleDataFromHeader = ( moduleHeader ) => { - const moduleData = {}; - // Map of module header => object property. - const headers = { - 'Module Name': 'name', - Description: 'description', - Experimental: 'experimental', - }; - - const regex = new RegExp( - `^(?:[ \t]* { - const regex = /\/\\*\\*[\s\S]+?(?=\*\/)/im; - const moduleHeader = moduleFileContent.match( regex )?.[ 0 ]; - return moduleHeader; -}; diff --git a/bin/plugin/commands/enabled-modules.js b/bin/plugin/commands/enabled-modules.js deleted file mode 100644 index bd85c7b38b..0000000000 --- a/bin/plugin/commands/enabled-modules.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * External dependencies - */ -const path = require( 'path' ); -const fs = require( 'fs' ); -const { EOL } = require( 'os' ); - -/** - * Internal dependencies - */ -const { log, formats } = require( '../lib/logger' ); -const { getModuleData } = require( './common' ); - -const TAB = '\t'; -const NEWLINE = EOL; -const FILE_HEADER = `', - description: 'Modules directory', - }, - { - argname: '-d, --output ', - description: 'Output file', - }, -]; - -/** - * Command that generates a PHP file with non-experimental module slugs. - * - * @param {WPEnabledModulesCommandOptions} opt - */ -exports.handler = async ( opt ) => { - await createEnabledModules( { - directory: opt.directory || 'modules', - output: opt.output || 'default-enabled-modules.php', - } ); -}; - -/** - * Gathers the non-experimental modules as the default enabled modules. - * - * @param {WPEnabledModulesSettings} settings Default enabled modules settings. - * - * @return {[]string} List of default enabled module paths relative to modules directory. - */ -async function getDefaultEnabledModules( settings ) { - const modulesData = await getModuleData( settings.directory ); - return modulesData - .filter( ( moduleData ) => ! moduleData.experimental ) - .map( ( moduleData ) => `${ moduleData.focus }/${ moduleData.slug }` ); -} - -/** - * Creates PHP file with the given default enabled modules. - * - * @param {[]string} enabledModules List of default enabled module paths relative to modules directory. - * @param {WPEnabledModulesSettings} settings Default enabled modules settings. - */ -function createEnabledModulesPHPFile( enabledModules, settings ) { - const output = enabledModules.map( ( enabledModule ) => { - // Escape single quotes. - return `${ TAB }'${ enabledModule.replace( /'/g, "\\'" ) }',`; - } ); - - const fileOutput = `${ FILE_HEADER }${ output.join( - NEWLINE - ) }${ FILE_FOOTER }`; - fs.writeFileSync( path.join( '.', settings.output ), fileOutput ); -} - -/** - * Gathers non-experimental modules and generates a PHP file with them. - * - * @param {WPEnabledModulesSettings} settings Default enabled modules settings. - */ -async function createEnabledModules( settings ) { - log( - formats.title( - `\n💃Gathering non-experimental modules for "${ settings.directory }" in "${ settings.output }"\n\n` - ) - ); - - try { - const enabledModules = await getDefaultEnabledModules( settings ); - createEnabledModulesPHPFile( enabledModules, settings ); - } catch ( error ) { - if ( error instanceof Error ) { - log( formats.error( error.stack ) ); - return; - } - } - - log( - formats.success( - `\n💃Non-experimental modules successfully set in "${ settings.output }"\n\n` - ) - ); -} diff --git a/bin/plugin/commands/readme.js b/bin/plugin/commands/readme.js index bc48ce6d20..12f44b0df9 100644 --- a/bin/plugin/commands/readme.js +++ b/bin/plugin/commands/readme.js @@ -9,13 +9,11 @@ const fs = require( 'fs' ); */ const { log, formats } = require( '../lib/logger' ); const config = require( '../config' ); -const { getModuleData } = require( './common' ); const { getChangelog } = require( './changelog' ); /** * @typedef WPReadmeCommandOptions * - * @property {string=} directory Optional directory, default is the root `/modules` directory. * @property {string=} milestone Optional milestone title, to update the changelog in the readme. * @property {string=} token Optional personal GitHub access token, only relevant for changelog updates. */ @@ -25,16 +23,11 @@ const { getChangelog } = require( './changelog' ); * * @property {string} owner GitHub repository owner. * @property {string} repo GitHub repository name. - * @property {string} directory Modules directory. * @property {string=} milestone Optional milestone title, to update the changelog in the readme. * @property {string=} token Optional personal GitHub access token, only relevant for changelog updates. */ exports.options = [ - { - argname: '-d, --directory ', - description: 'Modules directory', - }, { argname: '-m, --milestone ', description: 'Milestone title, to update the changelog', @@ -54,46 +47,11 @@ exports.handler = async ( opt ) => { await updateReadme( { owner: config.githubRepositoryOwner, repo: config.githubRepositoryName, - directory: opt.directory || 'modules', milestone: opt.milestone, token: opt.token, } ); }; -/** - * Returns a promise resolving to the module description list string for the `readme.txt` file. - * - * @param {WPReadmeSettings} settings Readme settings. - * - * @return {Promise} Promise resolving to module description list in markdown, with trailing newline. - */ -async function getModuleDescriptionList( settings ) { - const modulesData = await getModuleData( settings.directory ); - - return modulesData - .map( - ( moduleData ) => - `* **${ moduleData.name }:** ${ moduleData.description }` - ) - .join( '\n' ) - .concat( '\n' ); -} - -/** - * Updates the `readme.txt` file with the given module description list. - * - * @param {string} moduleList Module description list in markdown, with trailing newline. - */ -function updateReadmeModuleDescriptionList( moduleList ) { - const readmeFile = path.join( '.', 'readme.txt' ); - const fileContent = fs.readFileSync( readmeFile, 'utf8' ); - const newContent = fileContent.replace( - /(the following performance modules:\s+)((\*.*\n)+)/, - ( match, prefix ) => `${ prefix }${ moduleList }` - ); - fs.writeFileSync( readmeFile, newContent ); -} - /** * Updates the `readme.txt` file with the given changelog. * @@ -132,28 +90,10 @@ async function updateReadme( settings ) { if ( settings.milestone ) { log( formats.title( - `\n💃Updating readme.txt for "${ settings.directory }" and changelog for milestone "${ settings.milestone }"\n\n` - ) - ); - } else { - log( - formats.title( - `\n💃Updating readme.txt for "${ settings.directory }"\n\n` + `\n💃Updating readme.txt changelog for milestone "${ settings.milestone }"\n\n` ) ); - } - try { - const moduleList = await getModuleDescriptionList( settings ); - updateReadmeModuleDescriptionList( moduleList ); - } catch ( error ) { - if ( error instanceof Error ) { - log( formats.error( error.stack ) ); - return; - } - } - - if ( settings.milestone ) { try { const changelog = await getChangelog( { owner: settings.owner, @@ -168,7 +108,6 @@ async function updateReadme( settings ) { return; } } + log( formats.success( `\n💃readme.txt successfully updated\n\n` ) ); } - - log( formats.success( `\n💃readme.txt successfully updated\n\n` ) ); } diff --git a/bin/plugin/commands/test-plugins.js b/bin/plugin/commands/test-plugins.js index 5200bd57af..bb71e185cc 100644 --- a/bin/plugin/commands/test-plugins.js +++ b/bin/plugin/commands/test-plugins.js @@ -414,55 +414,32 @@ function doRunStandalonePluginTests( settings ) { try { // Read the plugins.json file synchronously. - const { modules, plugins } = require( pluginsFile ); + const { plugins } = require( pluginsFile ); // Create an array of plugins from entries in plugins JSON file. - builtPlugins = Object.keys( modules ) - .filter( ( item ) => { - if ( - ! fs.pathExistsSync( - `${ settings.builtPluginsDir }${ modules[ item ].slug }` - ) - ) { + builtPlugins = Object.values( plugins ) + .filter( ( plugin ) => { + try { + fs.copySync( + `${ settings.pluginsDir }${ plugin }/`, + `${ settings.builtPluginsDir }${ plugin }/`, + { + overwrite: true, + } + ); + log( formats.success( `Copied plugin "${ plugin }".\n` ) ); + return true; + } catch ( e ) { + // Handle the error appropriately log( formats.error( - `Built plugin path "${ settings.builtPluginsDir }${ modules[ item ].slug }" not found, skipping and removing from plugin list` + `Error copying plugin "${ plugin }": ${ e.message }` ) ); return false; } - return true; } ) - .map( ( item ) => modules[ item ].slug ); - - // Create an array of plugins from entries in plugins JSON file. - builtPlugins = builtPlugins.concat( - Object.values( plugins ) - .filter( ( plugin ) => { - try { - fs.copySync( - `${ settings.pluginsDir }${ plugin }/`, - `${ settings.builtPluginsDir }${ plugin }/`, - { - overwrite: true, - } - ); - log( - formats.success( `Copied plugin "${ plugin }".\n` ) - ); - return true; - } catch ( e ) { - // Handle the error appropriately - log( - formats.error( - `Error copying plugin "${ plugin }": ${ e.message }` - ) - ); - return false; - } - } ) - .map( ( plugin ) => plugin ) - ); + .map( ( plugin ) => plugin ); } catch ( error ) { throw Error( `Error reading file at "${ pluginsFile }": ${ error }` ); } diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js deleted file mode 100644 index cca33749c5..0000000000 --- a/bin/plugin/commands/translations.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * External dependencies - */ -const path = require( 'path' ); -const fs = require( 'fs' ); -const { EOL } = require( 'os' ); - -/** - * Internal dependencies - */ -const { log, formats } = require( '../lib/logger' ); -const config = require( '../config' ); -const { getModuleData } = require( './common' ); - -const TAB = '\t'; -const NEWLINE = EOL; -const FILE_HEADER = `', - description: 'Modules directory', - }, - { - argname: '-d, --output ', - description: 'Output file', - }, -]; - -/** - * Command that generates a PHP file from module header translation strings. - * - * @param {WPTranslationsCommandOptions} opt - */ -exports.handler = async ( opt ) => { - await createTranslations( { - textDomain: config.textDomain, - directory: opt.directory || 'modules', - output: opt.output || 'module-i18n.php', - } ); -}; - -/** - * Parses module header translation strings. - * - * @param {WPTranslationsSettings} settings Translations settings. - * - * @return {[]WPTranslationEntry} List of translation entries. - */ -async function getTranslations( settings ) { - const modulesData = await getModuleData( settings.directory ); - const moduleTranslations = modulesData.map( ( moduleData ) => { - return [ - { - text: moduleData.name, - context: 'module name', - }, - { - text: moduleData.description, - context: 'module description', - }, - ]; - } ); - - return moduleTranslations.flat(); -} - -/** - * Parses module header translation strings. - * - * @param {[]WPTranslationEntry} translations List of translation entries. - * @param {WPTranslationsSettings} settings Translations settings. - */ -function createTranslationsPHPFile( translations, settings ) { - const output = translations.map( ( translation ) => { - // Escape single quotes. - return `${ TAB }_x( '${ translation.text.replace( /'/g, "\\'" ) }', '${ - translation.context - }', '${ settings.textDomain }' ),`; - } ); - - const fileOutput = `${ FILE_HEADER }${ output.join( - NEWLINE - ) }${ FILE_FOOTER }`; - fs.writeFileSync( path.join( '.', settings.output ), fileOutput ); -} - -/** - * Parses module header translation strings and generates a PHP file with them. - * - * @param {WPTranslationsSettings} settings Translations settings. - */ -async function createTranslations( settings ) { - log( - formats.title( - `\n💃Preparing module translations for "${ settings.directory }" in "${ settings.output }"\n\n` - ) - ); - - try { - const translations = await getTranslations( settings ); - createTranslationsPHPFile( translations, settings ); - } catch ( error ) { - if ( error instanceof Error ) { - log( formats.error( error.stack ) ); - return; - } - } - - log( - formats.success( - `\n💃Module translations successfully set in "${ settings.output }"\n\n` - ) - ); -} diff --git a/composer.json b/composer.json index bd0d6338f8..eb72b6a8fe 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "wordpress/performance", "type": "wordpress-plugin", "license": "GPL-2.0-or-later", - "description": "Performance plugin from the WordPress Performance Team, which is a collection of standalone performance modules.", + "description": "Performance plugin from the WordPress Performance Team, which is a collection of standalone performance features.", "homepage": "https://wordpress.org/plugins/performance-lab/", "keywords": [ "performance", diff --git a/default-enabled-modules.php b/default-enabled-modules.php deleted file mode 100644 index 74c9838232..0000000000 --- a/default-enabled-modules.php +++ /dev/null @@ -1,8 +0,0 @@ - 'object', - 'sanitize_callback' => 'perflab_sanitize_modules_setting', - 'default' => perflab_get_modules_setting_default(), - ) - ); -} -add_action( 'init', 'perflab_register_modules_setting' ); - -/** - * Gets the default value for the performance modules setting. - * - * @since 1.0.0 - * - * @return array Associative array of module settings keyed by module slug. - */ -function perflab_get_modules_setting_default() { - // Since the default relies on some minimal logic that includes requiring an additional file, - // the result is "cached" in a static variable. - static $default_option = null; - - if ( null === $default_option ) { - // To set the default value for which modules are enabled, rely on this generated file. - $default_enabled_modules = require PERFLAB_PLUGIN_DIR_PATH . 'default-enabled-modules.php'; - $default_option = array_reduce( - $default_enabled_modules, - static function ( $module_settings, $module_dir ) { - $module_settings[ $module_dir ] = array( 'enabled' => true ); - return $module_settings; - }, - array() - ); - } - - return $default_option; -} - -/** - * Sanitizes the performance modules setting. - * - * @since 1.0.0 - * - * @param mixed $value Modules setting value. - * @return array Sanitized modules setting value. - */ -function perflab_sanitize_modules_setting( $value ) { - if ( ! is_array( $value ) ) { - return array(); - } - - // Ensure that every element is an array with an 'enabled' key. - return array_filter( - array_map( - static function ( $module_settings ) { - if ( ! is_array( $module_settings ) ) { - return array(); - } - return array_merge( - array( 'enabled' => false ), - $module_settings - ); - }, - $value - ) - ); -} - -/** - * Gets the performance module settings. - * - * @since 1.0.0 - * - * @return array Associative array of module settings keyed by module slug. - */ -function perflab_get_module_settings() { - // Even though a default value is registered for this setting, the default must be explicitly - // passed here, to support scenarios where this function is called before the 'init' action, - // for example when loading the active modules. - return (array) get_option( PERFLAB_MODULES_SETTING, perflab_get_modules_setting_default() ); -} - -/** - * Gets the active performance modules. - * - * @since 1.0.0 - * - * @return array List of active module slugs. - */ -function perflab_get_active_modules() { - $modules = array_keys( - array_filter( - perflab_get_module_settings(), - static function ( $module_settings ) { - return isset( $module_settings['enabled'] ) && $module_settings['enabled']; - } - ) - ); - - /** - * Filters active modules to allow programmatically control which modules are active. - * - * @since 1.0.0 - * - * @param array $modules An array of the currently active modules. - */ - $modules = apply_filters( 'perflab_active_modules', $modules ); - - return $modules; -} - -/** - * Gets the active and valid performance modules. - * - * @since 1.3.0 - * @since 2.2.0 Adds an additional check for standalone plugins. - * - * @param string $module Slug of the module. - * @return bool True if the module is active and valid, otherwise false. - */ -function perflab_is_valid_module( $module ) { - - if ( empty( $module ) ) { - return false; - } - - // Do not load the module if it can be loaded by a separate plugin. - if ( perflab_is_standalone_plugin_loaded( $module ) ) { - return false; - } - - // Do not load module if no longer exists. - $module_file = PERFLAB_PLUGIN_DIR_PATH . 'modules/' . $module . '/load.php'; - if ( ! file_exists( $module_file ) ) { - return false; - } - - // Do not load module if it cannot be loaded, e.g. if it was already merged and is available in WordPress core. - $can_load_module = perflab_can_load_module( $module ); - return $can_load_module && ! is_wp_error( $can_load_module ); -} - /** * Gets the content attribute for the generator tag for the Performance Lab plugin. * @@ -197,21 +46,19 @@ function perflab_is_valid_module( $module ) { * * @since 1.1.0 * @since 2.9.0 The generator tag now includes the active standalone plugin slugs. + * @since n.e.x.t The generator tag no longer includes module slugs. */ function perflab_get_generator_content() { - $active_and_valid_modules = array_filter( perflab_get_active_modules(), 'perflab_is_valid_module' ); - $active_plugins = array(); - foreach ( perflab_get_standalone_plugin_version_constants( 'plugins' ) as $plugin_slug => $constant_name ) { + foreach ( perflab_get_standalone_plugin_version_constants() as $plugin_slug => $constant_name ) { if ( defined( $constant_name ) && ! str_starts_with( constant( $constant_name ), 'Performance Lab ' ) ) { $active_plugins[] = $plugin_slug; } } return sprintf( - 'Performance Lab %1$s; modules: %2$s; plugins: %3$s', + 'Performance Lab %1$s; plugins: %2$s', PERFLAB_VERSION, - implode( ', ', $active_and_valid_modules ), implode( ', ', $active_plugins ) ); } @@ -231,90 +78,14 @@ function perflab_render_generator() { add_action( 'wp_head', 'perflab_render_generator' ); /** - * Checks whether the given module can be loaded in the current environment. - * - * @since 1.3.0 - * @since 2.8.0 The function may now alternatively return a WP_Error. - * - * @param string $module Slug of the module. - * @return bool|WP_Error True if the module can be loaded, or false or a WP_Error with more concrete information otherwise. - */ -function perflab_can_load_module( $module ) { - $module_load_file = PERFLAB_PLUGIN_DIR_PATH . 'modules/' . $module . '/can-load.php'; - - // If the `can-load.php` file does not exist, assume the module can be loaded. - if ( ! file_exists( $module_load_file ) ) { - return true; - } - - // Require the file to get the closure for whether the module can load. - $module = require $module_load_file; - - // If the `can-load.php` file is invalid and does not return a closure, assume the module can be loaded. - if ( ! is_callable( $module ) ) { - return true; - } - - // Call the closure to determine whether the module can be loaded. - $result = $module(); - - if ( is_wp_error( $result ) ) { - return $result; - } - - return (bool) $result; -} - -/** - * Checks whether the given module has already been loaded by a separate plugin. - * - * @since 2.2.0 - * - * @param string $module Slug of the module. - * @return bool Whether the module has already been loaded by a separate plugin. - */ -function perflab_is_standalone_plugin_loaded( $module ) { - $standalone_plugins_constants = perflab_get_standalone_plugin_version_constants( 'modules' ); - if ( - isset( $standalone_plugins_constants[ $module ] ) && - defined( $standalone_plugins_constants[ $module ] ) && - ! str_starts_with( constant( $standalone_plugins_constants[ $module ] ), 'Performance Lab ' ) - ) { - return true; - } - return false; -} - -/** - * Gets the standalone plugin constants used for each module with a standalone plugin. - * - * @since 2.2.0 - * @deprecated 2.9.0 - * - * @return array Map of module path to version constant used. - */ -function perflab_get_standalone_plugins_constants() { - _deprecated_function( __FUNCTION__, 'Performance Lab 2.9.0', "perflab_get_standalone_plugin_version_constants( 'modules' )" ); - return perflab_get_standalone_plugin_version_constants( 'modules' ); -} - -/** - * Gets the standalone plugin constants used for each available standalone plugin, or module with a standalone plugin. + * Gets the standalone plugin constants used for each available standalone plugin. * * @since 2.9.0 + * @since n.e.x.t The $source parameter was removed. * - * @param string $source Optional. Either 'plugins' or 'modules'. Default 'plugins'. * @return array Map of plugin slug / module path and the version constant used. */ -function perflab_get_standalone_plugin_version_constants( $source = 'plugins' ) { - if ( 'modules' === $source ) { - /* - * This list includes all modules which are also available as standalone plugins, - * as `$module_dir => $version_constant` pairs. - */ - return array(); - } - +function perflab_get_standalone_plugin_version_constants() { /* * This list includes all standalone plugins that are part of the Performance Lab project, * as `$plugin_slug => $version_constant` pairs. @@ -329,22 +100,6 @@ function perflab_get_standalone_plugin_version_constants( $source = 'plugins' ) ); } -/** - * Loads the active and valid performance modules. - * - * @since 1.0.0 - * @since 1.3.0 Renamed to perflab_load_active_and_valid_modules(). - */ -function perflab_load_active_and_valid_modules() { - $active_and_valid_modules = array_filter( perflab_get_active_modules(), 'perflab_is_valid_module' ); - - foreach ( $active_and_valid_modules as $module ) { - - require_once PERFLAB_PLUGIN_DIR_PATH . 'modules/' . $module . '/load.php'; - } -} -add_action( 'plugins_loaded', 'perflab_load_active_and_valid_modules' ); - /** * Places the Performance Lab's object cache drop-in in the drop-ins folder. * @@ -507,171 +262,44 @@ function perflab_maybe_remove_object_cache_dropin() { } register_deactivation_hook( __FILE__, 'perflab_maybe_remove_object_cache_dropin' ); -// Only load admin integration when in admin. -if ( is_admin() ) { - require_once PERFLAB_PLUGIN_DIR_PATH . 'admin/load.php'; - require_once PERFLAB_PLUGIN_DIR_PATH . 'admin/server-timing.php'; - require_once PERFLAB_PLUGIN_DIR_PATH . 'admin/plugins.php'; -} - /** - * Trigger actions when a module gets activated or deactivated. + * Redirects module pages to the performance feature page. * - * @since 1.8.0 + * @since n.e.x.t * - * @param mixed $old_value Old value of the option. - * @param mixed $value New value of the option. + * @global $plugin_page */ -function perflab_run_module_activation_deactivation( $old_value, $value ) { - $old_value = (array) $old_value; - $value = (array) $value; - - // Get the list of modules that were activated, and load the activate.php files if they exist. - if ( ! empty( $value ) ) { - $reset_migration_pointer_dismissals = false; - foreach ( $value as $module => $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 ); - } - } - } - } +function perflab_no_access_redirect_module_to_performance_feature_page() { + global $plugin_page; - // Get the list of modules that were deactivated, and load the deactivate.php files if they exist. - if ( ! empty( $old_value ) ) { - foreach ( $old_value as $module => $module_settings ) { - if ( ! empty( $module_settings['enabled'] ) && ( empty( $value[ $module ] ) || empty( $value[ $module ]['enabled'] ) ) ) { - perflab_deactivate_module( PERFLAB_PLUGIN_DIR_PATH . 'modules/' . $module ); - } - } - } - - return $value; -} - -/** - * Reverts the module migration pointer dismissal for the given user. - * - * @since 2.8.0 - * - * @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 ) { + if ( 'perflab-modules' !== $plugin_page ) { return; } - unset( $dismissed[ $pointer_index ] ); - $dismissed = implode( ',', $dismissed ); - - update_user_meta( $user->ID, 'dismissed_wp_pointers', $dismissed ); -} - -/** - * Activate a module. - * - * Runs the activate.php file if it exists. - * - * @since 1.8.0 - * - * @param string $module_dir_path The module's directory path. - */ -function perflab_activate_module( $module_dir_path ) { - $module_activation_file = $module_dir_path . '/activate.php'; - if ( ! file_exists( $module_activation_file ) ) { - return; - } - $module = require $module_activation_file; - if ( ! is_callable( $module ) ) { - return; + if ( + current_user_can( 'manage_options' ) && + wp_safe_redirect( add_query_arg( 'page', PERFLAB_SCREEN ) ) + ) { + exit; } - $module(); } +add_action( 'admin_page_access_denied', 'perflab_no_access_redirect_module_to_performance_feature_page' ); /** - * Deactivate a module. - * - * Runs the deactivate.php file if it exists. + * Cleanup function to delete 'perflab_modules_settings' option if present. * - * @since 1.8.0 - * - * @param string $module_dir_path The module's directory path. + * @since n.e.x.t */ -function perflab_deactivate_module( $module_dir_path ) { - $module_deactivation_file = $module_dir_path . '/deactivate.php'; - if ( ! file_exists( $module_deactivation_file ) ) { - return; - } - $module = require $module_deactivation_file; - if ( ! is_callable( $module ) ) { - return; +function perflab_cleanup_option() { + if ( current_user_can( 'manage_options' ) ) { + delete_option( 'perflab_modules_settings' ); } - $module(); } +add_action( 'admin_init', 'perflab_cleanup_option' ); -// Run the module activation & deactivation actions when the option is updated. -add_action( 'update_option_' . PERFLAB_MODULES_SETTING, 'perflab_run_module_activation_deactivation', 10, 2 ); - -// Run the module activation & deactivation actions when the option is added. -add_action( - 'add_option_' . PERFLAB_MODULES_SETTING, - /** - * Fires after the option has been added. - * - * @param string $option Name of the option to add. - * @param mixed $value Value of the option. - */ - static function ( $option, $value ) { - perflab_run_module_activation_deactivation( perflab_get_modules_setting_default(), $value ); - }, - 10, - 2 -); +// Only load admin integration when in admin. +if ( is_admin() ) { + require_once PERFLAB_PLUGIN_DIR_PATH . 'admin/load.php'; + require_once PERFLAB_PLUGIN_DIR_PATH . 'admin/server-timing.php'; + require_once PERFLAB_PLUGIN_DIR_PATH . 'admin/plugins.php'; +} diff --git a/module-i18n.php b/module-i18n.php deleted file mode 100644 index ad1dfa0872..0000000000 --- a/module-i18n.php +++ /dev/null @@ -1,17 +0,0 @@ - - - - default-enabled-modules.php - module-i18n.php - - includes/server-timing/object-cache.copy.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 55eff44c9d..13084b48f0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,9 +4,7 @@ parameters: level: 0 paths: - admin/ - - default-enabled-modules.php - load.php - - module-i18n.php - includes/ - plugins/ - tests/ diff --git a/plugins.json b/plugins.json index 8d660e4eee..3fe261ae2d 100644 --- a/plugins.json +++ b/plugins.json @@ -1,5 +1,4 @@ { - "modules": {}, "plugins": [ "auto-sizes", "dominant-color-images", diff --git a/readme.txt b/readme.txt index ae12ed822c..5298cf3279 100644 --- a/readme.txt +++ b/readme.txt @@ -9,19 +9,11 @@ License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, images, javascript, site health, measurement, object caching -Performance plugin from the WordPress Performance Team, which is a collection of standalone performance modules. +Performance plugin from the WordPress Performance Team, which is a collection of standalone performance features. == Description == -The Performance Lab plugin is a collection of modules focused on enhancing performance of your site, most of which should eventually be merged into WordPress core. The plugin allows to individually enable and test the modules to get their benefits before they become available in WordPress core, and to provide feedback to further improve the solutions. - -Currently the plugin includes the following performance modules: - -* **Dominant Color Images:** Adds support to store the dominant color of newly uploaded images and create a placeholder background of that color. -* **WebP Support Health Check:** Adds a WebP support check in Site Health status. -* **WebP Uploads:** Creates WebP versions for new JPEG image uploads if supported by the server. -* **Enqueued Assets Health Check:** Adds a CSS and JS resource check in Site Health status. -* **Autoloaded Options Health Check:** Adds a check for autoloaded options in Site Health status. +The Performance Lab plugin is a collection of features focused on enhancing performance of your site, most of which should eventually be merged into WordPress core. The plugin allows to individually enable and test the features to get their benefits before they become available in WordPress core, and to provide feedback to further improve the solutions. == Installation == @@ -40,17 +32,17 @@ Currently the plugin includes the following performance modules: = After activation = 1. Visit the new **Settings > Performance** menu. -2. Enable the individual modules you would like to use. +2. Enable the individual features you would like to use. == Frequently Asked Questions == = What is the purpose of this plugin? = -The primary purpose of the Performance Lab plugin is to allow testing of various performance modules for which the goal is to eventually land in WordPress core. It is essentially a collection of "feature plugins", which makes it different from other performance plugins that offer performance features which are not targeted at WordPress core and potentially rely on functionality that would not be feasible to use in WordPress core. The list of available modules will regularly change: Existing modules may be removed after they have been released in WordPress core, while new modules may be added in any release. +The primary purpose of the Performance Lab plugin is to allow testing of various performance features for which the goal is to eventually land in WordPress core. It is essentially a collection of "feature plugins", which makes it different from other performance plugins that offer performance features which are not targeted at WordPress core and potentially rely on functionality that would not be feasible to use in WordPress core. The list of available features will regularly change: Existing features may be removed after they have been released in WordPress core, while new features may be added in any release. = Can I use this plugin on my production site? = -Per the primary purpose of the plugin (see above), it can mostly be considered a beta testing plugin for the various performance modules it includes. However, unless a module is explicitly marked as "experimental", it has been tested and established to a degree where it should be okay to use in production. Still, as with every plugin, you are doing so at your own risk. +Per the primary purpose of the plugin (see above), it can mostly be considered a beta testing plugin for the various performance features it includes. However, it's essential to understand that utilizing it comes with inherent risks. Users are encouraged to proceed with caution and understand that they are doing so at their own risk. = Where can I submit my plugin feedback? = diff --git a/tests/admin/load-tests.php b/tests/admin/load-tests.php index 83e4555d8e..b8fc585923 100644 --- a/tests/admin/load-tests.php +++ b/tests/admin/load-tests.php @@ -10,56 +10,7 @@ */ class Admin_Load_Tests extends WP_UnitTestCase { - private static $demo_modules = array( - 'js-and-css/demo-module-1' => array( - 'name' => 'Demo Module 1', - 'description' => 'This is the description for demo module 1.', - 'experimental' => false, - 'focus' => 'js-and-css', - 'slug' => 'demo-module-1', - ), - 'something/demo-module-2' => array( - 'name' => 'Demo Module 2', - 'description' => 'This is the description for demo module 2.', - 'experimental' => true, - 'focus' => 'something', - 'slug' => 'demo-module-2', - ), - 'images/demo-module-3' => array( - 'name' => 'Demo Module 3', - 'description' => 'This is the description for demo module 3.', - 'experimental' => false, - 'focus' => 'images', - 'slug' => 'demo-module-3', - ), - 'check-error/demo-module-4' => array( - 'name' => 'Demo Module 4', - 'description' => 'This is the description for demo module 4.', - 'experimental' => false, - 'focus' => 'check-error', - 'slug' => 'demo-module-4', - ), - ); - - private static $demo_focus_areas = array( - 'images' => array( - 'name' => 'Images', - ), - 'js-and-css' => array( - 'name' => 'js-and-css', - ), - 'database' => array( - 'name' => 'Database', - ), - 'measurement' => array( - 'name' => 'Measurement', - ), - 'object-cache' => array( - 'name' => 'Object Cache', - ), - ); - - public function test_perflab_add_modules_page() { + public function test_perflab_add_features_page() { global $_wp_submenu_nopriv; // Reset relevant globals and filters. @@ -67,9 +18,9 @@ public function test_perflab_add_modules_page() { remove_all_filters( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ) ); // The default user does not have the 'manage_options' capability. - $hook_suffix = perflab_add_modules_page(); + $hook_suffix = perflab_add_features_page(); $this->assertFalse( $hook_suffix ); - $this->assertTrue( isset( $_wp_submenu_nopriv['options-general.php'][ PERFLAB_MODULES_SCREEN ] ) ); + $this->assertTrue( isset( $_wp_submenu_nopriv['options-general.php'][ PERFLAB_SCREEN ] ) ); // Ensure plugin action link is not added. $this->assertFalse( (bool) has_action( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ), 'perflab_plugin_action_links_add_settings' ) ); @@ -80,158 +31,29 @@ public function test_perflab_add_modules_page() { // Rely on current user to be an administrator (with 'manage_options' capability). $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $user_id ); - $hook_suffix = perflab_add_modules_page(); - $this->assertSame( get_plugin_page_hookname( PERFLAB_MODULES_SCREEN, 'options-general.php' ), $hook_suffix ); - $this->assertFalse( isset( $_wp_submenu_nopriv['options-general.php'][ PERFLAB_MODULES_SCREEN ] ) ); + $hook_suffix = perflab_add_features_page(); + $this->assertSame( get_plugin_page_hookname( PERFLAB_SCREEN, 'options-general.php' ), $hook_suffix ); + $this->assertFalse( isset( $_wp_submenu_nopriv['options-general.php'][ PERFLAB_SCREEN ] ) ); // Ensure plugin action link is added. $this->assertTrue( (bool) has_action( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ), 'perflab_plugin_action_links_add_settings' ) ); // Reset relevant globals and filters. $_wp_submenu_nopriv = array(); remove_all_filters( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ) ); - - // Does not register the page if the perflab_active_modules filter is used. - add_filter( 'perflab_active_modules', '__return_array' ); - $hook_suffix = perflab_add_modules_page(); - $this->assertFalse( $hook_suffix ); - $this->assertFalse( isset( $_wp_submenu_nopriv['options-general.php'][ PERFLAB_MODULES_SCREEN ] ) ); - // Ensure plugin action link is not added. - $this->assertFalse( (bool) has_action( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ), 'perflab_plugin_action_links_add_settings' ) ); - } - - public function test_perflab_load_modules_page() { - global $wp_settings_sections, $wp_settings_fields; - - // Reset relevant globals. - $wp_settings_sections = array(); - $wp_settings_fields = array(); - - // Pass no modules, resulting in nothing being registered. - perflab_load_modules_page( array(), self::$demo_focus_areas ); - $this->assertFalse( ! empty( $wp_settings_sections[ PERFLAB_MODULES_SCREEN ] ) ); - $this->assertFalse( ! empty( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ] ) ); - - // Reset relevant globals. - $wp_settings_sections = array(); - $wp_settings_fields = array(); - - // Pass demo modules, resulting in relevant sections and fields for those modules being registered. - perflab_load_modules_page( self::$demo_modules, self::$demo_focus_areas ); - $this->assertTrue( ! empty( $wp_settings_sections[ PERFLAB_MODULES_SCREEN ] ) ); - $this->assertSame( - array( - 'images' => array( - 'id' => 'images', - 'title' => 'Images', - 'callback' => null, - 'before_section' => '', - 'after_section' => '', - 'section_class' => '', - ), - 'js-and-css' => array( - 'id' => 'js-and-css', - 'title' => 'js-and-css', - 'callback' => null, - 'before_section' => '', - 'after_section' => '', - 'section_class' => '', - ), - 'other' => array( - 'id' => 'other', - 'title' => 'Other', - 'callback' => null, - 'before_section' => '', - 'after_section' => '', - 'section_class' => '', - ), - ), - $wp_settings_sections[ PERFLAB_MODULES_SCREEN ] - ); - $this->assertEqualSets( - array( - 'images', - 'js-and-css', - 'other', - ), - array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ] ) - ); - $this->assertEqualSets( - array( 'images/demo-module-3' ), - array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ]['images'] ) - ); - $this->assertEqualSets( - array( 'js-and-css/demo-module-1' ), - array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ]['js-and-css'] ) - ); - $this->assertEqualSets( - array( 'something/demo-module-2', 'check-error/demo-module-4' ), - array_keys( $wp_settings_fields[ PERFLAB_MODULES_SCREEN ]['other'] ) - ); } - public function test_perflab_render_modules_page() { + public function test_perflab_render_settings_page() { ob_start(); - perflab_render_modules_page(); + perflab_render_settings_page(); $output = ob_get_clean(); $this->assertStringContainsString( '
', $output ); - $this->assertStringContainsString( "", $output ); - } - - public function test_perflab_render_modules_page_field() { - $module_slug = 'js-and-css/demo-module-1'; - $module_data = self::$demo_modules[ $module_slug ]; - $module_settings = array( 'enabled' => false ); - - // Assert correct 'id' and 'name' attributes, label, and unchecked checkbox. - ob_start(); - perflab_render_modules_page_field( $module_slug, $module_data, $module_settings ); - $output = ob_get_clean(); - $this->assertStringContainsString( ' id="module_' . $module_slug . '_enabled"', $output ); - $this->assertStringContainsString( ' name="' . PERFLAB_MODULES_SETTING . '[' . $module_slug . '][enabled]"', $output ); - $this->assertStringContainsString( 'Enable ' . $module_data['name'], $output ); - $this->assertStringNotContainsString( ' checked', $output ); - - // Assert correct 'id' and 'name' attributes, experimental label, and checked checkbox. - $module_data['experimental'] = true; - $module_settings['enabled'] = true; - ob_start(); - perflab_render_modules_page_field( $module_slug, $module_data, $module_settings ); - $output = ob_get_clean(); - $this->assertStringContainsString( ' id="module_' . $module_slug . '_enabled"', $output ); - $this->assertStringContainsString( ' name="' . PERFLAB_MODULES_SETTING . '[' . $module_slug . '][enabled]"', $output ); - $this->assertStringContainsString( 'Enable ' . $module_data['name'] . ' (experimental)', $output ); - $this->assertStringContainsString( " checked='checked'", $output ); - } - - public function test_perflab_get_focus_areas() { - $expected_focus_areas = array( - 'images', - 'js-and-css', - 'database', - 'measurement', - 'object-cache', - ); - $this->assertSame( $expected_focus_areas, array_keys( perflab_get_focus_areas() ) ); - } - - public function test_perflab_get_modules() { - // Use test data directory with demo modules that match the modules declared on top of this file. - $modules = perflab_get_modules( TESTS_PLUGIN_DIR . '/tests/testdata/demo-modules' ); - $this->assertSame( self::$demo_modules, $modules ); - } - - public function test_perflab_get_module_data() { - // Use test data directory with demo modules that match the modules declared on top of this file. - foreach ( self::$demo_modules as $module_slug => $expected_module_data ) { - $module_data = perflab_get_module_data( TESTS_PLUGIN_DIR . '/tests/testdata/demo-modules/' . $module_slug . '/load.php' ); - $this->assertSame( $expected_module_data, $module_data ); - } + $this->assertStringNotContainsString( "", $output ); } public function test_perflab_plugin_action_links_add_settings() { $original_links = array( 'wordpress.org' ); $expected_links = array( - 'Settings', + 'Settings', $original_links[0], ); diff --git a/tests/load-tests.php b/tests/load-tests.php index f387e93f8b..e31e16c1a4 100644 --- a/tests/load-tests.php +++ b/tests/load-tests.php @@ -7,140 +7,14 @@ class Load_Tests extends WP_UnitTestCase { - public function test_perflab_register_modules_setting() { - global $new_allowed_options, $wp_registered_settings; - - // Reset relevant globals. - $wp_registered_settings = array(); - // `$new_allowed_options` was only introduced in WordPress 5.5. - if ( isset( $new_allowed_options ) ) { - $new_allowed_options = array(); - } - - perflab_register_modules_setting(); - - // Assert that the setting is correctly registered. - $settings = get_registered_settings(); - $this->assertTrue( isset( $settings[ PERFLAB_MODULES_SETTING ] ) ); - // `$new_allowed_options` was only introduced in WordPress 5.5. - if ( isset( $new_allowed_options ) ) { - $this->assertTrue( isset( $new_allowed_options[ PERFLAB_MODULES_SCREEN ] ) ); - } - - // Assert that registered default works correctly. - $this->assertSame( perflab_get_modules_setting_default(), get_option( PERFLAB_MODULES_SETTING ) ); - - // Assert that most basic sanitization works correctly (an array is required). - update_option( PERFLAB_MODULES_SETTING, 'invalid' ); - $this->assertSame( array(), get_option( PERFLAB_MODULES_SETTING ) ); - } - - public function test_perflab_sanitize_modules_setting() { - // Assert that any non-array value gets sanitized to an empty array. - $sanitized = perflab_sanitize_modules_setting( 'invalid' ); - $this->assertSame( array(), $sanitized ); - - // Assert that any non-array value within the array gets stripped. - $sanitized = perflab_sanitize_modules_setting( - array( - 'valid-module' => array( 'enabled' => true ), - 'invalid-module' => 'invalid', - ) - ); - $this->assertSame( array( 'valid-module' => array( 'enabled' => true ) ), $sanitized ); - - // Assert that every array value within the array has an 'enabled' key. - $sanitized = perflab_sanitize_modules_setting( - array( 'my-module' => array() ) - ); - $this->assertSame( array( 'my-module' => array( 'enabled' => false ) ), $sanitized ); - } - - public function test_perflab_get_modules_setting_default() { - $default_enabled_modules = require PERFLAB_PLUGIN_DIR_PATH . 'default-enabled-modules.php'; - $expected = array(); - foreach ( $default_enabled_modules as $default_enabled_module ) { - $expected[ $default_enabled_module ] = array( 'enabled' => true ); - } - - $this->assertSame( $expected, perflab_get_modules_setting_default() ); - } - - public function test_perflab_get_module_settings() { - // Assert that by default the settings are using the same value as the registered default. - $settings = perflab_get_module_settings(); - $this->assertEqualSetsWithIndex( perflab_get_modules_setting_default(), $settings ); - - // More specifically though, assert that the default is also passed through to the - // get_option() call, to support scenarios where the function is called before 'init'. - // Unhook the registered default logic to verify the default comes from the passed value. - remove_all_filters( 'default_option_' . PERFLAB_MODULES_SETTING ); - $has_passed_default = false; - add_filter( - 'default_option_' . PERFLAB_MODULES_SETTING, - static function ( $current_default, $option, $passed_default ) use ( &$has_passed_default ) { - // This callback just records whether there is a default value being passed. - $has_passed_default = $passed_default; - return $current_default; - }, - 10, - 3 - ); - $settings = perflab_get_module_settings(); - $this->assertTrue( $has_passed_default ); - $this->assertEqualSetsWithIndex( perflab_get_modules_setting_default(), $settings ); - - // Assert that option updates are reflected in the settings correctly. - $new_value = array( 'my-module' => array( 'enabled' => true ) ); - update_option( PERFLAB_MODULES_SETTING, $new_value ); - $settings = perflab_get_module_settings(); - $this->assertEqualSetsWithIndex( $new_value, $settings ); - } - - public function test_perflab_get_active_modules() { - // Assert that by default there are no active modules. - $active_modules = perflab_get_active_modules(); - $expected_active_modules = array_keys( - array_filter( - perflab_get_modules_setting_default(), - static function ( $module_settings ) { - return $module_settings['enabled']; - } - ) - ); - $this->assertEqualSetsWithIndex( $expected_active_modules, $active_modules ); - - // Assert that option updates affect the active modules correctly. - $new_value = array( - 'inactive-module' => array( 'enabled' => false ), - 'active-module' => array( 'enabled' => true ), - ); - update_option( PERFLAB_MODULES_SETTING, $new_value ); - $active_modules = perflab_get_active_modules(); - $this->assertEqualSetsWithIndex( array( 'active-module' ), $active_modules ); - } - public function test_perflab_get_generator_content() { - // Assert that it returns the current version and active modules. - // For this test, set the active modules to all defaults but the last one. - $active_modules = require PERFLAB_PLUGIN_DIR_PATH . 'default-enabled-modules.php'; - array_pop( $active_modules ); - add_filter( - 'perflab_active_modules', - static function () use ( $active_modules ) { - return $active_modules; - } - ); - $active_modules = array_filter( perflab_get_active_modules(), 'perflab_is_valid_module' ); - $expected = 'Performance Lab ' . PERFLAB_VERSION . '; modules: ' . implode( ', ', $active_modules ) . '; plugins: '; - $content = perflab_get_generator_content(); + $expected = 'Performance Lab ' . PERFLAB_VERSION . '; plugins: '; + $content = perflab_get_generator_content(); $this->assertSame( $expected, $content ); } public function test_perflab_render_generator() { - // Assert generator tag is rendered. Content does not matter, so just use no modules active. - add_filter( 'perflab_active_modules', '__return_empty_array' ); - $expected = '' . "\n"; + $expected = '' . "\n"; $output = get_echo( 'perflab_render_generator' ); $this->assertSame( $expected, $output ); @@ -151,16 +25,6 @@ public function test_perflab_render_generator() { $this->assertStringContainsString( $expected, $output ); } - public function test_perflab_activate_module() { - perflab_activate_module( __DIR__ . '/testdata/demo-modules/something/demo-module-2' ); - $this->assertSame( 'activated', get_option( 'test_demo_module_activation_status' ) ); - } - - public function test_perflab_deactivate_module() { - perflab_deactivate_module( __DIR__ . '/testdata/demo-modules/something/demo-module-2' ); - $this->assertSame( 'deactivated', get_option( 'test_demo_module_activation_status' ) ); - } - public function test_perflab_maybe_set_object_cache_dropin_no_conflict() { global $wp_filesystem; diff --git a/tests/testdata/demo-modules/check-error/demo-module-4/can-load.php b/tests/testdata/demo-modules/check-error/demo-module-4/can-load.php deleted file mode 100644 index 6a59ab2995..0000000000 --- a/tests/testdata/demo-modules/check-error/demo-module-4/can-load.php +++ /dev/null @@ -1,10 +0,0 @@ -