Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.39% covered (success)
94.39%
101 / 107
91.67% covered (success)
91.67%
11 / 12
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_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_sections_response
100.00% covered (success)
100.00%
7 / 7
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\register_dashboard_sections_rest_routes
100.00% covered (success)
100.00%
34 / 34
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'          => __( 'Store', '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 * Resolves a route section, including availability checks.
147 *
148 * @param string $dashboard_name Dashboard identifier.
149 * @param string $section_id     Section identifier.
150 * @return Dashboard_Section|\WP_Error Registered available section, or error.
151 */
152function get_available_dashboard_section_for_route( $dashboard_name, $section_id ) {
153    $section = get_registered_dashboard_section( $dashboard_name, $section_id );
154
155    if ( ! $section ) {
156        return new \WP_Error(
157            'dashboard_section_not_found',
158            __( 'Dashboard section not found.', 'jetpack-premium-analytics' ),
159            array( 'status' => 404 )
160        );
161    }
162
163    if ( ! $section->is_available() ) {
164        return new \WP_Error(
165            'dashboard_section_unavailable',
166            __( 'Dashboard section is not available.', 'jetpack-premium-analytics' ),
167            array( 'status' => 404 )
168        );
169    }
170
171    return $section;
172}
173
174/**
175 * REST callback returning available dashboard sections.
176 *
177 * @param \WP_REST_Request $request REST request carrying the dashboard name.
178 * @return \WP_REST_Response
179 */
180function get_dashboard_sections_response( $request ) {
181    $sections = array_map(
182        static function ( Dashboard_Section $section ) {
183            return $section->to_array();
184        },
185        get_available_dashboard_sections( $request['name'] )
186    );
187
188    return rest_ensure_response( $sections );
189}
190
191/**
192 * REST callback returning a section's default layout.
193 *
194 * @param \WP_REST_Request $request REST request carrying dashboard and section identifiers.
195 * @return \WP_REST_Response|\WP_Error
196 */
197function get_dashboard_section_default_layout_response( $request ) {
198    $section = get_available_dashboard_section_for_route( $request['name'], $request['section'] );
199
200    if ( is_wp_error( $section ) ) {
201        return $section;
202    }
203
204    return rest_ensure_response( $section->get_default_layout() );
205}
206
207/**
208 * Registers dashboard section REST routes.
209 *
210 * @return void
211 */
212function register_dashboard_sections_rest_routes() {
213    register_rest_route(
214        DASHBOARD_REST_NAMESPACE,
215        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections',
216        array(
217            'methods'             => \WP_REST_Server::READABLE,
218            'callback'            => __NAMESPACE__ . '\\get_dashboard_sections_response',
219            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
220            'args'                => array(
221                'name' => array(
222                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
223                    'type'        => 'string',
224                ),
225            ),
226        )
227    );
228
229    register_rest_route(
230        DASHBOARD_REST_NAMESPACE,
231        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections/(?P<section>' . get_dashboard_section_id_pattern() . ')/default-layout',
232        array(
233            'methods'             => \WP_REST_Server::READABLE,
234            'callback'            => __NAMESPACE__ . '\\get_dashboard_section_default_layout_response',
235            'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
236            'args'                => array(
237                'name'    => array(
238                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
239                    'type'        => 'string',
240                ),
241                'section' => array(
242                    'description' => __( 'Dashboard section identifier.', 'jetpack-premium-analytics' ),
243                    'type'        => 'string',
244                ),
245            ),
246        )
247    );
248}
249
250bootstrap_dashboard_sections();