Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.23% covered (success)
94.23%
49 / 52
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Stats_Plugin
94.23% covered (success)
94.23%
49 / 52
75.00% covered (warning)
75.00%
6 / 8
21.08
0.00% covered (danger)
0.00%
0 / 1
 bootstrap
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 configure_packages
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 initialize_other_packages
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
7.03
 is_jetpack_plugin_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 plugin_page_add_links
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 handle_plugin_activation
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 activate_stats_module
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 filter_available_modules_add_stats
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Bootstrap class for the Jetpack Stats plugin.
4 *
5 * @package automattic/jetpack-stats-plugin
6 */
7
8namespace Automattic\Jetpack\Stats_Plugin;
9
10use Automattic\Jetpack\Config;
11use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
13use Automattic\Jetpack\Modules;
14use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
15use Automattic\Jetpack\Paths;
16use Automattic\Jetpack\PremiumAnalytics\Analytics;
17use Automattic\Jetpack\PremiumAnalytics\Classic_Stats;
18use Automattic\Jetpack\PremiumAnalytics\Enablement;
19use Automattic\Jetpack\PremiumAnalytics\Enablement_Setting;
20use Automattic\Jetpack\Stats_Admin\Admin_Bar as Stats_Admin_Bar;
21use Automattic\Jetpack\Stats_Admin\Admin_Post_List_Column;
22use Automattic\Jetpack\Stats_Admin\Dashboard as Stats_Dashboard;
23use Automattic\Jetpack\Stats_Admin\WP_Dashboard_Odyssey_Widget;
24use Automattic\Jetpack\WooCommerceStats\Analytics_Dashboard;
25
26/**
27 * Class to bootstrap the Jetpack Stats plugin.
28 */
29class Jetpack_Stats_Plugin {
30    /**
31     * The admin page slug registered by the Stats dashboard.
32     *
33     * Owned by `Automattic\Jetpack\Stats_Admin\Dashboard`, which registers a top-level
34     * menu with this slug. Kept here so the plugin can link to the page.
35     */
36    const ADMIN_PAGE_SLUG = 'stats';
37
38    /**
39     * Register hooks to initialize the plugin.
40     */
41    public static function bootstrap() {
42        if ( class_exists( Classic_Stats::class ) ) {
43            Classic_Stats::register_feature_flag();
44        }
45
46        add_action( 'plugins_loaded', array( self::class, 'configure_packages' ), 1 );
47        add_action( 'plugins_loaded', array( self::class, 'initialize_other_packages' ) );
48        add_action( 'activated_plugin', array( self::class, 'handle_plugin_activation' ) );
49        add_action( 'jetpack_site_registered', array( self::class, 'activate_stats_module' ) );
50        add_filter( 'plugin_action_links_' . JETPACK_STATS_PLUGIN__FILE_RELATIVE_PATH, array( self::class, 'plugin_page_add_links' ) );
51        add_filter( 'jetpack_get_available_standalone_modules', array( self::class, 'filter_available_modules_add_stats' ) );
52
53        /**
54         * The Jetpack plugin owns the Stats admin menu while it is active, and it chooses
55         * between the legacy Stats screen and the Odyssey dashboard. Once it is deactivated
56         * that choice falls to this plugin, so re-activate the module and take over the menu.
57         */
58        add_action( 'deactivate_jetpack/jetpack.php', array( self::class, 'activate_stats_module' ) );
59    }
60
61    /**
62     * Configure packages controlled by the `Config` class, and boot Stats v2.
63     *
64     * `Config` initializes its packages on 'plugins_loaded' priority 2, hence priority 1 here.
65     * Stats v2 is the exception and boots right away; see `Analytics::init()` for why.
66     */
67    public static function configure_packages() {
68        $config = new Config();
69        // Connection package.
70        $config->ensure(
71            'connection',
72            array(
73                'slug'     => JETPACK_STATS_PLUGIN__SLUG,
74                'name'     => 'Jetpack Stats',
75                'url_info' => 'https://jetpack.com/stats/',
76            )
77        );
78        // Sync package.
79        $config->ensure( 'sync' );
80        // Identity crisis package.
81        $config->ensure( 'identity_crisis' );
82        // Stats package: the tracking pixel, the `view_stats` capability map and the REST provider.
83        $config->ensure( 'stats' );
84        // Stats Admin package: the REST proxy to the WPCOM stats API used by the dashboard.
85        $config->ensure( 'stats_admin' );
86
87        // Another plugin may have loaded an older package without the shared rule.
88        if ( ! self::is_jetpack_plugin_active() && class_exists( Enablement::class ) && Enablement::is_enabled() ) {
89            Analytics::init();
90            Analytics_Dashboard::init();
91        }
92    }
93
94    /**
95     * Initialize packages not controlled by the `Config` class.
96     */
97    public static function initialize_other_packages() {
98        // Set up the REST authentication hooks.
99        Connection_Rest_Authentication::init();
100        // Initialize My Jetpack.
101        My_Jetpack_Initializer::init();
102
103        /**
104         * Three places can register the `stats` menu slug, and only one of them may run.
105         * `Stats_Admin\Main` registers the dashboard itself while the site has no connection,
106         * and the Jetpack plugin registers it from `modules/stats.php` once the Stats module
107         * loads. That leaves the connected site without the Jetpack plugin to this plugin.
108         * The Jetpack plugin also adds the admin bar chart, the Posts column and the dashboard widget.
109         */
110        if ( self::is_jetpack_plugin_active() ) {
111            return;
112        }
113
114        // Registered while Stats v2 is off too: it is the setting that turns it on.
115        add_action( 'rest_api_init', array( Enablement_Setting::class, 'register' ), 0 );
116
117        if ( ( new Connection_Manager() )->is_connected() ) {
118            // Once Stats v2 takes over, old `page=stats` links open it instead.
119            if ( ! method_exists( Analytics::class, 'is_initialized' ) || ! Analytics::is_initialized() || ! Classic_Stats::is_retired() ) {
120                Stats_Dashboard::init();
121            }
122            if ( ( new Modules() )->is_active( 'stats' ) ) {
123                Stats_Admin_Bar::init();
124                Admin_Post_List_Column::register();
125            }
126            add_action( 'wp_dashboard_setup', array( WP_Dashboard_Odyssey_Widget::class, 'register_widget' ) );
127        }
128    }
129
130    /**
131     * Whether the full Jetpack plugin is running alongside this one.
132     *
133     * @return bool
134     */
135    public static function is_jetpack_plugin_active() {
136        return class_exists( 'Jetpack' );
137    }
138
139    /**
140     * Add a Stats link to the plugin actions.
141     *
142     * @param array $links the array of links.
143     * @return array
144     */
145    public static function plugin_page_add_links( $links ) {
146        /** This filter is documented in projects/packages/stats-admin/src/class-admin-bar.php */
147        $stats_url  = apply_filters( 'jetpack_stats_url', admin_url( 'admin.php?page=' . self::ADMIN_PAGE_SLUG ), array( 'view' => 'dashboard' ) );
148        $stats_link = '<a href="' . esc_url( $stats_url ) . '">' . esc_html__( 'Stats', 'jetpack-stats' ) . '</a>';
149        array_unshift( $links, $stats_link );
150
151        return $links;
152    }
153
154    /**
155     * Redirect to the Stats dashboard when the plugin is activated.
156     *
157     * @param string $plugin Path to the plugin file relative to the plugins directory.
158     */
159    public static function handle_plugin_activation( $plugin ) {
160        // `activated_plugin` fires for every plugin, so ignore everything but this one.
161        // Otherwise activating an unrelated plugin turns Stats back on after a user disabled it.
162        if ( JETPACK_STATS_PLUGIN__FILE_RELATIVE_PATH !== $plugin ) {
163            return;
164        }
165
166        // On a connected site the module can be switched on right away. On a site with no
167        // connection this is a no-op.
168        self::activate_stats_module();
169
170        // `page=stats` opens in both connection states, as the classic page or as Stats v2 once
171        // that retires it. Without a connection the dashboard offers a plan and drives the
172        // connection itself.
173        if ( ( new Paths() )->is_current_request_activating_plugin_from_plugins_screen( JETPACK_STATS_PLUGIN__FILE_RELATIVE_PATH ) ) {
174            wp_safe_redirect( esc_url( admin_url( 'admin.php?page=' . self::ADMIN_PAGE_SLUG ) ) );
175            exit( 0 );
176        }
177    }
178
179    /**
180     * Activate the Stats module on a connected site.
181     *
182     * @return bool True when the module is active after the call, false when the site has no
183     *              connection or the module could not be activated.
184     */
185    public static function activate_stats_module() {
186        if ( ! ( new Connection_Manager() )->is_connected() ) {
187            return false;
188        }
189
190        return (bool) ( new Modules() )->activate( 'stats', false, false );
191    }
192
193    /**
194     * Add the Stats module to the list of modules available without the Jetpack plugin.
195     *
196     * @param array $modules The available modules.
197     * @return array
198     */
199    public static function filter_available_modules_add_stats( $modules ) {
200        return array_merge( array( 'stats' ), $modules );
201    }
202}