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