Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.81% covered (warning)
88.81%
262 / 295
64.00% covered (warning)
64.00%
16 / 25
CRAP
n/a
0 / 0
Automattic\Jetpack\PremiumAnalytics\register_dashboard_section
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_registered_dashboard_section
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_available_dashboard_sections
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\is_woocommerce_dashboard_section_available
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\get_woocommerce_dashboard_section_default_layout
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\register_default_dashboard_sections
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\PremiumAnalytics\bootstrap_dashboard_sections
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
Automattic\Jetpack\PremiumAnalytics\check_dashboard_sections_permission
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_persisted_preferences_meta_key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_stored_persisted_preferences_for_user
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\PremiumAnalytics\get_dashboard_section_layouts_for_user
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
7.14
Automattic\Jetpack\PremiumAnalytics\update_dashboard_section_layout_for_user
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
Automattic\Jetpack\PremiumAnalytics\delete_dashboard_section_layout_for_user
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
7.18
Automattic\Jetpack\PremiumAnalytics\delete_dashboard_section_layouts_for_user
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
5.03
Automattic\Jetpack\PremiumAnalytics\is_dashboard_section_layout
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
19.82
Automattic\Jetpack\PremiumAnalytics\validate_dashboard_section_layout
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\sanitize_dashboard_section_layout
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\get_available_dashboard_section_for_route
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\PremiumAnalytics\get_dashboard_section_response_data
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\get_dashboard_sections_response
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_dashboard_section_default_layout_response
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\update_dashboard_section_layout_response
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
3.23
Automattic\Jetpack\PremiumAnalytics\delete_dashboard_section_layout_response
68.75% covered (warning)
68.75%
11 / 16
0.00% covered (danger)
0.00%
0 / 1
3.27
Automattic\Jetpack\PremiumAnalytics\delete_dashboard_sections_layouts_response
28.57% covered (danger)
28.57%
2 / 7
0.00% covered (danger)
0.00%
0 / 1
3.46
Automattic\Jetpack\PremiumAnalytics\register_dashboard_sections_rest_routes
100.00% covered (success)
100.00%
94 / 94
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Dashboard Sections: registry bootstrap and REST routes.
4 *
5 * @package automattic/jetpack-premium-analytics
6 */
7
8namespace Automattic\Jetpack\PremiumAnalytics;
9
10require_once __DIR__ . '/dashboard-layout.php';
11require_once __DIR__ . '/dashboard-grammar.php';
12require_once __DIR__ . '/class-dashboard-section.php';
13require_once __DIR__ . '/class-dashboard-section-registry.php';
14
15/**
16 * Filter through which WooCommerce section availability is resolved.
17 */
18const WOOCOMMERCE_DASHBOARD_SECTION_AVAILABLE_FILTER = 'jetpack_premium_analytics_woocommerce_dashboard_section_available';
19
20/**
21 * Registers a dashboard section.
22 *
23 * @param string $dashboard_name Dashboard identifier.
24 * @param string $id             Section identifier.
25 * @param array  $args           Optional. Section arguments.
26 * @return Dashboard_Section|false The registered section on success, or false on failure.
27 */
28function register_dashboard_section( $dashboard_name, $id, $args = array() ) {
29    return Dashboard_Section_Registry::get_instance()->register( $dashboard_name, $id, $args );
30}
31
32/**
33 * Retrieves a registered dashboard section.
34 *
35 * @param string $dashboard_name Dashboard identifier.
36 * @param string $id             Section identifier.
37 * @return Dashboard_Section|null The registered section, or null when absent.
38 */
39function get_registered_dashboard_section( $dashboard_name, $id ) {
40    return Dashboard_Section_Registry::get_instance()->get_registered( $dashboard_name, $id );
41}
42
43/**
44 * Retrieves available dashboard sections.
45 *
46 * @param string $dashboard_name Dashboard identifier.
47 * @return Dashboard_Section[] Ordered list of available sections.
48 */
49function get_available_dashboard_sections( $dashboard_name ) {
50    return Dashboard_Section_Registry::get_instance()->get_available_sections( $dashboard_name );
51}
52
53/**
54 * Whether the WooCommerce dashboard section should be exposed.
55 *
56 * @return bool True when WooCommerce is active.
57 */
58function is_woocommerce_dashboard_section_available() {
59    $is_available = class_exists( 'WooCommerce' ) || function_exists( 'WC' );
60
61    /**
62     * Filters whether the WooCommerce dashboard section is available.
63     *
64     * @param bool $is_available Whether WooCommerce was detected in the current request.
65     */
66    return (bool) apply_filters( WOOCOMMERCE_DASHBOARD_SECTION_AVAILABLE_FILTER, $is_available );
67}
68
69/**
70 * Returns the default widget layout for the WooCommerce dashboard section.
71 *
72 * @return array Array of widget instances.
73 */
74function get_woocommerce_dashboard_section_default_layout() {
75    return get_dashboard_default_layout_for( 'woocommerce/store' );
76}
77
78/**
79 * Registers the default Premium Analytics dashboard sections.
80 *
81 * @return void
82 */
83function register_default_dashboard_sections() {
84    $registry = Dashboard_Section_Registry::get_instance();
85
86    $sections = array(
87        'analytics/traffic'     => array(
88            'label'          => __( 'Traffic', 'jetpack-premium-analytics' ),
89            'order'          => 10,
90            'default_layout' => static function () {
91                return get_dashboard_default_layout_for( 'analytics/traffic' );
92            },
93        ),
94        'analytics/insights'    => array(
95            'label'          => __( 'Insights', 'jetpack-premium-analytics' ),
96            'order'          => 20,
97            'default_layout' => static function () {
98                return get_dashboard_default_layout_for( 'analytics/insights' );
99            },
100        ),
101        'analytics/subscribers' => array(
102            'label'          => __( 'Subscribers', 'jetpack-premium-analytics' ),
103            'order'          => 30,
104            'default_layout' => static function () {
105                return get_dashboard_default_layout_for( 'analytics/subscribers' );
106            },
107        ),
108        'woocommerce/store'     => array(
109            'label'          => __( 'WooCommerce', 'jetpack-premium-analytics' ),
110            'order'          => 40,
111            'is_available'   => __NAMESPACE__ . '\\is_woocommerce_dashboard_section_available',
112            'default_layout' => __NAMESPACE__ . '\\get_woocommerce_dashboard_section_default_layout',
113        ),
114    );
115
116    foreach ( $sections as $id => $args ) {
117        if ( ! $registry->is_registered( DASHBOARD_NAME, $id ) ) {
118            register_dashboard_section( DASHBOARD_NAME, $id, $args );
119        }
120    }
121}
122
123/**
124 * Hydrates the dashboard section registry.
125 *
126 * @return void
127 */
128function bootstrap_dashboard_sections() {
129    if ( did_action( 'init' ) ) {
130        register_default_dashboard_sections();
131    } else {
132        add_action( 'init', __NAMESPACE__ . '\\register_default_dashboard_sections' );
133    }
134}
135
136/**
137 * Whether the current user can access dashboard section routes.
138 *
139 * @return bool
140 */
141function check_dashboard_sections_permission() {
142    return current_user_can( 'manage_options' );
143}
144
145/**
146 * Gets the user meta key used by the WordPress preferences store.
147 *
148 * @global \wpdb $wpdb WordPress database abstraction object.
149 *
150 * @return string Persisted preferences user meta key.
151 */
152function get_persisted_preferences_meta_key() {
153    global $wpdb;
154
155    return $wpdb->get_blog_prefix() . 'persisted_preferences';
156}
157
158/**
159 * Reads the raw stored preferences for a user.
160 *
161 * The dashboard default layout is injected through the same user meta key at
162 * read time. Section layout writes need the committed value only, otherwise a
163 * section-only update can accidentally persist that synthetic default layout.
164 *
165 * @param int $user_id User ID.
166 * @return array Preferences array.
167 */
168function get_stored_persisted_preferences_for_user( $user_id ) {
169    $default_layout_filter = __NAMESPACE__ . '\\inject_dashboard_default_layout';
170    $removed_filter        = remove_filter( 'get_user_metadata', $default_layout_filter, 99 );
171
172    $preferences = get_user_meta( $user_id, get_persisted_preferences_meta_key(), true );
173
174    if ( $removed_filter ) {
175        add_filter( 'get_user_metadata', $default_layout_filter, 99, 3 );
176    }
177
178    return is_array( $preferences ) ? $preferences : array();
179}
180
181/**
182 * Reads stored dashboard section layouts for a user.
183 *
184 * Invalid or stale section layout entries are ignored while preserving the
185 * missing-key semantics used by the section API.
186 *
187 * @param int $user_id User ID.
188 * @return array<string,array> Map of section IDs to custom layouts.
189 */
190function get_dashboard_section_layouts_for_user( $user_id ) {
191    $preferences = get_stored_persisted_preferences_for_user( $user_id );
192    $scope       = $preferences[ DASHBOARD_LAYOUT_SCOPE ] ?? array();
193
194    if ( ! is_array( $scope ) ) {
195        return array();
196    }
197
198    $layouts = $scope[ DASHBOARD_SECTION_LAYOUTS_KEY ] ?? array();
199
200    if ( ! is_array( $layouts ) ) {
201        return array();
202    }
203
204    $normalized = array();
205
206    foreach ( $layouts as $section_id => $layout ) {
207        if (
208            is_string( $section_id )
209            && 1 === preg_match( '/^' . get_dashboard_section_id_pattern() . '$/', $section_id )
210            && is_dashboard_section_layout( $layout )
211        ) {
212            $normalized[ $section_id ] = array_values( $layout );
213        }
214    }
215
216    return $normalized;
217}
218
219/**
220 * Persists a custom dashboard section layout for a user.
221 *
222 * @param int    $user_id    User ID.
223 * @param string $section_id Section identifier.
224 * @param array  $layout     Widget layout.
225 * @return bool True when the write completed or the stored value was unchanged.
226 */
227function update_dashboard_section_layout_for_user( $user_id, $section_id, $layout ) {
228    $preferences = get_stored_persisted_preferences_for_user( $user_id );
229    $original    = $preferences;
230
231    if ( ! isset( $preferences[ DASHBOARD_LAYOUT_SCOPE ] ) || ! is_array( $preferences[ DASHBOARD_LAYOUT_SCOPE ] ) ) {
232        $preferences[ DASHBOARD_LAYOUT_SCOPE ] = array();
233    }
234
235    if (
236        ! isset( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] )
237        || ! is_array( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] )
238    ) {
239        $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] = array();
240    }
241
242    $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ][ $section_id ] = array_values( $layout );
243
244    if ( $preferences === $original ) {
245        return true;
246    }
247
248    $updated = update_user_meta( $user_id, get_persisted_preferences_meta_key(), $preferences );
249
250    return false !== $updated;
251}
252
253/**
254 * Removes a custom dashboard section layout for a user.
255 *
256 * @param int    $user_id    User ID.
257 * @param string $section_id Section identifier.
258 * @return bool True when the reset completed or there was nothing to reset.
259 */
260function delete_dashboard_section_layout_for_user( $user_id, $section_id ) {
261    $preferences = get_stored_persisted_preferences_for_user( $user_id );
262
263    if (
264        ! isset( $preferences[ DASHBOARD_LAYOUT_SCOPE ] )
265        || ! is_array( $preferences[ DASHBOARD_LAYOUT_SCOPE ] )
266        || ! isset( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] )
267        || ! is_array( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] )
268    ) {
269        return true;
270    }
271
272    if ( ! array_key_exists( $section_id, $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] ) ) {
273        return true;
274    }
275
276    unset( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ][ $section_id ] );
277
278    if ( empty( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] ) ) {
279        unset( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] );
280    }
281
282    $updated = update_user_meta( $user_id, get_persisted_preferences_meta_key(), $preferences );
283
284    return false !== $updated;
285}
286
287/**
288 * Removes all custom dashboard section layouts for a user.
289 *
290 * @param int $user_id User ID.
291 * @return bool True when the reset completed or there was nothing to reset.
292 */
293function delete_dashboard_section_layouts_for_user( $user_id ) {
294    $preferences = get_stored_persisted_preferences_for_user( $user_id );
295
296    if (
297        ! isset( $preferences[ DASHBOARD_LAYOUT_SCOPE ] )
298        || ! is_array( $preferences[ DASHBOARD_LAYOUT_SCOPE ] )
299        || ! isset( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] )
300    ) {
301        return true;
302    }
303
304    unset( $preferences[ DASHBOARD_LAYOUT_SCOPE ][ DASHBOARD_SECTION_LAYOUTS_KEY ] );
305
306    if ( empty( $preferences[ DASHBOARD_LAYOUT_SCOPE ] ) ) {
307        unset( $preferences[ DASHBOARD_LAYOUT_SCOPE ] );
308    }
309
310    $updated = update_user_meta( $user_id, get_persisted_preferences_meta_key(), $preferences );
311
312    return false !== $updated;
313}
314
315/**
316 * Checks whether a value is a valid dashboard section layout.
317 *
318 * @param mixed $layout Candidate layout.
319 * @return bool True when the value is a list of widget instances.
320 */
321function is_dashboard_section_layout( $layout ) {
322    if ( ! is_array( $layout ) ) {
323        return false;
324    }
325
326    $expected_key = 0;
327
328    foreach ( $layout as $key => $widget ) {
329        if ( $key !== $expected_key ) {
330            return false;
331        }
332
333        ++$expected_key;
334
335        if ( ! is_array( $widget ) ) {
336            return false;
337        }
338
339        if ( ! isset( $widget['uuid'] ) || ! is_string( $widget['uuid'] ) || '' === $widget['uuid'] ) {
340            return false;
341        }
342
343        if ( ! isset( $widget['type'] ) || ! is_string( $widget['type'] ) || '' === $widget['type'] ) {
344            return false;
345        }
346
347        if ( isset( $widget['placement'] ) && ! is_array( $widget['placement'] ) ) {
348            return false;
349        }
350
351        if ( isset( $widget['attributes'] ) && ! is_array( $widget['attributes'] ) ) {
352            return false;
353        }
354    }
355
356    return true;
357}
358
359/**
360 * Validates a dashboard section layout REST argument.
361 *
362 * @param mixed $value Candidate layout.
363 * @return true|\WP_Error True when valid, otherwise an error.
364 */
365function validate_dashboard_section_layout( $value ) {
366    if ( is_dashboard_section_layout( $value ) ) {
367        return true;
368    }
369
370    return new \WP_Error(
371        'invalid_dashboard_section_layout',
372        __( 'Dashboard section layout must be an array of widget instances.', 'jetpack-premium-analytics' )
373    );
374}
375
376/**
377 * Sanitizes a dashboard section layout REST argument.
378 *
379 * @param mixed $value Candidate layout.
380 * @return array Layout array.
381 */
382function sanitize_dashboard_section_layout( $value ) {
383    return is_array( $value ) ? array_values( $value ) : array();
384}
385
386/**
387 * Resolves a route section, including availability checks.
388 *
389 * @param string $dashboard_name Dashboard identifier.
390 * @param string $section_id     Section identifier.
391 * @return Dashboard_Section|\WP_Error Registered available section, or error.
392 */
393function get_available_dashboard_section_for_route( $dashboard_name, $section_id ) {
394    $section = get_registered_dashboard_section( $dashboard_name, $section_id );
395
396    if ( ! $section ) {
397        return new \WP_Error(
398            'dashboard_section_not_found',
399            __( 'Dashboard section not found.', 'jetpack-premium-analytics' ),
400            array( 'status' => 404 )
401        );
402    }
403
404    if ( ! $section->is_available() ) {
405        return new \WP_Error(
406            'dashboard_section_unavailable',
407            __( 'Dashboard section is not available.', 'jetpack-premium-analytics' ),
408            array( 'status' => 404 )
409        );
410    }
411
412    return $section;
413}
414
415/**
416 * Builds the public REST representation for a dashboard section.
417 *
418 * @param Dashboard_Section $section         Dashboard section.
419 * @param array             $section_layouts Map of section IDs to custom layouts.
420 * @return array REST response data.
421 */
422function get_dashboard_section_response_data( Dashboard_Section $section, $section_layouts ) {
423    $has_custom_layout = array_key_exists( $section->id, $section_layouts );
424    $data              = $section->to_array();
425
426    $data['layout']          = $has_custom_layout ? $section_layouts[ $section->id ] : $section->get_default_layout();
427    $data['hasCustomLayout'] = $has_custom_layout;
428
429    return $data;
430}
431
432/**
433 * REST callback returning available dashboard sections.
434 *
435 * @param \WP_REST_Request $request REST request carrying the dashboard name.
436 * @return \WP_REST_Response
437 */
438function get_dashboard_sections_response( $request ) {
439    $section_layouts = get_dashboard_section_layouts_for_user( get_current_user_id() );
440    $sections        = array_map(
441        static function ( Dashboard_Section $section ) use ( $section_layouts ) {
442            return get_dashboard_section_response_data( $section, $section_layouts );
443        },
444        get_available_dashboard_sections( $request['name'] )
445    );
446
447    return rest_ensure_response( $sections );
448}
449
450/**
451 * REST callback returning a section's default layout.
452 *
453 * @param \WP_REST_Request $request REST request carrying dashboard and section identifiers.
454 * @return \WP_REST_Response|\WP_Error
455 */
456function get_dashboard_section_default_layout_response( $request ) {
457    $section = get_available_dashboard_section_for_route( $request['name'], $request['section'] );
458
459    if ( is_wp_error( $section ) ) {
460        return $section;
461    }
462
463    return rest_ensure_response( $section->get_default_layout() );
464}
465
466/**
467 * REST callback persisting a dashboard section layout for the current user.
468 *
469 * @param \WP_REST_Request $request REST request carrying dashboard, section, and layout.
470 * @return \WP_REST_Response|\WP_Error
471 */
472function update_dashboard_section_layout_response( $request ) {
473    $section = get_available_dashboard_section_for_route( $request['name'], $request['section'] );
474
475    if ( is_wp_error( $section ) ) {
476        return $section;
477    }
478
479    $layout = sanitize_dashboard_section_layout( $request['layout'] );
480    $user   = get_current_user_id();
481
482    if ( ! update_dashboard_section_layout_for_user( $user, $section->id, $layout ) ) {
483        return new \WP_Error(
484            'dashboard_section_layout_update_failed',
485            __( 'Could not update dashboard section layout.', 'jetpack-premium-analytics' ),
486            array( 'status' => 500 )
487        );
488    }
489
490    return rest_ensure_response(
491        get_dashboard_section_response_data(
492            $section,
493            array( $section->id => $layout )
494        )
495    );
496}
497
498/**
499 * REST callback removing a dashboard section layout customization for the current user.
500 *
501 * @param \WP_REST_Request $request REST request carrying dashboard and section identifiers.
502 * @return \WP_REST_Response|\WP_Error
503 */
504function delete_dashboard_section_layout_response( $request ) {
505    $section = get_available_dashboard_section_for_route( $request['name'], $request['section'] );
506
507    if ( is_wp_error( $section ) ) {
508        return $section;
509    }
510
511    $user = get_current_user_id();
512
513    if ( ! delete_dashboard_section_layout_for_user( $user, $section->id ) ) {
514        return new \WP_Error(
515            'dashboard_section_layout_delete_failed',
516            __( 'Could not reset dashboard section layout.', 'jetpack-premium-analytics' ),
517            array( 'status' => 500 )
518        );
519    }
520
521    return rest_ensure_response(
522        get_dashboard_section_response_data(
523            $section,
524            get_dashboard_section_layouts_for_user( $user )
525        )
526    );
527}
528
529/**
530 * REST callback removing all dashboard section layout customizations for the current user.
531 *
532 * @param \WP_REST_Request $request REST request carrying the dashboard identifier.
533 * @return \WP_REST_Response|\WP_Error
534 */
535function delete_dashboard_sections_layouts_response( $request ) {
536    if ( ! delete_dashboard_section_layouts_for_user( get_current_user_id() ) ) {
537        return new \WP_Error(
538            'dashboard_section_layout_delete_failed',
539            __( 'Could not reset dashboard section layouts.', 'jetpack-premium-analytics' ),
540            array( 'status' => 500 )
541        );
542    }
543
544    return get_dashboard_sections_response( $request );
545}
546
547/**
548 * Registers dashboard section REST routes.
549 *
550 * @return void
551 */
552function register_dashboard_sections_rest_routes() {
553    register_rest_route(
554        DASHBOARD_REST_NAMESPACE,
555        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections',
556        array(
557            'methods'             => \WP_REST_Server::READABLE,
558            'callback'            => __NAMESPACE__ . '\\get_dashboard_sections_response',
559            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
560            'args'                => array(
561                'name' => array(
562                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
563                    'type'        => 'string',
564                ),
565            ),
566        )
567    );
568
569    register_rest_route(
570        DASHBOARD_REST_NAMESPACE,
571        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections',
572        array(
573            'methods'             => \WP_REST_Server::DELETABLE,
574            'callback'            => __NAMESPACE__ . '\\delete_dashboard_sections_layouts_response',
575            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
576            'args'                => array(
577                'name' => array(
578                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
579                    'type'        => 'string',
580                ),
581            ),
582        )
583    );
584
585    register_rest_route(
586        DASHBOARD_REST_NAMESPACE,
587        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections/(?P<section>' . get_dashboard_section_id_pattern() . ')/default-layout',
588        array(
589            'methods'             => \WP_REST_Server::READABLE,
590            'callback'            => __NAMESPACE__ . '\\get_dashboard_section_default_layout_response',
591            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
592            'args'                => array(
593                'name'    => array(
594                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
595                    'type'        => 'string',
596                ),
597                'section' => array(
598                    'description' => __( 'Dashboard section identifier.', 'jetpack-premium-analytics' ),
599                    'type'        => 'string',
600                ),
601            ),
602        )
603    );
604
605    register_rest_route(
606        DASHBOARD_REST_NAMESPACE,
607        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections/(?P<section>' . get_dashboard_section_id_pattern() . ')/layout',
608        array(
609            'methods'             => 'PUT',
610            'callback'            => __NAMESPACE__ . '\\update_dashboard_section_layout_response',
611            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
612            'args'                => array(
613                'name'    => array(
614                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
615                    'type'        => 'string',
616                ),
617                'section' => array(
618                    'description' => __( 'Dashboard section identifier.', 'jetpack-premium-analytics' ),
619                    'type'        => 'string',
620                ),
621                'layout'  => array(
622                    'description'       => __( 'Widget layout to persist for the dashboard section.', 'jetpack-premium-analytics' ),
623                    'type'              => 'array',
624                    'required'          => true,
625                    'validate_callback' => __NAMESPACE__ . '\\validate_dashboard_section_layout',
626                    'sanitize_callback' => __NAMESPACE__ . '\\sanitize_dashboard_section_layout',
627                ),
628            ),
629        )
630    );
631
632    register_rest_route(
633        DASHBOARD_REST_NAMESPACE,
634        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections/(?P<section>' . get_dashboard_section_id_pattern() . ')/layout',
635        array(
636            'methods'             => \WP_REST_Server::DELETABLE,
637            'callback'            => __NAMESPACE__ . '\\delete_dashboard_section_layout_response',
638            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
639            'args'                => array(
640                'name'    => array(
641                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
642                    'type'        => 'string',
643                ),
644                'section' => array(
645                    'description' => __( 'Dashboard section identifier.', 'jetpack-premium-analytics' ),
646                    'type'        => 'string',
647                ),
648            ),
649        )
650    );
651}
652
653bootstrap_dashboard_sections();
654add_action( 'rest_api_init', __NAMESPACE__ . '\\register_dashboard_sections_rest_routes' );