diff --git a/projects/plugins/boost/.phpcs.dir.xml b/projects/plugins/boost/.phpcs.dir.xml
index de9dcf15a11a6..0d50ca55e7c99 100644
--- a/projects/plugins/boost/.phpcs.dir.xml
+++ b/projects/plugins/boost/.phpcs.dir.xml
@@ -21,4 +21,38 @@
+
+ 0
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/plugins/boost/app/admin/class-admin.php b/projects/plugins/boost/app/admin/class-admin.php
index 014fc768c3b77..4cfe6eb0e9b69 100644
--- a/projects/plugins/boost/app/admin/class-admin.php
+++ b/projects/plugins/boost/app/admin/class-admin.php
@@ -10,14 +10,14 @@
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Status;
+use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Critical_CSS;
+use Automattic\Jetpack_Boost\Features\Optimizations\Optimizations;
+use Automattic\Jetpack_Boost\Features\Speed_Score\Speed_Score;
use Automattic\Jetpack_Boost\Jetpack_Boost;
use Automattic\Jetpack_Boost\Lib\Analytics;
use Automattic\Jetpack_Boost\Lib\Environment_Change_Detector;
-use Automattic\Jetpack_Boost\Lib\Speed_Score;
+use Automattic\Jetpack_Boost\REST_API\Permissions\Nonce;
-/**
- * Class Admin
- */
class Admin {
/**
@@ -45,7 +45,7 @@ class Admin {
*
* @var Jetpack_Boost Plugin.
*/
- private $jetpack_boost;
+ private $modules;
/**
* Speed_Score class instance.
@@ -54,21 +54,13 @@ class Admin {
*/
private $speed_score;
- /**
- * Initialize the class and set its properties.
- *
- * @param Jetpack_Boost $jetpack_boost Main plugin instance.
- *
- * @since 1.0.0
- */
- public function __construct( Jetpack_Boost $jetpack_boost ) {
- $this->jetpack_boost = $jetpack_boost;
- $this->speed_score = new Speed_Score( $jetpack_boost );
+ public function __construct( Optimizations $modules ) {
+ $this->modules = $modules;
+ $this->speed_score = new Speed_Score( $modules );
Environment_Change_Detector::init();
add_action( 'init', array( new Analytics(), 'init' ) );
add_filter( 'plugin_action_links_' . JETPACK_BOOST_PLUGIN_BASE, array( $this, 'plugin_page_settings_link' ) );
- add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
add_action( 'admin_notices', array( $this, 'show_notices' ) );
add_action( 'wp_ajax_set_show_rating_prompt', array( $this, 'handle_set_show_rating_prompt' ) );
add_filter( 'jetpack_boost_js_constants', array( $this, 'add_js_constants' ) );
@@ -102,7 +94,7 @@ public function enqueue_styles() {
$internal_path = apply_filters( 'jetpack_boost_asset_internal_path', 'app/assets/dist/' );
wp_enqueue_style(
- $this->jetpack_boost->get_plugin_name() . '-css',
+ 'jetpack-boost-css',
plugins_url( $internal_path . 'jetpack-boost.css', JETPACK_BOOST_PATH ),
array( 'wp-components' ),
JETPACK_BOOST_VERSION
@@ -117,7 +109,7 @@ public function enqueue_styles() {
public function enqueue_scripts() {
$internal_path = apply_filters( 'jetpack_boost_asset_internal_path', 'app/assets/dist/' );
- $admin_js_handle = $this->jetpack_boost->get_plugin_name() . '-admin';
+ $admin_js_handle = 'jetpack-boost-admin';
wp_register_script(
$admin_js_handle,
@@ -127,6 +119,7 @@ public function enqueue_scripts() {
true
);
+ $optimizations = ( new Optimizations() )->get_status();
// Prepare configuration constants for JavaScript.
$constants = array(
'version' => JETPACK_BOOST_VERSION,
@@ -134,8 +127,7 @@ public function enqueue_scripts() {
'namespace' => JETPACK_BOOST_REST_NAMESPACE,
'prefix' => JETPACK_BOOST_REST_PREFIX,
),
- 'modules' => $this->jetpack_boost->get_available_modules(),
- 'config' => $this->jetpack_boost->config()->get_data(),
+ 'optimizations' => $optimizations,
'locale' => get_locale(),
'site' => array(
'url' => get_home_url(),
@@ -146,6 +138,14 @@ public function enqueue_scripts() {
'preferences' => array(
'showRatingPrompt' => $this->get_show_rating_prompt(),
),
+
+ /**
+ * A bit of necessary magic,
+ * Explained more in the Nonce class.
+ *
+ * Nonces are automatically generated when registering routes.
+ */
+ 'nonces' => Nonce::get_generated_nonces(),
);
// Give each module an opportunity to define extra constants.
@@ -179,7 +179,7 @@ public function plugin_page_settings_link( $links ) {
*/
public function render_settings() {
wp_localize_script(
- $this->jetpack_boost->get_plugin_name() . '-admin',
+ 'jetpack-boost-admin',
'wpApiSettings',
array(
'root' => esc_url_raw( rest_url() ),
@@ -200,49 +200,6 @@ public function check_for_permissions() {
return current_user_can( 'manage_options' );
}
- /**
- * Register REST routes for settings.
- *
- * @return void
- */
- public function register_rest_routes() {
- // Activate and deactivate a module.
- register_rest_route(
- JETPACK_BOOST_REST_NAMESPACE,
- JETPACK_BOOST_REST_PREFIX . '/module/(?P[a-z\-]+)/status',
- array(
- 'methods' => \WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'set_module_status' ),
- 'permission_callback' => array( $this, 'check_for_permissions' ),
- )
- );
- }
-
- /**
- * Handler for the /module/(?P[a-z\-]+)/status endpoint.
- *
- * @param \WP_REST_Request $request The request object.
- *
- * @return \WP_REST_Response|\WP_Error The response.
- */
- public function set_module_status( $request ) {
- $params = $request->get_json_params();
-
- if ( ! isset( $params['status'] ) ) {
- return new \WP_Error(
- 'jetpack_boost_error_missing_module_status_param',
- __( 'Missing status param', 'jetpack-boost' )
- );
- }
-
- $module_slug = $request['slug'];
- $this->jetpack_boost->set_module_status( (bool) $params['status'], $module_slug );
-
- return rest_ensure_response(
- $this->jetpack_boost->get_module_status( $module_slug )
- );
- }
-
/**
* Show any admin notices from enabled modules.
*/
@@ -250,7 +207,7 @@ public function show_notices() {
// Determine if we're already on the settings page.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$on_settings_page = isset( $_GET['page'] ) && self::MENU_SLUG === $_GET['page'];
- $notices = $this->jetpack_boost->get_admin_notices();
+ $notices = $this->get_admin_notices();
// Filter out any that have been dismissed, unless newer than the dismissal.
$dismissed_notices = \get_option( self::DISMISSED_NOTICE_OPTION, array() );
@@ -281,7 +238,7 @@ function ( $notice ) use ( $dismissed_notices ) {
* @return array List of notice ids.
*/
private function get_shown_admin_notice_ids() {
- $notices = $this->jetpack_boost->get_admin_notices();
+ $notices = $this->get_admin_notices();
$ids = array();
foreach ( $notices as $notice ) {
$ids[] = $notice->get_id();
@@ -290,6 +247,18 @@ private function get_shown_admin_notice_ids() {
return $ids;
}
+ /**
+ * Returns a list of admin notices to show. Asks each module to provide admin notices the user needs to see.
+ *
+ * @TODO: This is still a code smell. We're carrying the whole modules instance just to get a list of admin notices.
+ *
+ * @return \Automattic\Jetpack_Boost\Admin\Admin_Notice[]
+ */
+ public function get_admin_notices() {
+ $critical_css = new Critical_CSS();
+ return $critical_css->get_admin_notices();
+ }
+
/**
* Check for a GET parameter used to dismiss an admin notice.
*
diff --git a/projects/plugins/boost/app/assets/src/js/global.d.ts b/projects/plugins/boost/app/assets/src/js/global.d.ts
index 16d75ef98e167..b341177e5a5a8 100644
--- a/projects/plugins/boost/app/assets/src/js/global.d.ts
+++ b/projects/plugins/boost/app/assets/src/js/global.d.ts
@@ -12,7 +12,7 @@ import type { BrowserInterfaceIframe, generateCriticalCSS } from 'jetpack-boost-
*/
import type { ConnectionStatus } from './stores/connection';
import type { CriticalCssStatus } from './stores/critical-css-status';
-import type { ModulesState } from './stores/modules';
+import type { Optimizations } from './stores/modules';
declare global {
const wpApiSettings: {
@@ -35,15 +35,17 @@ declare global {
connection: ConnectionStatus;
criticalCssStatus?: CriticalCssStatus;
showRatingPromptNonce?: string;
- criticalCssDismissRecommendationsNonce?: string;
criticalCssDismissedRecommendations: string[];
site: {
url: string;
online: boolean;
assetPath: string;
};
- config: ModulesState;
+ optimizations: Optimizations;
shownAdminNoticeIds: string[];
+ nonces: {
+ [ key: string ]: string;
+ };
};
// Critical CSS Generator library.
diff --git a/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts b/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts
index 4f80206670bc4..b6c98fe8d5aac 100644
--- a/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts
+++ b/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts
@@ -6,11 +6,11 @@ import { writable, derived } from 'svelte/store';
/**
* Internal dependencies
*/
+import api from '../api/api';
import { CriticalCssErrorDetails, criticalCssStatus } from './critical-css-status';
import type { JSONObject } from '../utils/json-types';
import { objectFilter } from '../utils/object-filter';
import { sortByFrequency } from '../utils/sort-by-frequency';
-import { makeAdminAjaxRequest } from '../utils/make-admin-ajax-request';
import { castToString } from '../utils/cast-to-string';
const importantProviders = [
@@ -118,10 +118,9 @@ export function setDismissalError( title: string, error: JSONObject ): void {
* @param {string} key Key of recommendation to dismiss.
*/
export async function dismissRecommendation( key: string ): Promise< void > {
- await makeAdminAjaxRequest( {
- action: 'dismiss_recommendations',
+ await api.post( '/recommendations/dismiss', {
providerKey: key,
- nonce: Jetpack_Boost.criticalCssDismissRecommendationsNonce,
+ nonce: Jetpack_Boost.nonces[ 'recommendations/dismiss' ],
} );
dismissed.update( keys => [ ...keys, key ] );
}
@@ -130,9 +129,8 @@ export async function dismissRecommendation( key: string ): Promise< void > {
* Clear all the dismissed recommendations.
*/
export async function clearDismissedRecommendations(): Promise< void > {
- await makeAdminAjaxRequest( {
- action: 'reset_dismissed_recommendations',
- nonce: Jetpack_Boost.criticalCssDismissRecommendationsNonce,
+ await api.post( '/recommendations/reset', {
+ nonce: Jetpack_Boost.nonces[ 'recommendations/reset' ],
} );
dismissed.set( [] );
}
diff --git a/projects/plugins/boost/app/assets/src/js/stores/modules.ts b/projects/plugins/boost/app/assets/src/js/stores/modules.ts
index f9accfb9b2544..668f0771c61e6 100644
--- a/projects/plugins/boost/app/assets/src/js/stores/modules.ts
+++ b/projects/plugins/boost/app/assets/src/js/stores/modules.ts
@@ -9,6 +9,10 @@ import { writable } from 'svelte/store';
import config from './config';
import { setModuleState } from '../api/modules';
+export type Optimizations = {
+ [ slug: string ]: boolean;
+};
+
export type ModulesState = {
[ slug: string ]: {
enabled: boolean;
@@ -16,7 +20,13 @@ export type ModulesState = {
};
};
-const initialState = config.config;
+const initialState = {};
+for ( const [ name, value ] of Object.entries( config.optimizations ) ) {
+ initialState[ name ] = {
+ enabled: value,
+ };
+}
+
const { subscribe, update } = writable< ModulesState >( initialState );
// Keep a subscribed copy for quick reading.
diff --git a/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts b/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts
index 9e931710023bb..a76d34c2321d6 100644
--- a/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts
+++ b/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts
@@ -76,12 +76,12 @@ export default async function generateCriticalCss(
hasGenerateRun = true;
let cancelling = false;
- if ( reset ) {
- await clearDismissedRecommendations();
- updateGenerateStatus( true, 0 );
- }
-
try {
+ if ( reset ) {
+ await clearDismissedRecommendations();
+ updateGenerateStatus( true, 0 );
+ }
+
// Fetch a list of provider keys and URLs while loading the Critical CSS lib.
const cssStatus = await requestGeneration( reset, isShowstopperRetry );
diff --git a/projects/plugins/boost/app/class-jetpack-boost.php b/projects/plugins/boost/app/class-jetpack-boost.php
index cbef9b8fcd3f3..02ac717a4041c 100644
--- a/projects/plugins/boost/app/class-jetpack-boost.php
+++ b/projects/plugins/boost/app/class-jetpack-boost.php
@@ -12,19 +12,17 @@
namespace Automattic\Jetpack_Boost;
-use Automattic\Jetpack\Config as Jetpack_Config;
use Automattic\Jetpack_Boost\Admin\Admin;
+use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Critical_CSS;
+use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Critical_CSS_Storage;
+use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Regenerate_Admin_Notice;
+use Automattic\Jetpack_Boost\Features\Optimizations\Optimizations;
use Automattic\Jetpack_Boost\Lib\Analytics;
use Automattic\Jetpack_Boost\Lib\CLI;
-use Automattic\Jetpack_Boost\Lib\Config;
use Automattic\Jetpack_Boost\Lib\Connection;
-use Automattic\Jetpack_Boost\Lib\Speed_Score_History;
-use Automattic\Jetpack_Boost\Lib\Viewport;
-use Automattic\Jetpack_Boost\Modules\Critical_CSS\Critical_CSS;
-use Automattic\Jetpack_Boost\Modules\Critical_CSS\Regenerate_Admin_Notice;
-use Automattic\Jetpack_Boost\Modules\Lazy_Images\Lazy_Images;
-use Automattic\Jetpack_Boost\Modules\Module;
-use Automattic\Jetpack_Boost\Modules\Render_Blocking_JS\Render_Blocking_JS;
+use Automattic\Jetpack_Boost\Lib\Setup;
+use Automattic\Jetpack_Boost\REST_API\Endpoints\Optimization_Status;
+use Automattic\Jetpack_Boost\REST_API\REST_API;
/**
* The core plugin class.
@@ -40,28 +38,6 @@
*/
class Jetpack_Boost {
- const MODULES = array(
- Critical_CSS::MODULE_SLUG => Critical_CSS::class,
- Lazy_Images::MODULE_SLUG => Lazy_Images::class,
- Render_Blocking_JS::MODULE_SLUG => Render_Blocking_JS::class,
- );
-
- /**
- * Default enabled modules.
- */
- const ENABLED_MODULES_DEFAULT = array();
-
- /**
- * Default available modules.
- */
- const AVAILABLE_MODULES_DEFAULT = array(
- Critical_CSS::MODULE_SLUG,
- Render_Blocking_JS::MODULE_SLUG,
- Lazy_Images::MODULE_SLUG,
- );
-
- const CURRENT_CONFIG_ID = 'default';
-
/**
* The unique identifier of this plugin.
*
@@ -78,21 +54,6 @@ class Jetpack_Boost {
*/
private $version;
- /**
- * The config
- *
- * @since 1.0.0
- * @var Config|null $config The configuration object
- */
- private $config;
-
- /**
- * Store all plugin module instances here
- *
- * @var array
- */
- private $modules = array();
-
/**
* The Jetpack Boost Connection manager instance.
*
@@ -127,20 +88,12 @@ public function __construct() {
\WP_CLI::add_command( 'jetpack-boost', $cli_instance );
}
- // Initialize the config module separately.
- $this->init_config();
-
- $this->prepare_modules();
+ $optimizations = new Optimizations();
+ Setup::add( $optimizations );
// Initialize the Admin experience.
- $this->init_admin();
-
- // Module readiness filter.
- add_action( 'wp_head', array( $this, 'display_meta_field_module_ready' ) );
-
- add_action( 'init', array( $this, 'initialize_modules' ) );
+ $this->init_admin( $optimizations );
add_action( 'init', array( $this, 'init_textdomain' ) );
- add_action( 'init', array( $this, 'register_cache_clear_actions' ) );
add_action( 'handle_theme_change', array( $this, 'handle_theme_change' ) );
@@ -156,307 +109,23 @@ private function register_deactivation_hook() {
register_deactivation_hook( $plugin_file, array( $this, 'deactivate' ) );
}
- /**
- * Wipe all cached values.
- */
- public function clear_cache() {
- do_action( 'jetpack_boost_clear_cache' );
- }
-
/**
* Plugin deactivation handler. Clear cache, and reset admin notices.
*/
public function deactivate() {
do_action( 'jetpack_boost_deactivate' );
-
- $this->clear_cache();
- Admin::clear_dismissed_notices();
- }
-
- /**
- * Plugin uninstallation handler. Delete all settings and cache.
- */
- public function uninstall() {
- do_action( 'jetpack_boost_uninstall' );
-
- Speed_Score_History::clear_all();
- $this->clear_cache();
- delete_option( apply_filters( 'jetpack_boost_options_store_key_name', 'jetpack_boost_config' ) );
- }
-
- /**
- * Handlers for clearing module caches go here, so that caches get cleared even if the module is not enabled.
- */
- public function register_cache_clear_actions() {
- add_action( 'jetpack_boost_clear_cache', array( $this, 'record_clear_cache_event' ) );
- }
-
- /**
- * Record the clear cache event.
- */
- public function record_clear_cache_event() {
+ do_action( 'jetpack_boost_clear_cache' );
Analytics::record_user_event( 'clear_cache' );
- }
-
- /**
- * Initialize modules.
- *
- * Note: this method ignores the nonce verification linter rule, as jb-disable-modules is intended to work
- * without a nonce.
- *
- * phpcs:disable WordPress.Security.NonceVerification.Recommended
- */
- public function prepare_modules() {
- $available_modules = $this->get_available_modules();
-
- $forced_disabled_modules = array();
-
- // Get the lists of modules explicitly disabled from the 'jb-disable-modules' query string.
- // The parameter is a comma separated value list of module slug.
- if ( ! empty( $_GET['jb-disable-modules'] ) ) {
- // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
- // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
- $forced_disabled_modules = array_map( 'sanitize_key', explode( ',', $_GET['jb-disable-modules'] ) );
- }
-
- foreach ( self::MODULES as $module_slug => $module_class ) {
- // Don't register modules that have been forcibly disabled from the url 'jb-disable-modules' query string parameter.
- if ( in_array( $module_slug, $forced_disabled_modules, true ) || in_array( 'all', $forced_disabled_modules, true ) ) {
- continue;
- }
-
- // All Jetpack Boost modules should extend Module class.
- if ( ! is_subclass_of( $module_class, Module::class ) ) {
- continue;
- }
-
- // Don't register modules that aren't available.
- if ( ! in_array( $module_slug, $available_modules, true ) ) {
- continue;
- }
-
- $module = new $module_class();
- $this->modules[ $module_slug ] = $module;
- }
-
- do_action( 'jetpack_boost_modules_loaded' );
- }
-
- /**
- * Initialize modules when WordPress is ready
- */
- public function initialize_modules() {
- foreach ( $this->modules as $module_slug => $module ) {
- if ( true === $this->get_module_status( $module_slug ) ) {
- $module->initialize();
- }
- }
- }
-
- /**
- * Returns the list of available modules.
- *
- * @return array The available modules.
- */
- public function get_available_modules() {
- $available_modules = self::AVAILABLE_MODULES_DEFAULT;
-
- // Add the Lazy Images module if Jetpack Lazy Images module is enabled.
- if ( Lazy_Images::is_jetpack_lazy_images_module_enabled() ) {
- $available_modules = array_unique( array_merge( self::AVAILABLE_MODULES_DEFAULT, array( Lazy_Images::MODULE_SLUG ) ) );
- }
-
- return apply_filters(
- 'jetpack_boost_modules',
- $available_modules
- );
- }
-
- /**
- * Returns an array of active modules.
- */
- public function get_active_modules() {
- // Cache active modules.
- static $active_modules = null;
- if ( null !== $active_modules ) {
- return $active_modules;
- }
-
- return array_filter(
- $this->modules,
- function ( $module, $module_slug ) {
- return true === $this->get_module_status( $module_slug );
- },
- ARRAY_FILTER_USE_BOTH
- );
- }
-
- /**
- * Returns the status of a given module.
- *
- * @param string $module_slug The module's slug.
- *
- * @return bool The enablement status of the module.
- */
- public function get_module_status( $module_slug ) {
- $default_module_status = in_array( $module_slug, self::ENABLED_MODULES_DEFAULT, true );
-
- return apply_filters( 'jetpack_boost_module_enabled', $default_module_status, $module_slug );
- }
-
- /**
- * Check if a module is enabled.
- *
- * @param boolean $is_enabled Default value.
- * @param string $module_slug The module we are checking.
- *
- * @return mixed|null
- */
- public function is_module_enabled( $is_enabled, $module_slug ) {
- do_action( 'jetpack_boost_pre_is_module_enabled', $is_enabled, $module_slug );
-
- return $this->config()->get_value( "$module_slug/enabled", $is_enabled );
- }
-
- /**
- * Set status of a module.
- *
- * @param boolean $is_enabled Default value.
- * @param string $module_slug The module we are checking.
- */
- public function set_module_status( $is_enabled, $module_slug ) {
- do_action( 'jetpack_boost_pre_set_module_status', $is_enabled, $module_slug );
- Analytics::record_user_event(
- 'set_module_status',
- array(
- 'module' => $module_slug,
- 'status' => $is_enabled,
- )
- );
- $this->config()->set_value( "$module_slug/enabled", $is_enabled, true );
- }
-
- /**
- * Get critical CSS viewport sizes.
- *
- * @param mixed $default The default value.
- *
- * @return mixed|null
- */
- public function get_critical_css_viewport_sizes( $default ) {
- return $this->config()->get_value( 'critical-css/settings/viewport_sizes', $default );
- }
-
- /**
- * Get critical CSS default viewports.
- *
- * @param mixed $default The default value.
- *
- * @return mixed|null
- */
- public function get_critical_css_default_viewports( $default ) {
- return $this->config()->get_value( 'critical-css/settings/default_viewports', $default );
- }
-
- /**
- * Get critical CSS ignore rules.
- *
- * @param mixed $default The default value.
- *
- * @return mixed|null
- */
- public function get_critical_css_ignore_rules( $default ) {
- return $this->config()->get_value( 'critical-css/settings/css-ignore-rules', $default );
- }
-
- /**
- * Returns configuration array.
- *
- * @return Config Configuration array.
- */
- public function config() {
- if ( ! $this->config ) {
- do_action( 'jetpack_boost_pre_get_config' );
- $this->config = Config::get( self::CURRENT_CONFIG_ID ); // under the hood, this actually fetches from an option, not the config cache.
- }
-
- return apply_filters( 'jetpack_boost_config', $this->config );
- }
-
- /**
- * Initialize config system.
- *
- * @todo This should be replaced by a proper configuration implementation eventually.
- */
- public function init_config() {
- add_action( 'switch_blog', array( $this, 'clear_memoized_config' ) );
- add_filter( 'jetpack_boost_module_enabled', array( $this, 'is_module_enabled' ), 0, 2 );
- add_filter( 'jetpack_boost_critical_css_viewport_sizes', array( $this, 'get_critical_css_viewport_sizes' ) );
- add_filter( 'jetpack_boost_critical_css_default_viewports', array( $this, 'get_critical_css_default_viewports' ) );
- add_filter( 'jetpack_boost_critical_css_ignore_rules', array( $this, 'get_critical_css_ignore_rules' ) );
- }
-
- /**
- * Clear the memoized config, executed on `switch_blog`
- */
- public function clear_memoized_config() {
- $this->config = null;
- }
-
- /**
- * Returns a default config array.
- *
- * @return array Default config.
- */
- public static function get_default_config_array() {
- return apply_filters(
- 'jetpack_boost_config_array',
- array(
- Render_Blocking_JS::MODULE_SLUG => array(
- 'enabled' => false,
- ),
- Critical_CSS::MODULE_SLUG => array(
- 'enabled' => false,
- 'settings' => array(
- 'viewport_sizes' => Viewport::DEFAULT_VIEWPORT_SIZES,
- 'default_viewports' => Viewport::DEFAULT_VIEWPORTS,
- 'css-ignore-rules' => array(
- // TODO: Define if we need any default CSS ignore rules
- // Example regex, exclude all css where there is a url inside.
- 'url\(',
- ),
- ),
- ),
- Lazy_Images::MODULE_SLUG => array(
- 'enabled' => false,
- ),
- 'show_rating_prompt' => true,
- )
- );
+ Admin::clear_dismissed_notices();
}
/**
* Initialize the admin experience.
*/
- public function init_admin() {
- if ( ! apply_filters( 'jetpack_boost_connection_bypass', false ) ) {
- $jetpack_config = new Jetpack_Config();
- $jetpack_config->ensure(
- 'connection',
- array(
- 'slug' => 'jetpack-boost',
- 'name' => 'Jetpack Boost',
- 'url_info' => '', // Optional, URL of the plugin.
- )
- );
- }
-
- /**
- * The class responsible for defining all actions that occur in the admin area.
- */
- require_once plugin_dir_path( __FILE__ ) . 'admin/class-admin.php';
-
- new Admin( $this );
+ public function init_admin( $modules ) {
+ REST_API::register( Optimization_Status::class );
+ $this->connection->ensure_connection();
+ new Admin( $modules );
}
/**
@@ -470,15 +139,6 @@ public function init_textdomain() {
);
}
- /**
- * Registers the `jetpack_boost_url_ready` filter which allows modules to provide their readiness status.
- */
- public function display_meta_field_module_ready() {
- ?>
-
- version;
}
- /**
- * Returns a list of admin notices to show. Asks each module to provide admin notices the user needs to see.
- *
- * @return \Automattic\Jetpack_Boost\Admin\Admin_Notice[]
- */
- public function get_admin_notices() {
- $all_notices = array();
-
- foreach ( $this->get_active_modules() as $module ) {
- $module_notices = $module->get_admin_notices();
-
- if ( ! empty( $module_notices ) ) {
- $all_notices = array_merge( $all_notices, $module_notices );
- }
- }
-
- return $all_notices;
- }
-
/**
* Handle an environment change to set the correct status to the Critical CSS request.
* This is done here so even if the Critical CSS module is switched off we can
@@ -528,4 +169,28 @@ public function handle_theme_change() {
Admin::clear_dismissed_notice( Regenerate_Admin_Notice::SLUG );
\update_option( Critical_CSS::RESET_REASON_STORAGE_KEY, Regenerate_Admin_Notice::REASON_THEME_CHANGE, false );
}
+
+ /**
+ * Plugin uninstallation handler. Delete all settings and cache.
+ */
+ public function uninstall() {
+
+ global $wpdb;
+
+ // When uninstalling, make sure all deactivation cleanups have run as well.
+ $this->deactivate();
+
+ // Delete all Jetpack Boost options.
+ $wpdb->query(
+ "
+ DELETE
+ FROM `$wpdb->options`
+ WHERE `option_name` LIKE jetpack_boost_%
+ "
+ );
+
+ // Delete stored Critical CSS.
+ ( new Critical_CSS_Storage() )->clear();
+
+ }
}
diff --git a/projects/plugins/boost/app/contracts/Feature.php b/projects/plugins/boost/app/contracts/Feature.php
new file mode 100644
index 0000000000000..33edf2c86b622
--- /dev/null
+++ b/projects/plugins/boost/app/contracts/Feature.php
@@ -0,0 +1,11 @@
+feature = $feature;
+ $this->status = new Status( $feature->get_slug() );
+ }
+}
diff --git a/projects/plugins/boost/app/features/optimizations/Optimizations.php b/projects/plugins/boost/app/features/optimizations/Optimizations.php
new file mode 100644
index 0000000000000..a933dbeb54edd
--- /dev/null
+++ b/projects/plugins/boost/app/features/optimizations/Optimizations.php
@@ -0,0 +1,127 @@
+get_slug();
+ $this->features[ $slug ] = new Optimization( $feature );
+ }
+ }
+
+ public function available_modules() {
+ $forced_disabled_modules = $this->get_disabled_modules();
+
+ if ( empty( $forced_disabled_modules ) ) {
+ return $this->features;
+ }
+
+ if ( array( 'all' ) === $forced_disabled_modules ) {
+ return array();
+ }
+
+ $available_modules = array();
+ foreach ( $this->features as $slug => $feature ) {
+ if ( ! in_array( $slug, $forced_disabled_modules, true ) ) {
+ $available_modules[ $slug ] = $feature;
+ }
+ }
+
+ return $available_modules;
+ }
+
+ public function have_enabled_modules() {
+ return count( $this->get_status() ) > 0;
+ }
+
+ public function get_status() {
+ $status = array();
+ foreach ( $this->features as $slug => $optimization ) {
+ $status[ $slug ] = $optimization->status->is_enabled();
+ }
+ return $status;
+ }
+
+ public function register_endpoints( $feature ) {
+ if ( ! $feature instanceof Has_Endpoints ) {
+ return false;
+ }
+
+ if ( empty( $feature->get_endpoints() ) ) {
+ return false;
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setup() {
+
+ foreach ( $this->available_modules() as $slug => $optimization ) {
+
+ if ( ! $optimization->status->is_enabled() ) {
+ continue;
+ }
+
+ $optimization->feature->setup();
+ $this->register_endpoints( $optimization->feature );
+
+ do_action( "jetpack_boost_{$slug}_initialized", $this );
+
+ }
+ }
+
+ /**
+ * Get the lists of modules explicitly disabled from the 'jb-disable-modules' query string.
+ * The parameter is a comma separated value list of module slug.
+ *
+ * @return array
+ */
+
+ public function get_disabled_modules() {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
+ if ( ! empty( $_GET['jb-disable-modules'] ) ) {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ return array_map( 'sanitize_key', explode( ',', $_GET['jb-disable-modules'] ) );
+ }
+
+ return array();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setup_trigger() {
+ return 'plugins_loaded';
+ }
+
+}
diff --git a/projects/plugins/boost/app/modules/critical-css/class-admin-bar-css-compat.php b/projects/plugins/boost/app/features/optimizations/critical-css/Admin_Bar_Compatibilty.php
similarity index 75%
rename from projects/plugins/boost/app/modules/critical-css/class-admin-bar-css-compat.php
rename to projects/plugins/boost/app/features/optimizations/critical-css/Admin_Bar_Compatibilty.php
index 5d7924ac6bf13..a2f18f83e04c0 100644
--- a/projects/plugins/boost/app/modules/critical-css/class-admin-bar-css-compat.php
+++ b/projects/plugins/boost/app/features/optimizations/critical-css/Admin_Bar_Compatibilty.php
@@ -1,24 +1,11 @@
storage = new Critical_CSS_Storage();
+ $this->paths = new Source_Providers();
+
+ }
+
+ /**
+ * This is only run if Critical CSS module has been activated.
+ */
+ public function setup() {
+ // Touch to setup the post type. This is a temporary hack.
+ // This should instantiate a new Post_Type_Storage class,
+ // so that Critical_CSS class is responsible
+ // for setting up the storage.
+ $recommendations = new Recommendations();
+ $recommendations->attach_hooks();
+
+ add_action( 'wp', array( $this, 'display_critical_css' ) );
+
+ if ( Generator::is_generating_critical_css() ) {
+ add_action( 'wp_head', array( $this, 'display_generate_meta' ), 0 );
+ $this->force_logged_out_render();
+ }
+
+ add_action( 'handle_theme_change', array( $this, 'clear_critical_css' ) );
+ add_action( 'jetpack_boost_clear_cache', array( $this, 'clear_critical_css' ) );
+ add_filter( 'jetpack_boost_js_constants', array( $this, 'add_critical_css_constants' ) );
+
+ REST_API::register( $this->get_endpoints() );
+ return true;
+ }
+
+ public function get_slug() {
+ return 'critical-css';
+ }
+
+ /**
+ * Renders a tag used to verify this is a valid page to generate Critical CSS with.
+ */
+ public function display_generate_meta() {
+ ?>
+
+ paths->get_current_request_css();
+ if ( ! $critical_css ) {
+ return;
+ }
+
+ $display = new Display_Critical_CSS( $critical_css );
+ add_action( 'wp_head', array( $display, 'display_critical_css' ), 0 );
+ add_filter( 'style_loader_tag', array( $display, 'asynchronize_stylesheets' ), 10, 4 );
+ add_action( 'wp_footer', array( $display, 'onload_flip_stylesheets' ) );
+ }
+
+ /**
+ * Clear Critical CSS.
+ */
+ public function clear_critical_css() {
+ // Mass invalidate all cached values.
+ // ^^ Not true anymore. Mass invalidate __some__ cached values.
+ $this->storage->clear();
+ Critical_CSS_State::reset();
+ }
+
+ /**
+ * Force the current page to render as viewed by a logged out user. Useful when generating
+ * Critical CSS.
+ */
+ private function force_logged_out_render() {
+ $current_user_id = get_current_user_id();
+
+ if ( 0 !== $current_user_id ) {
+ // Force current user to 0 to ensure page is rendered as a non-logged-in user.
+ wp_set_current_user( 0 );
+
+ // Turn off display of admin bar.
+ add_filter( 'show_admin_bar', '__return_false', PHP_INT_MAX );
+ }
+ }
+
+ /**
+ * Override; returns an admin notice to show if there was a reset reason.
+ *
+ * @TODO:
+ * There should be an Admin_Notice class
+ * To create a notice, (new Admin_Notice())->create("notice text");
+ * To view notices: (new Admin_Notice())->get_all();
+ * @return null|\Automattic\Jetpack_Boost\Admin\Admin_Notice[]
+ */
+ public function get_admin_notices() {
+ $reason = \get_option( self::RESET_REASON_STORAGE_KEY );
+
+ if ( ! $reason ) {
+ return array();
+ }
+
+ return array( new Regenerate_Admin_Notice( $reason ) );
+ }
+
+ /**
+ * Clear Critical CSS reset reason option.
+ *
+ * @TODO: Admin notices need to be moved elsewhere.
+ * Note: Looks like we need a way to and options throughout the app.
+ * This is why it's currently awkwardly using a static method with a constant
+ * If we could trust classes to use constructors properly - without performing actions
+ * Then we could easily (and cheaply) instantiate all Boost objects
+ * and kindly ask them to delete themselves
+ */
+ public static function clear_reset_reason() {
+ \delete_option( self::RESET_REASON_STORAGE_KEY );
+ }
+
+ /**
+ * Add Critical CSS related constants to be passed to JavaScript only if the module is enabled.
+ *
+ * @param array $constants Constants to be passed to JavaScript.
+ *
+ * @return array
+ */
+ public function add_critical_css_constants( $constants ) {
+ // Information about the current status of Critical CSS / generation.
+ $generator = new Generator();
+ $constants['criticalCssStatus'] = $generator->get_local_critical_css_generation_info();
+
+ return $constants;
+ }
+
+ /**
+ * @TODO: Facepalm. PHP Typehinting is broken.
+ * @return Endpoint[]
+ *
+ */
+ public function get_endpoints() {
+ return array(
+ Generator_Status::class,
+ Generator_Request::class,
+ Generator_Success::class,
+ Recommendations_Dismiss::class,
+ Recommendations_Reset::class,
+ Generator_Error::class,
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setup_trigger() {
+ return 'init';
+ }
+}
diff --git a/projects/plugins/boost/app/modules/critical-css/class-critical-css-state.php b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_State.php
similarity index 97%
rename from projects/plugins/boost/app/modules/critical-css/class-critical-css-state.php
rename to projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_State.php
index 1768bd0227abc..e5867d9887da1 100644
--- a/projects/plugins/boost/app/modules/critical-css/class-critical-css-state.php
+++ b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_State.php
@@ -7,10 +7,9 @@
* @package automattic/jetpack-boost
*/
-namespace Automattic\Jetpack_Boost\Modules\Critical_CSS;
+namespace Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS;
use Automattic\Jetpack_Boost\Lib\Transient;
-use Automattic\Jetpack_Boost\Modules\Critical_CSS\Providers\Provider;
/**
* Critical CSS State
@@ -203,11 +202,6 @@ public function get_core_providers_status( $keys ) {
protected function get_provider_sources( $providers ) {
$sources = array();
- /**
- * Provider.
- *
- * @var $provider Provider
- */
foreach ( $providers as $provider ) {
$provider_name = $provider::get_provider_name();
@@ -382,7 +376,7 @@ public function get_percent_complete() {
/**
* Reset the Critical CSS state.
*/
- public function reset() {
+ public static function reset() {
Transient::delete( self::KEY );
}
diff --git a/projects/plugins/boost/app/modules/critical-css/class-critical-css-storage.php b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_Storage.php
similarity index 94%
rename from projects/plugins/boost/app/modules/critical-css/class-critical-css-storage.php
rename to projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_Storage.php
index 29f233f445fe5..820b560ac3de3 100644
--- a/projects/plugins/boost/app/modules/critical-css/class-critical-css-storage.php
+++ b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_Storage.php
@@ -7,7 +7,7 @@
* @package automattic/jetpack-boost
*/
-namespace Automattic\Jetpack_Boost\Modules\Critical_CSS;
+namespace Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS;
use Automattic\Jetpack_Boost\Lib\Storage_Post_Type;
diff --git a/projects/plugins/boost/app/features/optimizations/critical-css/Display_Critical_CSS.php b/projects/plugins/boost/app/features/optimizations/critical-css/Display_Critical_CSS.php
new file mode 100644
index 0000000000000..35cf33817306c
--- /dev/null
+++ b/projects/plugins/boost/app/features/optimizations/critical-css/Display_Critical_CSS.php
@@ -0,0 +1,149 @@
+css = $css;
+ }
+
+ /**
+ * Converts existing screen CSS to be asynchronously loaded.
+ *
+ * @param string $html The link tag for the enqueued style.
+ * @param string $handle The style's registered handle.
+ * @param string $href The stylesheet's source URL.
+ * @param string $media The stylesheet's media attribute.
+ *
+ * @return string
+ * @see style_loader_tag
+ */
+ public function asynchronize_stylesheets(
+ $html,
+ $handle,
+ $href,
+ $media
+ ) {
+ // If there is no critical CSS, do not alter the stylesheet loading.
+ if ( false === $this->css ) {
+ return $html;
+ }
+
+ $available_methods = array(
+ 'async' => 'media="not all" data-media="' . $media . '" onload="this.media=this.dataset.media; delete this.dataset.media; this.removeAttribute( \'onload\' );"',
+ 'deferred' => 'media="not all" data-media="' . $media . '"',
+ );
+
+ /**
+ * Loading method for stylesheets.
+ *
+ * Filter the loading method for each stylesheet for the screen with following values:
+ * async - Stylesheets are loaded asynchronously.
+ * Styles are applied once the stylesheet is loaded completely without render blocking.
+ * deferred - Loading of stylesheets are deferred until the window load event.
+ * Styles from all the stylesheets are applied at once after the page load.
+ *
+ * Stylesheet loading behaviour is not altered for any other value such as false or 'default'.
+ * Stylesheet loading is instant and the process blocks the page rendering.
+ * Eg: add_filter( 'jetpack_boost_async_style', '__return_false' );
+ *
+ * @param string $handle The style's registered handle.
+ * @param string $media The stylesheet's media attribute.
+ *
+ * @see onload_flip_stylesheets for how stylesheets loading is deferred.
+ *
+ * @todo Retrieve settings from database, either via auto-configuration or UI option.
+ */
+ $method = apply_filters( 'jetpack_boost_async_style', 'async', $handle, $media );
+
+ // If the loading method is not allowed, do not alter the stylesheet loading.
+ if ( ! isset( $available_methods[ $method ] ) ) {
+ return $html;
+ }
+
+ $html_no_script = '';
+
+ // Update the stylesheet markup for allowed methods.
+ $html = preg_replace( '~media=(\'[^\']+\')|("[^"]+")~', $available_methods[ $method ], $html );
+
+ // Append to the HTML stylesheet tag the same untouched HTML stylesheet tag within the noscript tag
+ // to support the rendering of the stylesheet when JavaScript is disabled.
+ return $html_no_script . $html;
+ }
+
+ /**
+ * Prints the critical CSS to the page.
+ */
+ public function display_critical_css() {
+ $critical_css = $this->css;
+
+ if ( false === $critical_css ) {
+ return false;
+ }
+
+ echo ' tag (or any HTML tags) in output.
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo wp_strip_all_tags( $critical_css );
+
+ echo '';
+ }
+
+ /**
+ * Add a small piece of JavaScript to the footer, which on load flips all
+ * linked stylesheets from media="not all" to "all", and switches the
+ * Critical CSS tag (or any HTML tags) in output.
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo wp_strip_all_tags( $critical_css );
-
- echo '';
- }
-
- /**
- * Check if the current URL is warmed up. For this module, "warmed up" means that
- * either Critical CSS has been generated for this page, or this page is not
- * eligible to have Critical CSS generated for it.
- *
- * @param bool $ready Injected filter value.
- *
- * @return bool
- */
- public function is_ready_filter( $ready ) {
- if ( ! $ready ) {
- return $ready;
- }
-
- // If this page has no provider keys, it is ineligible for Critical CSS.
- $keys = $this->get_current_request_css_keys();
- if ( count( $keys ) === 0 ) {
- return true;
- }
-
- // Return "ready" if Critical CSS has been generated.
- return ! empty( $this->get_critical_css() );
- }
-
- /**
- * Force the current page to render as viewed by a logged out user. Useful when generating
- * Critical CSS.
- */
- private function force_logged_out_render() {
- $current_user_id = get_current_user_id();
-
- if ( 0 !== $current_user_id ) {
- // Force current user to 0 to ensure page is rendered as a non-logged-in user.
- wp_set_current_user( 0 );
-
- // Turn off display of admin bar.
- add_filter( 'show_admin_bar', '__return_false', PHP_INT_MAX );
- }
- }
-
- /**
- * AJAX handler to handle proxying of external CSS resources.
- */
- public function handle_css_proxy() {
- // Verify valid nonce.
- if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), self::GENERATE_PROXY_NONCE ) ) {
- wp_die( '', 400 );
- }
-
- // Make sure currently logged in as admin.
- if ( ! $this->current_user_can_modify_critical_css() ) {
- wp_die( '', 400 );
- }
-
- // Reject any request made when not generating.
- if ( ! $this->state->is_pending() ) {
- wp_die( '', 400 );
- }
-
- // Validate URL and fetch.
- $proxy_url = filter_var( wp_unslash( $_POST['proxy_url'] ), FILTER_VALIDATE_URL );
- if ( ! wp_http_validate_url( $proxy_url ) ) {
- die( 'Invalid URL' );
- }
-
- $response = wp_remote_get( $proxy_url );
- if ( is_wp_error( $response ) ) {
- // TODO: Nicer error handling.
- die( 'error' );
- }
-
- header( 'Content-type: text/css' );
-
- // Outputting proxied CSS contents unescaped.
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo wp_strip_all_tags( $response['body'] );
-
- die();
- }
-
- /**
- * API helper for ensuring this module is enabled before allowing an API
- * endpoint to continue. Will die if this module is not initialized, with
- * a status message indicating so.
- */
- public function ensure_module_initialized() {
- if ( ! $this->is_initialized() ) {
- wp_send_json( array( 'status' => 'module-unavailable' ) );
- }
- }
-
- /**
- * Add a small piece of JavaScript to the footer, which on load flips all
- * linked stylesheets from media="not all" to "all", and switches the
- * Critical CSS