Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.45% covered (warning)
65.45%
108 / 165
35.29% covered (danger)
35.29%
6 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Boost
65.45% covered (warning)
65.45%
108 / 165
35.29% covered (danger)
35.29%
6 / 17
101.70
0.00% covered (danger)
0.00%
0 / 1
 __construct
93.10% covered (success)
93.10%
27 / 29
0.00% covered (danger)
0.00%
0 / 1
3.00
 register_deactivation_hook
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 schedule_version_change
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
4.12
 handle_version_change
85.71% covered (warning)
85.71%
24 / 28
0.00% covered (danger)
0.00%
0 / 1
9.24
 setup_cron_schedules
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 custom_cron_intervals
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 whitelist_query_args
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 activate
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
 deactivate
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 cleanup_image_size_analysis_data
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 init_admin
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 init_jetpack_config
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
2
 init_textdomain
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 get_plugin_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_version
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handle_environment_change
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 uninstall
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * The file that defines the core plugin class
4 *
5 * A class definition that includes attributes and functions used across both the
6 * public-facing side of the site and the admin area.
7 *
8 * @link       https://automattic.com
9 * @since      1.0.0
10 * @package    automattic/jetpack-boost
11 */
12
13namespace Automattic\Jetpack_Boost;
14
15use Automattic\Jetpack\Activity_Log\Jetpack_Activity_Log;
16use Automattic\Jetpack\Boost_Core\Lib\Transient;
17use Automattic\Jetpack\Boost_Speed_Score\Speed_Score_History;
18use Automattic\Jetpack\Config as Jetpack_Config;
19use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
20use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
21use Automattic\Jetpack\Plugin_Deactivation\Deactivation_Handler;
22use Automattic\Jetpack_Boost\Admin\Admin;
23use Automattic\Jetpack_Boost\Admin\Regenerate_Admin_Notice;
24use Automattic\Jetpack_Boost\Data_Sync\Getting_Started_Entry;
25use Automattic\Jetpack_Boost\Lib\Analytics;
26use Automattic\Jetpack_Boost\Lib\Cleanup_Transients;
27use Automattic\Jetpack_Boost\Lib\CLI;
28use Automattic\Jetpack_Boost\Lib\Connection;
29use Automattic\Jetpack_Boost\Lib\Cornerstone\Cornerstone_Pages;
30use Automattic\Jetpack_Boost\Lib\Critical_CSS\Critical_CSS_State;
31use Automattic\Jetpack_Boost\Lib\Critical_CSS\Critical_CSS_Storage;
32use Automattic\Jetpack_Boost\Lib\Critical_CSS\Generator;
33use Automattic\Jetpack_Boost\Lib\Setup;
34use Automattic\Jetpack_Boost\Lib\Site_Health;
35use Automattic\Jetpack_Boost\Lib\Status;
36use Automattic\Jetpack_Boost\Lib\Super_Cache_Tracking;
37use Automattic\Jetpack_Boost\Modules\Module;
38use Automattic\Jetpack_Boost\Modules\Modules_Setup;
39use Automattic\Jetpack_Boost\Modules\Optimizations\Lcp\LCP_State;
40use Automattic\Jetpack_Boost\Modules\Optimizations\Lcp\LCP_Storage;
41use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Cache_Preload;
42use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Page_Cache;
43use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Page_Cache_Setup;
44use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress\Boost_Cache;
45use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress\Boost_Cache_Settings;
46use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress\Storage\File_Storage;
47use Automattic\Jetpack_Boost\REST_API\Endpoints\List_Cornerstone_Pages;
48use Automattic\Jetpack_Boost\REST_API\Endpoints\List_LCP_Analysis;
49use Automattic\Jetpack_Boost\REST_API\Endpoints\List_Site_Urls;
50use Automattic\Jetpack_Boost\REST_API\Endpoints\List_Source_Providers;
51use Automattic\Jetpack_Boost\REST_API\REST_API;
52
53/**
54 * The core plugin class.
55 *
56 * This is used to define internationalization, admin-specific hooks, and
57 * public-facing site hooks.
58 *
59 * Also maintains the unique identifier of this plugin as well as the current
60 * version of the plugin.
61 *
62 * @since      1.0.0
63 * @author     Automattic <support@jetpack.com>
64 *
65 * @phan-constructor-used-for-side-effects
66 */
67class Jetpack_Boost {
68
69    /**
70     * The unique identifier of this plugin.
71     *
72     * @since    1.0.0
73     * @var string The string used to uniquely identify this plugin.
74     */
75    private $plugin_name;
76
77    /**
78     * The current version of the plugin.
79     *
80     * @since    1.0.0
81     * @var string The current version of the plugin.
82     */
83    private $version;
84
85    /**
86     * The Jetpack Boost Connection manager instance.
87     *
88     * @since    1.0.0
89     * @access   public
90     * @var Connection The Jetpack Boost Connection manager instance.
91     */
92    public $connection;
93
94    /**
95     * Define the core functionality of the plugin.
96     *
97     * Set the plugin name and the plugin version that can be used throughout the plugin.
98     * Load the dependencies, define the locale, and set the hooks for the admin area and
99     * the public-facing side of the site.
100     *
101     * @since    1.0.0
102     */
103    public function __construct() {
104        $this->version     = JETPACK_BOOST_VERSION;
105        $this->plugin_name = 'jetpack-boost';
106
107        $this->connection = new Connection();
108        $this->connection->init();
109
110        $this->register_deactivation_hook();
111
112        if ( defined( 'WP_CLI' ) && WP_CLI ) {
113            $cli_instance = new CLI( $this );
114            \WP_CLI::add_command( 'jetpack-boost', $cli_instance );
115        }
116
117        $modules_setup = new Modules_Setup();
118        Setup::add( $modules_setup );
119
120        $cornerstone_pages = new Cornerstone_Pages();
121        Setup::add( $cornerstone_pages );
122
123        // Initialize Jetpack packages (connection, sync, identity crisis).
124        $this->init_jetpack_config();
125
126        // Initialize the Admin experience.
127        $this->init_admin( $modules_setup );
128
129        add_action( 'admin_init', array( $this, 'schedule_version_change' ) );
130
131        add_action( 'init', array( $this, 'init_textdomain' ) );
132        add_action( 'init', array( $this, 'setup_cron_schedules' ) );
133
134        add_action( 'jetpack_boost_environment_changed', array( $this, 'handle_environment_change' ), 10, 2 );
135
136        add_action( 'jetpack_boost_handle_version_change_cron', array( $this, 'handle_version_change' ) );
137
138        add_action( 'jetpack_boost_general_cleanup', array( Cleanup_Transients::class, 'run_cleanup' ) );
139        add_action( Cleanup_Transients::FOLLOWUP_HOOK, array( Cleanup_Transients::class, 'run_cleanup' ) );
140
141        // Fired when plugin ready.
142        do_action( 'jetpack_boost_loaded', $this );
143
144        My_Jetpack_Initializer::init();
145
146        // Activity Log. No-ops while the `activity-log` module is off.
147        Jetpack_Activity_Log::initialize();
148
149        Deactivation_Handler::init( $this->plugin_name, __DIR__ . '/admin/deactivation-dialog.php' );
150
151        // Register the core Image CDN hooks.
152        Image_CDN_Core::setup();
153
154        // Setup Site Health panel functionality.
155        Site_Health::init();
156
157        Super_Cache_Tracking::setup();
158
159        // Boost abilities (WP Abilities API). Gated by `jetpack_wp_abilities_enabled`;
160        // priority 20 leaves room for default-priority filter registrations to land first.
161        add_action( 'plugins_loaded', array( '\Automattic\Jetpack_Boost\Abilities\Boost_Abilities', 'init' ), 20 );
162    }
163
164    /**
165     * Register deactivation hook.
166     */
167    private function register_deactivation_hook() {
168        $plugin_file = trailingslashit( dirname( __DIR__ ) ) . 'jetpack-boost.php';
169        register_deactivation_hook( $plugin_file, array( $this, 'deactivate' ) );
170    }
171
172    public function schedule_version_change() {
173        $version = get_option( 'jetpack_boost_version' );
174
175        if ( $version === JETPACK_BOOST_VERSION ) {
176            return;
177        }
178        update_option( 'jetpack_boost_version', JETPACK_BOOST_VERSION );
179
180        // Schedule the cron event to handle the version change. This ensures the previous version's handle is always flushed.
181        if ( ! wp_next_scheduled( 'jetpack_boost_handle_version_change_cron' ) ) {
182            wp_schedule_single_event( time() + 2, 'jetpack_boost_handle_version_change_cron' );
183        }
184    }
185
186    public function handle_version_change() {
187        // Remove this option to prevent the notice from showing up.
188        delete_site_option( 'jetpack_boost_static_minification' );
189
190        jetpack_boost_minify_empty_caches_for_rules();
191
192        // Add upgrade check for Cornerstone Pages.
193        $pages = jetpack_boost_ds_get( 'cornerstone_pages_list' );
194        if ( is_array( $pages ) && in_array( home_url( '' ), $pages, true ) ) {
195            // Remove homepage (empty string) from the cornerstone pages list.
196            $pages = array_filter(
197                $pages,
198                function ( $page ) {
199                    return $page !== home_url( '' );
200                }
201            );
202            jetpack_boost_ds_set( 'cornerstone_pages_list', $pages );
203        }
204
205        if ( jetpack_boost_minify_is_enabled() ) {
206            // We need to clear Minify scheduled events to ensure the latest scheduled jobs are only scheduled irrespective of scheduled arguments.
207            jetpack_boost_minify_clear_scheduled_events();
208            jetpack_boost_minify_activation();
209        }
210
211        if ( ! get_option( 'jetpack_boost_page_cache_uri_keys' ) ) {
212            $home_url  = home_url();
213            $home_host = wp_parse_url( $home_url, PHP_URL_HOST );
214            if ( is_string( $home_host ) ) {
215                $home_port = wp_parse_url( $home_url, PHP_URL_PORT );
216                if ( is_int( $home_port ) ) {
217                    $home_host .= ':' . $home_port;
218                }
219                ( new Boost_Cache( new File_Storage( strtolower( $home_host ) ) ) )->delete_recursive( $home_url );
220            }
221            update_option( 'jetpack_boost_page_cache_uri_keys', 1, false );
222        }
223
224        $page_cache = new Module( new Page_Cache() );
225        if ( $page_cache->is_enabled() ) {
226            // Schedule the cronjob to preload the cache for Cornerstone Pages.
227            ( new Cache_Preload() )->schedule_cornerstone_cronjob();
228        }
229
230        // Setup a cleanup job
231        if ( ! wp_next_scheduled( 'jetpack_boost_general_cleanup' ) ) {
232            wp_schedule_event( time(), 'daily', 'jetpack_boost_general_cleanup' );
233        }
234    }
235
236    /**
237     * Adds the custom cron intervals to the schedules list.
238     */
239    public function setup_cron_schedules() {
240        add_filter( 'cron_schedules', array( $this, 'custom_cron_intervals' ) );
241    }
242
243    /**
244     * Adds custom cron intervals used by Boost.
245     *
246     * @param array $schedules The existing cron schedules.
247     * @return array The modified cron schedules.
248     *
249     * @since 3.12.0
250     */
251    public function custom_cron_intervals( $schedules ) {
252        // The "twicehourly" name maintains the same pattern as the default "twicedaily" name.
253        if ( ! isset( $schedules['twicehourly'] ) ) {
254            $schedules['twicehourly'] = array(
255                'interval' => 30 * MINUTE_IN_SECONDS,
256                'display'  => __( 'Twice Hourly', 'jetpack-boost' ),
257            );
258        }
259        return $schedules;
260    }
261
262    /**
263     * Add query args used by Boost to a list of allowed query args.
264     *
265     * @param array $allowed_query_args The list of allowed query args.
266     *
267     * @return array The modified list of allowed query args.
268     */
269    public static function whitelist_query_args( $allowed_query_args ) {
270        $allowed_query_args[] = Generator::GENERATE_QUERY_ACTION;
271        $allowed_query_args[] = Module::DISABLE_MODULE_QUERY_VAR;
272        return $allowed_query_args;
273    }
274
275    /**
276     * Plugin activation handler.
277     */
278    public static function activate() {
279        // Make sure user sees the "Get Started" when first time opening.
280        ( new Getting_Started_Entry() )->set( true );
281        Analytics::record_user_event( 'activate_plugin' );
282
283        $page_cache_status = new Status( Page_Cache::get_slug() );
284        if ( $page_cache_status->get() && Boost_Cache_Settings::get_instance()->get_enabled() ) {
285            Page_Cache_Setup::run_setup();
286        }
287
288        $modules_setup = new Modules_Setup();
289
290        // Setup a cleanup job
291        if ( ! wp_next_scheduled( 'jetpack_boost_general_cleanup' ) ) {
292            wp_schedule_event( time(), 'daily', 'jetpack_boost_general_cleanup' );
293        }
294
295        /*
296         * Check what modules are already active (from a previous activation for example).
297         * If there are active modules, we need to ensure each module-related event is triggered again.
298         */
299        $active_modules = $modules_setup->get_status();
300        if (
301            ! empty( $active_modules )
302            && ( new Connection() )->is_connected()
303        ) {
304            foreach ( $active_modules as $module => $status ) {
305                $modules_setup->on_module_status_update( $module, true );
306            }
307        }
308    }
309
310    /**
311     * Plugin deactivation handler. Clear cache, and reset admin notices.
312     */
313    public function deactivate() {
314        do_action( 'jetpack_boost_deactivate' );
315
316        wp_clear_scheduled_hook( 'jetpack_boost_general_cleanup' );
317        wp_clear_scheduled_hook( Cleanup_Transients::FOLLOWUP_HOOK );
318
319        // Tell Minify JS/CSS to clean up.
320        jetpack_boost_page_optimize_deactivate();
321
322        Regenerate_Admin_Notice::dismiss();
323        Analytics::record_user_event( 'deactivate_plugin' );
324        Page_Cache_Setup::deactivate();
325
326        // Clean up Image Size Analysis data.
327        $this->cleanup_image_size_analysis_data();
328    }
329
330    /**
331     * Clean up Image Size Analysis data from the database.
332     *
333     * @since 4.3.0
334     */
335    private function cleanup_image_size_analysis_data() {
336        global $wpdb;
337
338        // Delete all post meta entries for Image Size Analysis fixes.
339        //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
340        $wpdb->delete(
341            $wpdb->postmeta,
342            array( 'meta_key' => '_jb_image_fixes' ),
343            array( '%s' )
344        );
345    }
346
347    /**
348     * Initialize the admin experience.
349     */
350    public function init_admin( $modules_setup ) {
351        REST_API::register( List_Site_Urls::class );
352        REST_API::register( List_Source_Providers::class );
353        REST_API::register( List_Cornerstone_Pages::class );
354        REST_API::register( List_LCP_Analysis::class );
355
356        ( new Admin() )->init( $modules_setup );
357    }
358
359    /**
360     * Initialize Jetpack packages via the Config class.
361     *
362     * Consolidates connection, sync, and identity crisis initialization
363     * into a single Config instance, matching the pattern used by other
364     * standalone Jetpack plugins (Protect, Social, VideoPress, etc.).
365     */
366    private function init_jetpack_config() {
367        $jetpack_config = new Jetpack_Config();
368
369        /**
370         * Filter that fakes the connection to WordPress.com. Useful for testing.
371         *
372         * @param bool $connection Return true to fake the connection.
373         *
374         * @since 1.0.0
375         */
376        if ( ! apply_filters( 'jetpack_boost_connection_bypass', false ) ) {
377            $jetpack_config->ensure(
378                'connection',
379                array(
380                    'slug'     => JETPACK_BOOST_SLUG,
381                    'name'     => 'Jetpack Boost',
382                    'url_info' => '',
383                )
384            );
385        }
386
387        $jetpack_config->ensure(
388            'sync',
389            array(
390                // Closures defer construction (and option reads) to Sync invocation time.
391                'jetpack_sync_callable_whitelist' => array(
392                    // boost_sub_modules_state was removed: it pointed at a method that never
393                    // existed, so the callable never resolved and Sync never sent it.
394                    'boost_modules'                => static function () {
395                        return ( new Modules_Setup() )->get_status();
396                    },
397                    'boost_latest_scores'          => static function () {
398                        return ( new Speed_Score_History( get_home_url() ) )->latest();
399                    },
400                    'boost_latest_no_boost_scores' => static function () {
401                        return ( new Speed_Score_History( add_query_arg( Module::DISABLE_MODULE_QUERY_VAR, 'all', get_home_url() ) ) )->latest();
402                    },
403                    'critical_css_state'           => static function () {
404                        return ( new Critical_CSS_State() )->get();
405                    },
406                    'lcp_state'                    => static function () {
407                        return ( new LCP_State() )->get();
408                    },
409                ),
410            )
411        );
412
413        $jetpack_config->ensure( 'identity_crisis' );
414    }
415
416    /**
417     * Loads the textdomain.
418     */
419    public function init_textdomain() {
420        load_plugin_textdomain(
421            'jetpack-boost',
422            false,
423            JETPACK_BOOST_DIR_PATH . '/languages/'
424        );
425    }
426
427    /**
428     * The name of the plugin used to uniquely identify it within the context of
429     * WordPress and to define internationalization functionality.
430     *
431     * @return string The name of the plugin.
432     * @since     1.0.0
433     */
434    public function get_plugin_name() {
435        return $this->plugin_name;
436    }
437
438    /**
439     * Retrieve the version number of the plugin.
440     *
441     * @return string The version number of the plugin.
442     * @since     1.0.0
443     */
444    public function get_version() {
445        return $this->version;
446    }
447
448    /**
449     * Handle an environment change to set the correct status to the Critical CSS request.
450     * This is done here so even if the Critical CSS module is switched off we can
451     * still capture the change of environment event and flag Critical CSS for a rebuild.
452     */
453    public function handle_environment_change( $is_major_change, $change_type ) {
454        if ( $is_major_change ) {
455            Regenerate_Admin_Notice::enable();
456        }
457
458        jetpack_boost_ds_set( 'critical_css_suggest_regenerate', $change_type );
459    }
460
461    /**
462     * Plugin uninstallation handler. Delete all settings and cache.
463     */
464    public function uninstall() {
465        global $wpdb;
466
467        // When uninstalling, make sure all deactivation cleanups have run as well.
468        $this->deactivate();
469
470        // Delete all Jetpack Boost options.
471        //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
472        $option_names = $wpdb->get_col(
473            "
474                SELECT `option_name`
475                FROM   `$wpdb->options`
476                WHERE  `option_name` LIKE 'jetpack_boost_%';
477            "
478        );
479        //phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
480
481        foreach ( $option_names as $option_name ) {
482            delete_option( $option_name );
483        }
484
485        // Delete the last run options for the network-wide cron jobs.
486        delete_site_option( 'jetpack_boost_404_tester_last_run' );
487        delete_site_option( 'jetpack_boost_minify_cron_cache_cleanup_last_run' );
488        delete_site_option( 'jetpack_boost_minify_cache_rules_applied' );
489
490        // Delete stored Critical CSS and LCP data.
491        ( new Critical_CSS_Storage() )->clear();
492        ( new LCP_Storage() )->clear();
493
494        // Delete all transients created by boost.
495        Transient::delete_bulk();
496
497        // Clear getting started value
498        ( new Getting_Started_Entry() )->set( false );
499
500        Page_Cache_Setup::uninstall();
501    }
502}