Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.36% covered (success)
95.36%
144 / 151
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_REST_API_V2_Endpoint_Launchpad
96.64% covered (success)
96.64%
144 / 149
75.00% covered (warning)
75.00%
6 / 8
27
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
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
8.03
 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
10
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 = isset( $request['checklist_slug'] ) ? $request['checklist_slug'] : get_option( 'site_intent' );
177        $use_goals      = isset( $request['use_goals'] ) ? $request['use_goals'] : false;
178
179        $launchpad_context = isset( $request['launchpad_context'] )
180            ? $request['launchpad_context']
181            : null;
182
183        if ( $use_goals ) {
184            // The user must be part of a cohort which should deterine which checklist to show soley on
185            // goal selection, not the "intent".
186            $site_goals     = get_option( 'site_goals', array() );
187            $checklist_slug = Launchpad\get_checklist_slug_by_goals( $site_goals, $request['enable_checklist_for_goals'] );
188        }
189
190        $response = array(
191            'site_intent'        => get_option( 'site_intent' ),
192            'launchpad_screen'   => get_option( 'launchpad_screen' ),
193            'checklist_statuses' => get_option( 'launchpad_checklist_tasks_statuses', array() ),
194            'checklist'          => wpcom_get_launchpad_checklist_by_checklist_slug( $checklist_slug, $launchpad_context ),
195            'is_enabled'         => wpcom_get_launchpad_task_list_is_enabled( $checklist_slug ),
196            'is_dismissed'       => wpcom_launchpad_is_task_list_dismissed( $checklist_slug ),
197            'is_dismissible'     => wpcom_launchpad_is_task_list_dismissible( $checklist_slug ),
198            'title'              => wpcom_get_launchpad_checklist_title_by_checklist_slug( $checklist_slug ),
199        );
200
201        if ( $switched_locale ) {
202            restore_previous_locale();
203        }
204
205        return $response;
206    }
207
208    /**
209     * Parses the relative date string and returns the timestamp
210     *
211     * @param string $relative_date The string to parse.
212     *
213     * @return int The timestamp of when the checklist should be shown again.
214     */
215    public function parse_relative_date( $relative_date ) {
216        $date = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
217
218        return $date->modify( $relative_date )->getTimestamp();
219    }
220
221    /**
222     * Updates Launchpad-related options and returns the result
223     *
224     * @param WP_REST_Request $request Request object.
225     * @return array Associative array with updated site options.
226     */
227    public function update_site_options( $request ) {
228        $updated = array();
229        $input   = $request->get_json_params();
230
231        foreach ( $input as $key => $value ) {
232            switch ( $key ) {
233                case 'checklist_statuses':
234                    $updated[ $key ] = wpcom_launchpad_update_task_status( $value );
235
236                    // This will check if we have completed all the tasks and disable Launchpad if so.
237                    wpcom_launchpad_checklists()->maybe_disable_fullscreen_launchpad();
238                    break;
239
240                case 'is_checklist_dismissed':
241                    $checklist_slug  = $value['slug'];
242                    $is_dismissed    = isset( $value['is_dismissed'] ) ? $value['is_dismissed'] : false;
243                    $dismissed_until = isset( $value['dismiss_by'] ) ? $this->parse_relative_date( $value['dismiss_by'] ) : null;
244
245                    wpcom_launchpad_set_task_list_dismissed( $checklist_slug, $is_dismissed, $dismissed_until );
246                    break;
247
248                case 'hide_fse_next_steps_modal':
249                    $value = (bool) $value;
250                    if ( wpcom_launchpad_set_fse_next_steps_modal_hidden( $value ) ) {
251                        $updated[ $key ] = $value;
252                    }
253                    break;
254
255                default:
256                    if ( update_option( $key, $value ) ) {
257                        $updated[ $key ] = $value;
258                    }
259                    break;
260            }
261        }
262
263        return array(
264            'updated' => $updated,
265        );
266    }
267}
268
269wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launchpad' );