Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.19% covered (warning)
85.19%
69 / 81
40.00% covered (danger)
40.00%
4 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Initial_State
85.19% covered (warning)
85.19%
69 / 81
40.00% covered (danger)
40.00%
4 / 10
24.72
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 render
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_initial_state
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
3
 get_wp_api_root
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 get_reader_chat_guidelines_url
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 current_user_data
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 get_post_types_with_labels
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 get_purchase_token
28.57% covered (danger)
28.57%
2 / 7
0.00% covered (danger)
0.00%
0 / 1
6.28
 generate_purchase_token
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 current_user_can_purchase
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
1<?php
2/**
3 * The React initial state.
4 *
5 * @package automattic/jetpack-search
6 */
7
8namespace Automattic\Jetpack\Search;
9
10use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11use Automattic\Jetpack\Status;
12use Jetpack_Options;
13
14/**
15 * The React initial state.
16 */
17class Initial_State {
18
19    /**
20     * Connection Manager
21     *
22     * @var Connection_Manager
23     */
24    protected $connection_manager;
25
26    /**
27     * Search Module Control
28     *
29     * @var Module_Control
30     */
31    protected $module_control;
32
33    /**
34     * Constructor
35     *
36     * @param Connection_Manager $connection_manager - Connection mananger instance.
37     * @param Module_Control     $module_control - Module control instance.
38     */
39    public function __construct( $connection_manager = null, $module_control = null ) {
40        $this->connection_manager = $connection_manager ? $connection_manager : new Connection_Manager( Package::SLUG );
41        $this->module_control     = $module_control ? $module_control : new Module_Control();
42    }
43
44    /**
45     * Render JS for the initial state
46     *
47     * @return string - JS string.
48     */
49    public function render() {
50        return 'var JETPACK_SEARCH_DASHBOARD_INITIAL_STATE=' . wp_json_encode( $this->get_initial_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';';
51    }
52
53    /**
54     * Get the initial state data.
55     *
56     * @return array
57     */
58    public function get_initial_state() {
59        return array(
60            'siteData'        => array(
61                'WP_API_root'             => esc_url_raw( rest_url() ),
62                'wpcomOriginApiUrl'       => $this->get_wp_api_root(),
63                'WP_API_nonce'            => wp_create_nonce( 'wp_rest' ),
64                'registrationNonce'       => wp_create_nonce( 'jetpack-registration-nonce' ),
65                'purchaseToken'           => $this->get_purchase_token(),
66                /**
67                 * Whether promotions are visible or not.
68                 *
69                 * @param bool $are_promotions_active Status of promotions visibility. True by default.
70                 */
71                'showPromotions'          => apply_filters( 'jetpack_show_promotions', true ),
72                'adminUrl'                => esc_url( admin_url() ),
73                'readerChatGuidelinesUrl' => $this->get_reader_chat_guidelines_url(),
74                'blogId'                  => Jetpack_Options::get_option( 'id', 0 ),
75                'version'                 => Package::VERSION,
76                'calypsoSlug'             => ( new Status() )->get_site_suffix(),
77                'title'                   => get_bloginfo( 'name' ),
78                'postTypes'               => $this->get_post_types_with_labels(),
79                'isWpcom'                 => Helper::is_wpcom(),
80                // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
81                'isPlanJustUpgraded'      => isset( $_GET['just_upgraded'] ) && wp_unslash( $_GET['just_upgraded'] ),
82                /**
83                 * Whether the Jetpack Search 3.0 Interactivity API blocks are enabled.
84                 * Mirrors the `jetpack_search_blocks_enabled` server-side filter so the
85                 * dashboard React app can gate the new feature-selection UI on the
86                 * same flag the back end uses to register the blocks themselves.
87                 */
88                'searchBlocksEnabled'     => (bool) apply_filters( 'jetpack_search_blocks_enabled', false ),
89            ),
90            'userData'        => array(
91                'currentUser' => $this->current_user_data(),
92            ),
93            'jetpackSettings' => array(
94                'search'                 => $this->module_control->is_active(),
95                'instant_search_enabled' => $this->module_control->is_instant_search_enabled(),
96                'experience'             => $this->module_control->get_experience(),
97            ),
98            'features'        => array_map(
99                'sanitize_text_field',
100                // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
101                isset( $_GET['features'] ) ? explode( ',', wp_unslash( $_GET['features'] ) ) : array()
102            ),
103        );
104    }
105
106    /**
107     * Get API root.
108     *
109     * It return first party API root for WPCOM simple sites.
110     */
111    protected function get_wp_api_root() {
112        if ( ! Helper::is_wpcom() ) {
113            return esc_url_raw( rest_url() );
114        }
115        // First party API prefix for WPCOM.
116        return esc_url_raw( site_url( '/wp-json/wpcom-origin/' ) );
117    }
118
119    /**
120     * Get the Reader Chat guidelines admin page URL when it is registered.
121     *
122     * The guidelines page is controlled outside Jetpack. Returning an empty
123     * URL lets the dashboard hide the link when that experiment is unavailable.
124     *
125     * @return string Guidelines admin URL, or an empty string when unavailable.
126     */
127    protected function get_reader_chat_guidelines_url() {
128        if ( ! function_exists( 'menu_page_url' ) ) {
129            return '';
130        }
131
132        return esc_url_raw( menu_page_url( 'guidelines-wp-admin', false ) );
133    }
134
135    /**
136     * Gather data about the current user.
137     *
138     * @return array
139     */
140    protected function current_user_data() {
141        $current_user      = wp_get_current_user();
142        $is_user_connected = $this->connection_manager->is_user_connected( $current_user->ID );
143        $is_master_user    = $is_user_connected && (int) $current_user->ID && (int) Jetpack_Options::get_option( 'master_user' ) === (int) $current_user->ID;
144        $dotcom_data       = $this->connection_manager->get_connected_user_data();
145
146        $current_user_data = array(
147            'isConnected' => $is_user_connected,
148            'isMaster'    => $is_master_user,
149            'username'    => $current_user->user_login,
150            'id'          => $current_user->ID,
151            'wpcomUser'   => $dotcom_data,
152            'permissions' => array(
153                'manage_options' => current_user_can( 'manage_options' ),
154            ),
155        );
156
157        return $current_user_data;
158    }
159
160    /**
161     * Gets the post type labels for all of the site's post types (including custom post types)
162     *
163     * @return array
164     */
165    protected function get_post_types_with_labels() {
166
167        $args = array(
168            'public' => true,
169        );
170
171        $post_types_with_labels = array();
172
173        $post_types = get_post_types( $args, 'objects' );
174
175        // We don't need all the additional post_type data, just the slug & label
176        foreach ( $post_types as $post_type ) {
177            $post_type_with_label = array(
178                'slug'  => $post_type->name,
179                'label' => $post_type->label,
180            );
181
182            $post_types_with_labels[ $post_type->name ] = $post_type_with_label;
183        }
184        return $post_types_with_labels;
185    }
186
187    /**
188     * Gets a purchase token that is used for Jetpack logged out visitor checkout.
189     * The purchase token should be appended to all CTA url's that lead to checkout.
190     *
191     * @return string|boolean
192     */
193    protected function get_purchase_token() {
194        if ( ! $this->current_user_can_purchase() ) {
195            return false;
196        }
197
198        $purchase_token = Jetpack_Options::get_option( 'purchase_token', false );
199
200        if ( $purchase_token ) {
201            return $purchase_token;
202        }
203        // If the purchase token is not saved in the options table yet, then add it.
204        Jetpack_Options::update_option( 'purchase_token', $this->generate_purchase_token(), true );
205        return Jetpack_Options::get_option( 'purchase_token', false );
206    }
207
208    /**
209     * Generates a purchase token that is used for Jetpack logged out visitor checkout.
210     *
211     * @return string
212     */
213    protected function generate_purchase_token() {
214        return wp_generate_password( 12, false );
215    }
216
217    /**
218     * Determine if the current user is allowed to make Jetpack purchases without
219     * a WordPress.com account
220     *
221     * @return boolean True if the user can make purchases, false if not
222     */
223    public function current_user_can_purchase() {
224        // The site must be site-connected to Jetpack (no users connected).
225        if ( ! $this->connection_manager->is_site_connection() ) {
226            return false;
227        }
228
229        // Make sure only administrators can make purchases.
230        if ( ! current_user_can( 'manage_options' ) ) {
231            return false;
232        }
233
234        return true;
235    }
236}