Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.39% covered (success)
95.39%
145 / 152
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_REST_API_V2_Endpoint_Launchpad
96.67% covered (success)
96.67%
145 / 150
77.78% covered (warning)
77.78%
7 / 9
24
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 register_routes
100.00% covered (success)
100.00%
79 / 79
100.00% covered (success)
100.00%
1 / 1
1
 get_checklist_slug_enums
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_readable_checklist_slug_enums
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 get_checklist_statuses_properties
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 can_access
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_data
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
5.02
 parse_relative_date
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 update_site_options
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2/**
3 * Launchpad API endpoint
4 *
5 * @package automattic/jetpack-mu-wpcom
6 * @since 1.1.0
7 */
8
9use Automattic\Jetpack\Launchpad;
10
11require_once __DIR__ . '/../launchpad/goal-launchpad-task-lists.php';
12
13/**
14 * Fetches Launchpad-related data for the site.
15 *
16 * @since 1.1.0
17 */
18class WPCOM_REST_API_V2_Endpoint_Launchpad extends WP_REST_Controller {
19
20    /**
21     * Class constructor
22     */
23    public function __construct() {
24        $this->namespace = 'wpcom/v2';
25        $this->rest_base = 'launchpad';
26
27        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
28    }
29
30    /**
31     * Register our routes.
32     */
33    public function register_routes() {
34        register_rest_route(
35            $this->namespace,
36            $this->rest_base,
37            array(
38                array(
39                    'methods'             => WP_REST_Server::READABLE,
40                    'callback'            => array( $this, 'get_data' ),
41                    'permission_callback' => array( $this, 'can_access' ),
42                    'args'                => array(
43                        'checklist_slug'             => array(
44                            'description' => 'Checklist slug',
45                            'type'        => 'string',
46                            'enum'        => $this->get_readable_checklist_slug_enums(),
47                        ),
48                        'launchpad_context'          => array(
49                            'description' => 'Screen where Launchpand instance is loaded.',
50                            'type'        => 'string',
51                        ),
52                        'use_goals'                  => array(
53                            'description' => 'Should the launchpad data use site goals or intent.',
54                            'type'        => 'boolean',
55                            'required'    => false,
56                            'default'     => false,
57                        ),
58                        'enable_checklist_for_goals' => array(
59                            'description' => 'Used by the client to signal to Jetpack which launchpad goals have been enabled (e.g. via feature flags)',
60                            'type'        => 'array',
61                            'items'       => array( 'type' => 'string' ),
62                            'required'    => false,
63                            'default'     => array(),
64                        ),
65                    ),
66                ),
67                array(
68                    'methods'             => WP_REST_Server::EDITABLE,
69                    'callback'            => array( $this, 'update_site_options' ),
70                    'permission_callback' => array( $this, 'can_access' ),
71                    'args'                => array(
72                        'checklist_statuses'        => array(
73                            'description'          => 'Launchpad statuses',
74                            'type'                 => 'object',
75                            'properties'           => $this->get_checklist_statuses_properties(),
76                            'additionalProperties' => false,
77                        ),
78                        'launchpad_screen'          => array(
79                            'description' => 'Launchpad screen',
80                            'type'        => 'string',
81                            'enum'        => array( 'off', 'minimized', 'full', 'skipped' ),
82                        ),
83                        'is_checklist_dismissed'    => array(
84                            'description'          => 'Marks a checklist as dismissed by the user',
85                            'type'                 => 'object',
86                            'properties'           => array(
87                                'slug'         => array(
88                                    'description' => 'Checklist slug',
89                                    'type'        => 'string',
90                                    'enum'        => $this->get_checklist_slug_enums(),
91                                ),
92                                'is_dismissed' => array(
93                                    'type'     => 'boolean',
94                                    'required' => false,
95                                    'default'  => false,
96                                ),
97                                'dismiss_by'   => array(
98                                    'description' => 'Timestamp of when the checklist should be shown again',
99                                    'type'        => 'string',
100                                    'enum'        => array( '+ 1 day', '+ 1 week' ),
101                                ),
102                            ),
103                            'additionalProperties' => false,
104                        ),
105                        'hide_fse_next_steps_modal' => array(
106                            'description' => 'Controls whether we show or hide the next steps modal in the full site editor',
107                            'type'        => 'boolean',
108                        ),
109                    ),
110                ),
111            )
112        );
113    }
114
115    /**
116     * Returns all available checklist slugs.
117     *
118     * @return array Array of checklist slugs.
119     */
120    public function get_checklist_slug_enums() {
121        $checklists = wpcom_launchpad_checklists()->get_all_task_lists();
122        return array_keys( $checklists );
123    }
124
125    /**
126     * Returns the checklist slugs a read request may reference.
127     *
128     * Retired flow slugs are included so a stale client request degrades to an
129     * empty checklist rather than a 400. Read-only: mutations use the registered
130     * slugs from get_checklist_slug_enums() so they cannot act on a retired slug.
131     *
132     * @return array Array of checklist slugs.
133     */
134    public function get_readable_checklist_slug_enums() {
135        return array_values(
136            array_unique( array_merge( $this->get_checklist_slug_enums(), wpcom_launchpad_get_retired_site_intents() ) )
137        );
138    }
139
140    /**
141     * Returns all registered checklist statuses.
142     *
143     * @return array Associative array of checklist status properties for the REST API.
144     */
145    public function get_checklist_statuses_properties() {
146        $tasks            = wpcom_launchpad_checklists()->get_all_tasks();
147        $allowed_task_ids = array();
148        foreach ( $tasks as $task ) {
149            $allowed_task_ids[] = $task['id'];
150            if ( isset( $task['id_map'] ) ) {
151                $allowed_task_ids[] = $task['id_map'];
152            }
153        }
154        $allowed_task_ids = array_unique( $allowed_task_ids );
155        $properties       = array();
156        foreach ( $allowed_task_ids as $task_id ) {
157            $properties[ $task_id ] = array(
158                'type' => 'boolean',
159            );
160        }
161        return $properties;
162    }
163
164    /**
165     * Permission callback for the REST route.
166     *
167     * @return boolean
168     */
169    public function can_access() {
170        return current_user_can( 'manage_options' );
171    }
172
173    /**
174     * Returns Launchpad-related options.
175     *
176     * @param WP_REST_Request $request Full data about the request.
177     *
178     * @return array Associative array with `site_intent`, `launchpad_screen`,
179     *               `launchpad_checklist_tasks_statuses` as `checklist_statuses`,
180     *               and `checklist`.
181     */
182    public function get_data( $request ) {
183        // Handle translations for Atomic sites.
184        $switched_locale = false;
185        $locale          = $request['_locale'] ? get_user_locale() : null;
186
187        if ( $locale ) {
188            $switched_locale = switch_to_locale( $locale );
189        }
190
191        $checklist_slug = $request['checklist_slug'] ?? get_option( 'site_intent' );
192        $use_goals      = $request['use_goals'] ?? false;
193
194        $launchpad_context = $request['launchpad_context'] ?? null;
195
196        if ( $use_goals ) {
197            // The user must be part of a cohort which should deterine which checklist to show soley on
198            // goal selection, not the "intent".
199            $site_goals     = get_option( 'site_goals', array() );
200            $checklist_slug = Launchpad\get_checklist_slug_by_goals( $site_goals, $request['enable_checklist_for_goals'] );
201        }
202
203        $response = array(
204            'site_intent'        => get_option( 'site_intent' ),
205            'launchpad_screen'   => get_option( 'launchpad_screen' ),
206            'checklist_statuses' => get_option( 'launchpad_checklist_tasks_statuses', array() ),
207            'checklist'          => wpcom_get_launchpad_checklist_by_checklist_slug( $checklist_slug, $launchpad_context ),
208            'is_enabled'         => (bool) wpcom_get_launchpad_task_list_is_enabled( $checklist_slug ),
209            'is_dismissed'       => wpcom_launchpad_is_task_list_dismissed( $checklist_slug ),
210            'is_dismissible'     => wpcom_launchpad_is_task_list_dismissible( $checklist_slug ),
211            'title'              => wpcom_get_launchpad_checklist_title_by_checklist_slug( $checklist_slug ),
212        );
213
214        if ( $switched_locale ) {
215            restore_previous_locale();
216        }
217
218        return $response;
219    }
220
221    /**
222     * Parses the relative date string and returns the timestamp
223     *
224     * @param string $relative_date The string to parse.
225     *
226     * @return int The timestamp of when the checklist should be shown again.
227     */
228    public function parse_relative_date( $relative_date ) {
229        $date = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
230
231        return $date->modify( $relative_date )->getTimestamp();
232    }
233
234    /**
235     * Updates Launchpad-related options and returns the result
236     *
237     * @param WP_REST_Request $request Request object.
238     * @return array Associative array with updated site options.
239     */
240    public function update_site_options( $request ) {
241        $updated = array();
242        $input   = $request->get_json_params();
243
244        foreach ( $input as $key => $value ) {
245            switch ( $key ) {
246                case 'checklist_statuses':
247                    $updated[ $key ] = wpcom_launchpad_update_task_status( $value );
248
249                    // This will check if we have completed all the tasks and disable Launchpad if so.
250                    wpcom_launchpad_checklists()->maybe_disable_fullscreen_launchpad();
251                    break;
252
253                case 'is_checklist_dismissed':
254                    $checklist_slug  = $value['slug'];
255                    $is_dismissed    = $value['is_dismissed'] ?? false;
256                    $dismissed_until = isset( $value['dismiss_by'] ) ? $this->parse_relative_date( $value['dismiss_by'] ) : null;
257
258                    wpcom_launchpad_set_task_list_dismissed( $checklist_slug, $is_dismissed, $dismissed_until );
259                    break;
260
261                case 'hide_fse_next_steps_modal':
262                    $value = (bool) $value;
263                    if ( wpcom_launchpad_set_fse_next_steps_modal_hidden( $value ) ) {
264                        $updated[ $key ] = $value;
265                    }
266                    break;
267
268                default:
269                    if ( update_option( $key, $value ) ) {
270                        $updated[ $key ] = $value;
271                    }
272                    break;
273            }
274        }
275
276        return array(
277            'updated' => $updated,
278        );
279    }
280}
281
282wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launchpad' );