Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.30% covered (success)
95.30%
142 / 149
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_REST_API_V2_Endpoint_Launchpad
96.60% covered (success)
96.60%
142 / 147
75.00% covered (warning)
75.00%
6 / 8
23
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_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_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 all registered checklist statuses.
127     *
128     * @return array Associative array of checklist status properties for the REST API.
129     */
130    public function get_checklist_statuses_properties() {
131        $tasks            = wpcom_launchpad_checklists()->get_all_tasks();
132        $allowed_task_ids = array();
133        foreach ( $tasks as $task ) {
134            $allowed_task_ids[] = $task['id'];
135            if ( isset( $task['id_map'] ) ) {
136                $allowed_task_ids[] = $task['id_map'];
137            }
138        }
139        $allowed_task_ids = array_unique( $allowed_task_ids );
140        $properties       = array();
141        foreach ( $allowed_task_ids as $task_id ) {
142            $properties[ $task_id ] = array(
143                'type' => 'boolean',
144            );
145        }
146        return $properties;
147    }
148
149    /**
150     * Permission callback for the REST route.
151     *
152     * @return boolean
153     */
154    public function can_access() {
155        return current_user_can( 'manage_options' );
156    }
157
158    /**
159     * Returns Launchpad-related options.
160     *
161     * @param WP_REST_Request $request Full data about the request.
162     *
163     * @return array Associative array with `site_intent`, `launchpad_screen`,
164     *               `launchpad_checklist_tasks_statuses` as `checklist_statuses`,
165     *               and `checklist`.
166     */
167    public function get_data( $request ) {
168        // Handle translations for Atomic sites.
169        $switched_locale = false;
170        $locale          = $request['_locale'] ? get_user_locale() : null;
171
172        if ( $locale ) {
173            $switched_locale = switch_to_locale( $locale );
174        }
175
176        $checklist_slug = $request['checklist_slug'] ?? get_option( 'site_intent' );
177        $use_goals      = $request['use_goals'] ?? false;
178
179        $launchpad_context = $request['launchpad_context'] ?? null;
180
181        if ( $use_goals ) {
182            // The user must be part of a cohort which should deterine which checklist to show soley on
183            // goal selection, not the "intent".
184            $site_goals     = get_option( 'site_goals', array() );
185            $checklist_slug = Launchpad\get_checklist_slug_by_goals( $site_goals, $request['enable_checklist_for_goals'] );
186        }
187
188        $response = array(
189            'site_intent'        => get_option( 'site_intent' ),
190            'launchpad_screen'   => get_option( 'launchpad_screen' ),
191            'checklist_statuses' => get_option( 'launchpad_checklist_tasks_statuses', array() ),
192            'checklist'          => wpcom_get_launchpad_checklist_by_checklist_slug( $checklist_slug, $launchpad_context ),
193            'is_enabled'         => wpcom_get_launchpad_task_list_is_enabled( $checklist_slug ),
194            'is_dismissed'       => wpcom_launchpad_is_task_list_dismissed( $checklist_slug ),
195            'is_dismissible'     => wpcom_launchpad_is_task_list_dismissible( $checklist_slug ),
196            'title'              => wpcom_get_launchpad_checklist_title_by_checklist_slug( $checklist_slug ),
197        );
198
199        if ( $switched_locale ) {
200            restore_previous_locale();
201        }
202
203        return $response;
204    }
205
206    /**
207     * Parses the relative date string and returns the timestamp
208     *
209     * @param string $relative_date The string to parse.
210     *
211     * @return int The timestamp of when the checklist should be shown again.
212     */
213    public function parse_relative_date( $relative_date ) {
214        $date = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
215
216        return $date->modify( $relative_date )->getTimestamp();
217    }
218
219    /**
220     * Updates Launchpad-related options and returns the result
221     *
222     * @param WP_REST_Request $request Request object.
223     * @return array Associative array with updated site options.
224     */
225    public function update_site_options( $request ) {
226        $updated = array();
227        $input   = $request->get_json_params();
228
229        foreach ( $input as $key => $value ) {
230            switch ( $key ) {
231                case 'checklist_statuses':
232                    $updated[ $key ] = wpcom_launchpad_update_task_status( $value );
233
234                    // This will check if we have completed all the tasks and disable Launchpad if so.
235                    wpcom_launchpad_checklists()->maybe_disable_fullscreen_launchpad();
236                    break;
237
238                case 'is_checklist_dismissed':
239                    $checklist_slug  = $value['slug'];
240                    $is_dismissed    = $value['is_dismissed'] ?? false;
241                    $dismissed_until = isset( $value['dismiss_by'] ) ? $this->parse_relative_date( $value['dismiss_by'] ) : null;
242
243                    wpcom_launchpad_set_task_list_dismissed( $checklist_slug, $is_dismissed, $dismissed_until );
244                    break;
245
246                case 'hide_fse_next_steps_modal':
247                    $value = (bool) $value;
248                    if ( wpcom_launchpad_set_fse_next_steps_modal_hidden( $value ) ) {
249                        $updated[ $key ] = $value;
250                    }
251                    break;
252
253                default:
254                    if ( update_option( $key, $value ) ) {
255                        $updated[ $key ] = $value;
256                    }
257                    break;
258            }
259        }
260
261        return array(
262            'updated' => $updated,
263        );
264    }
265}
266
267wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launchpad' );