Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.40% covered (success)
98.40%
123 / 125
83.33% covered (warning)
83.33%
10 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Odyssey_Config_Data
98.40% covered (success)
98.40%
123 / 125
83.33% covered (warning)
83.33%
10 / 12
21
0.00% covered (danger)
0.00%
0 / 1
 get_js_config_data
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 get_data
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 get_connected_blog_id
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 get_site_agnostic_data
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
2
 get_initial_state
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
3
 is_automated_transfer
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 get_gmt_offset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_admin_path
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 get_user_locale
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 get_plan_features
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 get_plan
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 get_current_user_capabilities
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Stats Initial State
4 *
5 * @package automattic/jetpack-stats-admin
6 */
7
8namespace Automattic\Jetpack\Stats_Admin;
9
10use Automattic\Jetpack\Blaze;
11use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
12use Automattic\Jetpack\Modules;
13use Automattic\Jetpack\Status\Host;
14use Jetpack_Options;
15
16/**
17 * Class Odyssey_Config_Data
18 *
19 * @package automattic/jetpack-stats-admin
20 */
21class Odyssey_Config_Data {
22
23    /**
24     * Set configData to window.configData.
25     *
26     * @param string $config_variable_name The config variable name.
27     * @param array  $config_data The config data.
28     */
29    public function get_js_config_data( $config_variable_name = 'configData', $config_data = null ) {
30        return "window.{$config_variable_name} = " . wp_json_encode(
31            $config_data === null ? $this->get_data() : $config_data,
32            JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
33        ) . ';';
34    }
35
36    /**
37     * Return the config for the app.
38     */
39    public function get_data() {
40        $blog_id = $this->get_connected_blog_id();
41        $data    = $this->get_site_agnostic_data( $blog_id );
42
43        /*
44         * A site the app cannot reach has no record to seed its store with. Emitting one anyway
45         * would key every entry on a blog ID of 0 and make each lookup miss, so leave it out and
46         * let the app fall back to the screens it shows without a site.
47         */
48        if ( $blog_id ) {
49            $data['intial_state'] = $this->get_initial_state( $blog_id );
50        }
51
52        return $data;
53    }
54
55    /**
56     * The WordPress.com blog ID the app can talk to, or 0 when there is none.
57     *
58     * A site keeps its blog ID when it disconnects, but every request the app would make with it
59     * has to be signed with a token the site no longer holds. Such a site has no more to offer
60     * the app than one that was never connected.
61     *
62     * @return int
63     */
64    protected function get_connected_blog_id() {
65        $blog_id = (int) Jetpack_Options::get_option( 'id' );
66
67        if ( ! Main::is_site_connected() ) {
68            return 0;
69        }
70
71        // Keep registered sites in the dashboard even when their token is malformed.
72        // The traffic request reports the connection error; an ID of 0 routes to plan selection.
73        return $blog_id;
74    }
75
76    /**
77     * Config the app can rely on with or without a WordPress.com connection.
78     *
79     * @param int $blog_id The WordPress.com blog ID, 0 when the site is not connected.
80     * @return array
81     */
82    protected function get_site_agnostic_data( $blog_id ) {
83        global $wp_version;
84
85        return array(
86            'admin_page_base'                => $this->get_admin_path(),
87            'api_root'                       => esc_url_raw( rest_url() ),
88            'blog_id'                        => $blog_id,
89            'enable_all_sections'            => false,
90            'env_id'                         => 'production',
91            'google_analytics_key'           => 'UA-10673494-15',
92            'google_maps_and_places_api_key' => '',
93            'hostname'                       => wp_parse_url( get_site_url(), PHP_URL_HOST ),
94            'i18n_default_locale_slug'       => 'en',
95            'i18n_locale_slug'               => $this->get_user_locale(),
96            'mc_analytics_enabled'           => false,
97            'meta'                           => array(),
98            'nonce'                          => wp_create_nonce( 'wp_rest' ),
99            'site_name'                      => \get_bloginfo( 'name' ),
100            'sections'                       => array(),
101            // Features are inlined @see https://github.com/Automattic/wp-calypso/pull/70122
102            'features'                       => array(
103                'is_running_in_jetpack_site' => ! ( new Host() )->is_wpcom_simple(),
104            ),
105            // Intended for apps that do not use redux.
106            'gmt_offset'                     => $this->get_gmt_offset(),
107            'odyssey_stats_base_url'         => admin_url( 'admin.php?page=stats' ),
108            // Repeated in the site record below, which a site without a connection never gets.
109            'admin_url'                      => admin_url(),
110            'jetpack_version'                => defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '',
111            'stats_admin_version'            => Main::VERSION,
112            'software_version'               => $wp_version,
113        );
114    }
115
116    /**
117     * The Redux state the app boots with.
118     *
119     * @param int $blog_id The WordPress.com blog ID.
120     * @return array
121     */
122    protected function get_initial_state( $blog_id ) {
123        global $wp_version;
124
125        $host         = new Host();
126        $capabilities = $this->get_current_user_capabilities();
127        $can_blaze    = class_exists( 'Automattic\Jetpack\Blaze' ) && Blaze::should_initialize()['can_init'];
128
129        return array(
130            'currentUser' => array(
131                'id'           => 1000,
132                'user'         => array(
133                    'ID'       => 1000,
134                    'username' => 'no-user',
135                ),
136                'capabilities' => array(
137                    "$blog_id" => $capabilities,
138                ),
139            ),
140            'sites'       => array(
141                'items'    => array(
142                    "$blog_id" => array(
143                        'ID'           => $blog_id,
144                        'URL'          => site_url(),
145                        // Atomic and jetpack sites should return true.
146                        'jetpack'      => ! $host->is_wpcom_simple(),
147                        'visible'      => true,
148                        'capabilities' => $capabilities,
149                        'products'     => Jetpack_Plan::get_products(),
150                        'plan'         => $this->get_plan(),
151                        'options'      => array(
152                            'wordads'               => ( new Modules() )->is_active( 'wordads' ),
153                            'admin_url'             => admin_url(),
154                            'gmt_offset'            => $this->get_gmt_offset(),
155                            'is_automated_transfer' => $this->is_automated_transfer( $blog_id ),
156                            'is_wpcom_atomic'       => $host->is_woa_site(),
157                            'is_wpcom_simple'       => $host->is_wpcom_simple(),
158                            'is_vip'                => $host->is_vip_site(),
159                            'jetpack_version'       => defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '',
160                            'stats_admin_version'   => Main::VERSION,
161                            'software_version'      => $wp_version,
162                            'can_blaze'             => $can_blaze,
163                        ),
164                    ),
165                ),
166                'features' => array( "$blog_id" => array( 'data' => $this->get_plan_features() ) ),
167            ),
168        );
169    }
170
171    /**
172     * Defines a filter to set whether a site is an automated_transfer site or not.
173     *
174     * Default is false. On Atomic, this is set to true by `wpcomsh`.
175     *
176     * @param int $blog_id Blog ID.
177     *
178     * @return bool
179     */
180    public function is_automated_transfer( $blog_id ) {
181        /**
182         * Filter if a site is an automated-transfer site.
183         *
184         * @module json-api
185         *
186         * @since 6.4.0
187         *
188         * @param bool is_automated_transfer( $this->blog_id )
189         * @param int  $blog_id Blog identifier.
190         */
191        return apply_filters(
192            'jetpack_site_automated_transfer',
193            false,
194            $blog_id
195        );
196    }
197
198    /**
199     * Get the current site GMT Offset.
200     *
201     * @return float The current site GMT Offset by hours.
202     */
203    protected function get_gmt_offset() {
204        return (float) get_option( 'gmt_offset' );
205    }
206
207    /**
208     * Page base for the Calypso admin page.
209     */
210    protected function get_admin_path() {
211        if ( ! isset( $_SERVER['PHP_SELF'] ) || ! isset( $_SERVER['QUERY_STRING'] ) ) {
212            $parsed = wp_parse_url( admin_url( 'admin.php?page=stats' ) );
213            return $parsed['path'] . '?' . $parsed['query'];
214        }
215        // We do this because page.js requires the exactly page base to be set otherwise it will not work properly.
216        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
217        return wp_unslash( $_SERVER['PHP_SELF'] ) . '?' . wp_unslash( $_SERVER['QUERY_STRING'] );
218    }
219
220    /**
221     * Get locale acceptable by Calypso.
222     */
223    protected function get_user_locale() {
224        /**
225         * In WP, locales are formatted as LANGUAGE_REGION, for example `en`, `en_US`, `es_AR`,
226         * but Calypso expects language-region, e.g. `en-us`, `en`,  `es-ar`. So we need to convert
227         * them to lower case and replace the underscore with a dash.
228         */
229        $locale = strtolower( get_user_locale() );
230        $locale = str_replace( '_', '-', $locale );
231
232        return $locale;
233    }
234
235    /**
236     * Get the features of the current plan.
237     */
238    protected function get_plan_features() {
239        $plan = Jetpack_Plan::get();
240        if ( empty( $plan['features'] ) ) {
241            return array();
242        }
243        return $plan['features'];
244    }
245
246    /**
247     * Get the current plan.
248     *
249     * @return array
250     */
251    protected function get_plan() {
252        $plan = Jetpack_Plan::get();
253        unset( $plan['features'] );
254        unset( $plan['supports'] );
255        return $plan;
256    }
257
258    /**
259     * Get the capabilities of the current user.
260     *
261     * @return array An array of capabilities.
262     */
263    protected function get_current_user_capabilities() {
264        return array(
265            'edit_pages'          => current_user_can( 'edit_pages' ),
266            'edit_posts'          => current_user_can( 'edit_posts' ),
267            'edit_others_posts'   => current_user_can( 'edit_others_posts' ),
268            'edit_others_pages'   => current_user_can( 'edit_others_pages' ),
269            'delete_posts'        => current_user_can( 'delete_posts' ),
270            'delete_others_posts' => current_user_can( 'delete_others_posts' ),
271            'edit_theme_options'  => current_user_can( 'edit_theme_options' ),
272            'edit_users'          => current_user_can( 'edit_users' ),
273            'list_users'          => current_user_can( 'list_users' ),
274            'manage_categories'   => current_user_can( 'manage_categories' ),
275            'manage_options'      => current_user_can( 'manage_options' ),
276            'moderate_comments'   => current_user_can( 'moderate_comments' ),
277            'activate_wordads'    => current_user_can( 'manage_options' ),
278            'promote_users'       => current_user_can( 'promote_users' ),
279            'publish_posts'       => current_user_can( 'publish_posts' ),
280            'upload_files'        => current_user_can( 'upload_files' ),
281            'delete_users'        => current_user_can( 'delete_users' ),
282            'remove_users'        => current_user_can( 'remove_users' ),
283            'own_site'            => current_user_can( 'manage_options' ), // Administrators are considered owners on site.
284            'view_stats'          => current_user_can( 'view_stats' ),
285            'activate_plugins'    => current_user_can( 'activate_plugins' ),
286        );
287    }
288}