PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-premium-analytics/src/dashboard-sections.php +345 -0 16.2-beta → 16.3-beta View file →
@@ -1,0 +1,345 @@
1 +<?php
2 +/**
3 + * Dashboard Sections API: the registry helpers, the section script data, and the REST routes.
4 + *
5 + * The package's own sections register through this API from default-dashboard-sections.php,
6 + * the same way a plugin extending the dashboard does.
7 + *
8 + * @package automattic/jetpack-premium-analytics
9 + */
10 +
11 +namespace Automattic\Jetpack\PremiumAnalytics;
12 +
13 +require_once __DIR__ . '/dashboard-layout.php';
14 +require_once __DIR__ . '/dashboard-grammar.php';
15 +require_once __DIR__ . '/rest-namespace.php';
16 +require_once __DIR__ . '/class-dashboard-section.php';
17 +require_once __DIR__ . '/class-dashboard-section-registry.php';
18 +
19 +// Guarded on a symbol the file declares, so a second copy of the package can't redeclare it.
20 +if ( ! function_exists( __NAMESPACE__ . '\\register_dashboard_feature_flags' ) ) {
21 + require_once __DIR__ . '/dashboard-policy.php';
22 +}
23 +
24 +/**
25 + * Registers a dashboard section.
26 + *
27 + * @param string $dashboard_name Dashboard identifier.
28 + * @param string $id Section identifier.
29 + * @param array $args Optional. Section arguments.
30 + * @return Dashboard_Section|false The registered section on success, or false on failure.
31 + */
32 +function register_dashboard_section( $dashboard_name, $id, $args = array() ) {
33 + return Dashboard_Section_Registry::get_instance()->register( $dashboard_name, $id, $args );
34 +}
35 +
36 +/**
37 + * Retrieves a registered dashboard section.
38 + *
39 + * @param string $dashboard_name Dashboard identifier.
40 + * @param string $id Section identifier.
41 + * @return Dashboard_Section|null The registered section, or null when absent.
42 + */
43 +function get_registered_dashboard_section( $dashboard_name, $id ) {
44 + return Dashboard_Section_Registry::get_instance()->get_registered( $dashboard_name, $id );
45 +}
46 +
47 +/**
48 + * Retrieves available dashboard sections.
49 + *
50 + * @param string $dashboard_name Dashboard identifier.
51 + * @return Dashboard_Section[] Ordered list of available sections.
52 + */
53 +function get_available_dashboard_sections( $dashboard_name ) {
54 + return Dashboard_Section_Registry::get_instance()->get_available_sections( $dashboard_name );
55 +}
56 +
57 +/**
58 + * Slugs of the tabs the dashboard exposes, for the client's report routes.
59 + *
60 + * Reads the same sections the tab list does, so a report cannot outlive the tab it sits
61 + * behind. Null, never `array()`, while nothing is registered: an empty array means no
62 + * tab is available.
63 + *
64 + * @since 0.6.0
65 + *
66 + * @return string[]|null
67 + */
68 +function get_available_dashboard_section_slugs() {
69 + $registry = Dashboard_Section_Registry::get_instance();
70 +
71 + if ( empty( $registry->get_all_registered( DASHBOARD_NAME ) ) ) {
72 + return null;
73 + }
74 +
75 + return array_map(
76 + static function ( Dashboard_Section $section ) {
77 + return $section->slug;
78 + },
79 + $registry->get_available_sections( DASHBOARD_NAME )
80 + );
81 +}
82 +
83 +/**
84 + * Configures the section script data.
85 + *
86 + * @since 0.6.0
87 + *
88 + * @return void
89 + */
90 +function configure_dashboard_sections_script_data() {
91 + add_filter( 'jetpack_admin_js_script_data', __NAMESPACE__ . '\\inject_dashboard_sections_script_data', 20 );
92 +}
93 +
94 +/**
95 + * Kept for older copies of the package, whose Dashboard_Section::is_available() calls it on every
96 + * availability check. Every section is in scope now, so the section's own rule decides.
97 + *
98 + * @since 0.6.0
99 + * @deprecated 0.10.0 The preview scope is gone.
100 + *
101 + * @param string $dashboard_name Dashboard identifier.
102 + * @param string $slug URL-facing section slug.
103 + * @return bool Always true.
104 + */
105 +function is_dashboard_section_in_preview_scope( $dashboard_name, $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Kept for the signature older copies call.
106 + return true;
107 +}
108 +
109 +/**
110 + * Kept for older copies of the package: they guard their include of this file on another
111 + * symbol and call this, so a newer copy loading first must still define it.
112 + *
113 + * @since 0.6.0
114 + * @deprecated 0.10.0 Use configure_dashboard_sections_script_data().
115 + *
116 + * @return void
117 + */
118 +function configure_dashboard_preview_scope() {
119 + configure_dashboard_sections_script_data();
120 +}
121 +
122 +/**
123 + * Injects the available section slugs into JetpackScriptData.
124 + *
125 + * The same list travels over REST for the tab bar, but a report route reads no REST before
126 + * choosing its redirect, so it reads the slugs from boot data instead.
127 + *
128 + * @since 0.6.0
129 + *
130 + * @param array $data The script data passed by the assets package.
131 + * @return array
132 + */
133 +function inject_dashboard_sections_script_data( array $data ): array {
134 + $sections = get_available_dashboard_section_slugs();
135 +
136 + if ( null === $sections ) {
137 + return $data;
138 + }
139 +
140 + if ( ! isset( $data['premium_analytics'] ) || ! is_array( $data['premium_analytics'] ) ) {
141 + $data['premium_analytics'] = array();
142 + }
143 +
144 + $data['premium_analytics']['sections'] = $sections;
145 +
146 + return $data;
147 +}
148 +
149 +/**
150 + * Whether the current user can access dashboard section routes.
151 + *
152 + * @return bool
153 + */
154 +function check_dashboard_sections_permission() {
155 + return Capabilities::current_user_can_view_analytics();
156 +}
157 +
158 +/**
159 + * Resolves a route section, including availability checks.
160 + *
161 + * @param string $dashboard_name Dashboard identifier.
162 + * @param string $section_id Section identifier.
163 + * @return Dashboard_Section|\WP_Error Registered available section, or error.
164 + */
165 +function get_available_dashboard_section_for_route( $dashboard_name, $section_id ) {
166 + $section = get_registered_dashboard_section( $dashboard_name, $section_id );
167 +
168 + if ( ! $section ) {
169 + return new \WP_Error(
170 + 'dashboard_section_not_found',
171 + __( 'Dashboard section not found.', 'jetpack-premium-analytics-pkg' ),
172 + array( 'status' => 404 )
173 + );
174 + }
175 +
176 + if ( ! $section->is_available() ) {
177 + return new \WP_Error(
178 + 'dashboard_section_unavailable',
179 + __( 'Dashboard section is not available.', 'jetpack-premium-analytics-pkg' ),
180 + array( 'status' => 404 )
181 + );
182 + }
183 +
184 + return $section;
185 +}
186 +
187 +/**
188 + * REST schema for one dashboard section, as returned by the sections route.
189 + *
190 + * Mirrored by the frontend's `sections.ts` and reused by WPCOM for Simple sites (see AGENTS.md).
191 + *
192 + * @since 0.2.0
193 + *
194 + * @return array The JSON schema for a dashboard section.
195 + */
196 +function get_dashboard_section_schema() {
197 + return array(
198 + '$schema' => 'http://json-schema.org/draft-04/schema#',
199 + 'title' => 'jetpack-premium-analytics-dashboard-section',
200 + 'type' => 'object',
201 + 'properties' => array(
202 + 'id' => array(
203 + 'description' => __( 'Namespaced section identifier.', 'jetpack-premium-analytics-pkg' ),
204 + 'type' => 'string',
205 + 'readonly' => true,
206 + ),
207 + 'slug' => array(
208 + 'description' => __( 'URL-facing section slug, derived from the identifier.', 'jetpack-premium-analytics-pkg' ),
209 + 'type' => 'string',
210 + 'readonly' => true,
211 + ),
212 + 'label' => array(
213 + 'description' => __( 'Translated display label, naming the section tab.', 'jetpack-premium-analytics-pkg' ),
214 + 'type' => 'string',
215 + 'readonly' => true,
216 + ),
217 + 'title' => array(
218 + 'description' => __( 'Translated section heading, distinct from the tab label. Null falls back to the label.', 'jetpack-premium-analytics-pkg' ),
219 + 'type' => array( 'string', 'null' ),
220 + 'readonly' => true,
221 + ),
222 + 'order' => array(
223 + 'description' => __( 'Sort order, ascending.', 'jetpack-premium-analytics-pkg' ),
224 + 'type' => 'integer',
225 + 'readonly' => true,
226 + ),
227 + 'date_filter' => array(
228 + 'description' => __( 'Which shape the section date filter takes: the rolling date range, or all time plus single years.', 'jetpack-premium-analytics-pkg' ),
229 + 'type' => 'string',
230 + 'enum' => Dashboard_Section::DATE_FILTERS,
231 + 'default' => Dashboard_Section::DATE_FILTER_RANGE,
232 + 'readonly' => true,
233 + ),
234 + 'date_filter_options' => array(
235 + 'description' => __( 'What the section date filter supports, and where it renders.', 'jetpack-premium-analytics-pkg' ),
236 + 'type' => 'object',
237 + 'properties' => array(
238 + 'with_date_comparison' => array(
239 + 'description' => __( 'Whether the section supports period-over-period comparison at all. When false, no widget in the section receives comparison parameters.', 'jetpack-premium-analytics-pkg' ),
240 + 'type' => 'boolean',
241 + 'default' => true,
242 + ),
243 + 'with_header_date_control' => array(
244 + 'description' => __( 'Whether the section header renders the date control. When false, the section widgets host their own.', 'jetpack-premium-analytics-pkg' ),
245 + 'type' => 'boolean',
246 + 'default' => true,
247 + ),
248 + ),
249 + 'readonly' => true,
250 + ),
251 + 'requires_sync' => array(
252 + 'description' => __( 'Whether the section\'s numbers stay incomplete until the analytics initial full sync has finished.', 'jetpack-premium-analytics-pkg' ),
253 + 'type' => 'boolean',
254 + 'default' => false,
255 + 'readonly' => true,
256 + ),
257 + 'default_layout' => array(
258 + 'description' => __( 'Bundled default widget layout.', 'jetpack-premium-analytics-pkg' ),
259 + 'type' => 'array',
260 + 'items' => array( 'type' => 'object' ),
261 + 'readonly' => true,
262 + ),
263 + ),
264 + );
265 +}
266 +
267 +/**
268 + * REST callback returning available dashboard sections.
269 + *
270 + * @param \WP_REST_Request $request REST request carrying the dashboard name.
271 + * @return \WP_REST_Response
272 + */
273 +function get_dashboard_sections_response( $request ) {
274 + $sections = array_map(
275 + static function ( Dashboard_Section $section ) {
276 + return $section->to_array();
277 + },
278 + get_available_dashboard_sections( $request['name'] )
279 + );
280 +
281 + return rest_ensure_response( $sections );
282 +}
283 +
284 +/**
285 + * REST callback returning a section's default layout.
286 + *
287 + * @param \WP_REST_Request $request REST request carrying dashboard and section identifiers.
288 + * @return \WP_REST_Response|\WP_Error
289 + */
290 +function get_dashboard_section_default_layout_response( $request ) {
291 + $section = get_available_dashboard_section_for_route( $request['name'], $request['section'] );
292 +
293 + if ( is_wp_error( $section ) ) {
294 + return $section;
295 + }
296 +
297 + return rest_ensure_response( $section->get_default_layout() );
298 +}
299 +
300 +/**
301 + * Registers dashboard section REST routes.
302 + *
303 + * @return void
304 + */
305 +function register_dashboard_sections_rest_routes() {
306 + register_rest_route(
307 + DASHBOARD_REST_NAMESPACE,
308 + '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections',
309 + array(
310 + array(
311 + // @phan-suppress-next-line PhanPluginMixedKeyNoKey -- register_rest_route()'s own signature mixes a numerically keyed endpoint list with a route-level `schema` key.
312 + 'methods' => \WP_REST_Server::READABLE,
313 + 'callback' => __NAMESPACE__ . '\\get_dashboard_sections_response',
314 + 'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
315 + 'args' => array(
316 + 'name' => array(
317 + 'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics-pkg' ),
318 + 'type' => 'string',
319 + ),
320 + ),
321 + ),
322 + 'schema' => __NAMESPACE__ . '\\get_dashboard_section_schema',
323 + )
324 + );
325 +
326 + register_rest_route(
327 + DASHBOARD_REST_NAMESPACE,
328 + '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections/(?P<section>' . get_dashboard_section_id_pattern() . ')/default-layout',
329 + array(
330 + 'methods' => \WP_REST_Server::READABLE,
331 + 'callback' => __NAMESPACE__ . '\\get_dashboard_section_default_layout_response',
332 + 'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
333 + 'args' => array(
334 + 'name' => array(
335 + 'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics-pkg' ),
336 + 'type' => 'string',
337 + ),
338 + 'section' => array(
339 + 'description' => __( 'Dashboard section identifier.', 'jetpack-premium-analytics-pkg' ),
340 + 'type' => 'string',
341 + ),
342 + ),
343 + )
344 + );
345 +}