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