PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.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 13.7.2 All 507 releases
jetpack / jetpack_vendor / automattic / jetpack-premium-analytics / src / dashboard-sections.php

dashboard-sections.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.3, at jetpack_vendor/automattic/jetpack-premium-analytics/src/dashboard-sections.php

368 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Dashboard Sections API: the registry helpers, the preview scope, 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 /**
20 * Filter through which the preview's section scope is resolved.
21 */
22 const DASHBOARD_PREVIEW_SCOPE_FILTER = 'jetpack_premium_analytics_dashboard_preview_scope';
23
24 /**
25 * Section slugs the customer preview exposes as tabs. A section still rolling out to some
26 * sites is opened through the filter instead. Widget types are registered independently of
27 * this, as they are of the per-section availability checks.
28 */
29 const PREVIEW_SECTIONS = array( 'traffic', 'insights' );
30
31 /**
32 * Registers a dashboard section.
33 *
34 * @param string $dashboard_name Dashboard identifier.
35 * @param string $id Section identifier.
36 * @param array $args Optional. Section arguments.
37 * @return Dashboard_Section|false The registered section on success, or false on failure.
38 */
39 function register_dashboard_section( $dashboard_name, $id, $args = array() ) {
40 return Dashboard_Section_Registry::get_instance()->register( $dashboard_name, $id, $args );
41 }
42
43 /**
44 * Retrieves a registered dashboard section.
45 *
46 * @param string $dashboard_name Dashboard identifier.
47 * @param string $id Section identifier.
48 * @return Dashboard_Section|null The registered section, or null when absent.
49 */
50 function get_registered_dashboard_section( $dashboard_name, $id ) {
51 return Dashboard_Section_Registry::get_instance()->get_registered( $dashboard_name, $id );
52 }
53
54 /**
55 * Retrieves available dashboard sections.
56 *
57 * @param string $dashboard_name Dashboard identifier.
58 * @return Dashboard_Section[] Ordered list of available sections.
59 */
60 function get_available_dashboard_sections( $dashboard_name ) {
61 return Dashboard_Section_Registry::get_instance()->get_available_sections( $dashboard_name );
62 }
63
64 /**
65 * Whether the dashboard is running as the customer-facing preview.
66 *
67 * The site's own opt-in means the preview. Anything else that switches the dashboard on, the
68 * WordPress.com blog sticker or the `jetpack_premium_analytics_enabled` filter, means us.
69 *
70 * @since 0.6.0
71 *
72 * @return bool
73 */
74 function is_dashboard_preview_scoped() {
75 return (bool) get_option( Enablement_Setting::ENABLED_OPTION );
76 }
77
78 /**
79 * Whether the preview exposes a dashboard section.
80 *
81 * @since 0.6.0
82 *
83 * @param string $dashboard_name Dashboard identifier. Only this package's own dashboard is scoped.
84 * @param string $slug URL-facing section slug.
85 * @return bool
86 */
87 function is_dashboard_section_in_preview_scope( $dashboard_name, $slug ) {
88 $in_scope = DASHBOARD_NAME !== $dashboard_name
89 || ! is_dashboard_preview_scoped()
90 || in_array( $slug, PREVIEW_SECTIONS, true );
91
92 /**
93 * Filters whether the preview exposes a dashboard section.
94 *
95 * `__return_true` restores the whole dashboard, which is how a development or test site
96 * sees every tab.
97 *
98 * @since 0.6.0
99 *
100 * @param bool $in_scope Whether the preview exposes the section.
101 * @param string $slug URL-facing section slug.
102 * @param string $dashboard_name Dashboard the section belongs to.
103 */
104 return (bool) apply_filters( DASHBOARD_PREVIEW_SCOPE_FILTER, $in_scope, $slug, $dashboard_name );
105 }
106
107 /**
108 * Slugs of the tabs the dashboard exposes, for the client's report routes.
109 *
110 * Reads the same sections the tab list does, so a report cannot outlive the tab it sits
111 * behind. Null, never `array()`, before the registry is hydrated: an empty array is a
112 * published scope that exposes nothing.
113 *
114 * @since 0.6.0
115 *
116 * @return string[]|null
117 */
118 function get_dashboard_preview_scope_sections() {
119 $registry = Dashboard_Section_Registry::get_instance();
120
121 if ( empty( $registry->get_all_registered( DASHBOARD_NAME ) ) ) {
122 return null;
123 }
124
125 return array_map(
126 static function ( Dashboard_Section $section ) {
127 return $section->slug;
128 },
129 $registry->get_available_sections( DASHBOARD_NAME )
130 );
131 }
132
133 /**
134 * Configures the preview scope script data.
135 *
136 * @since 0.6.0
137 *
138 * @return void
139 */
140 function configure_dashboard_preview_scope() {
141 add_filter( 'jetpack_admin_js_script_data', __NAMESPACE__ . '\\inject_dashboard_preview_scope_script_data', 20 );
142 }
143
144 /**
145 * Injects the preview's section scope into JetpackScriptData.
146 *
147 * The same list travels over REST for the tab bar, but a report route reads no REST before
148 * choosing its redirect, so it reads the scope from boot data instead.
149 *
150 * @since 0.6.0
151 *
152 * @param array $data The script data passed by the assets package.
153 * @return array
154 */
155 function inject_dashboard_preview_scope_script_data( array $data ): array {
156 $sections = get_dashboard_preview_scope_sections();
157
158 if ( null === $sections ) {
159 return $data;
160 }
161
162 if ( ! isset( $data['premium_analytics'] ) || ! is_array( $data['premium_analytics'] ) ) {
163 $data['premium_analytics'] = array();
164 }
165
166 $data['premium_analytics']['preview_sections'] = $sections;
167
168 return $data;
169 }
170
171 /**
172 * Whether the current user can access dashboard section routes.
173 *
174 * @return bool
175 */
176 function check_dashboard_sections_permission() {
177 return Capabilities::current_user_can_view_analytics();
178 }
179
180 /**
181 * Resolves a route section, including availability checks.
182 *
183 * @param string $dashboard_name Dashboard identifier.
184 * @param string $section_id Section identifier.
185 * @return Dashboard_Section|\WP_Error Registered available section, or error.
186 */
187 function get_available_dashboard_section_for_route( $dashboard_name, $section_id ) {
188 $section = get_registered_dashboard_section( $dashboard_name, $section_id );
189
190 if ( ! $section ) {
191 return new \WP_Error(
192 'dashboard_section_not_found',
193 __( 'Dashboard section not found.', 'jetpack-premium-analytics-pkg' ),
194 array( 'status' => 404 )
195 );
196 }
197
198 if ( ! $section->is_available() ) {
199 return new \WP_Error(
200 'dashboard_section_unavailable',
201 __( 'Dashboard section is not available.', 'jetpack-premium-analytics-pkg' ),
202 array( 'status' => 404 )
203 );
204 }
205
206 return $section;
207 }
208
209 /**
210 * REST schema for one dashboard section, as returned by the sections route.
211 *
212 * Mirrored by the frontend's `sections.ts` and reused by WPCOM for Simple sites (see AGENTS.md).
213 *
214 * @since 0.2.0
215 *
216 * @return array The JSON schema for a dashboard section.
217 */
218 function get_dashboard_section_schema() {
219 return array(
220 '$schema' => 'http://json-schema.org/draft-04/schema#',
221 'title' => 'jetpack-premium-analytics-dashboard-section',
222 'type' => 'object',
223 'properties' => array(
224 'id' => array(
225 'description' => __( 'Namespaced section identifier.', 'jetpack-premium-analytics-pkg' ),
226 'type' => 'string',
227 'readonly' => true,
228 ),
229 'slug' => array(
230 'description' => __( 'URL-facing section slug, derived from the identifier.', 'jetpack-premium-analytics-pkg' ),
231 'type' => 'string',
232 'readonly' => true,
233 ),
234 'label' => array(
235 'description' => __( 'Translated display label, naming the section tab.', 'jetpack-premium-analytics-pkg' ),
236 'type' => 'string',
237 'readonly' => true,
238 ),
239 'title' => array(
240 'description' => __( 'Translated section heading, distinct from the tab label. Null falls back to the label.', 'jetpack-premium-analytics-pkg' ),
241 'type' => array( 'string', 'null' ),
242 'readonly' => true,
243 ),
244 'order' => array(
245 'description' => __( 'Sort order, ascending.', 'jetpack-premium-analytics-pkg' ),
246 'type' => 'integer',
247 'readonly' => true,
248 ),
249 'date_filter' => array(
250 'description' => __( 'Which shape the section date filter takes: the rolling date range, or all time plus single years.', 'jetpack-premium-analytics-pkg' ),
251 'type' => 'string',
252 'enum' => Dashboard_Section::DATE_FILTERS,
253 'default' => Dashboard_Section::DATE_FILTER_RANGE,
254 'readonly' => true,
255 ),
256 'date_filter_options' => array(
257 'description' => __( 'What the section date filter supports, and where it renders.', 'jetpack-premium-analytics-pkg' ),
258 'type' => 'object',
259 'properties' => array(
260 'with_date_comparison' => array(
261 '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' ),
262 'type' => 'boolean',
263 'default' => true,
264 ),
265 'with_header_date_control' => array(
266 'description' => __( 'Whether the section header renders the date control. When false, the section widgets host their own.', 'jetpack-premium-analytics-pkg' ),
267 'type' => 'boolean',
268 'default' => true,
269 ),
270 ),
271 'readonly' => true,
272 ),
273 'requires_sync' => array(
274 'description' => __( 'Whether the section\'s numbers stay incomplete until the analytics initial full sync has finished.', 'jetpack-premium-analytics-pkg' ),
275 'type' => 'boolean',
276 'default' => false,
277 'readonly' => true,
278 ),
279 'default_layout' => array(
280 'description' => __( 'Bundled default widget layout.', 'jetpack-premium-analytics-pkg' ),
281 'type' => 'array',
282 'items' => array( 'type' => 'object' ),
283 'readonly' => true,
284 ),
285 ),
286 );
287 }
288
289 /**
290 * REST callback returning available dashboard sections.
291 *
292 * @param \WP_REST_Request $request REST request carrying the dashboard name.
293 * @return \WP_REST_Response
294 */
295 function get_dashboard_sections_response( $request ) {
296 $sections = array_map(
297 static function ( Dashboard_Section $section ) {
298 return $section->to_array();
299 },
300 get_available_dashboard_sections( $request['name'] )
301 );
302
303 return rest_ensure_response( $sections );
304 }
305
306 /**
307 * REST callback returning a section's default layout.
308 *
309 * @param \WP_REST_Request $request REST request carrying dashboard and section identifiers.
310 * @return \WP_REST_Response|\WP_Error
311 */
312 function get_dashboard_section_default_layout_response( $request ) {
313 $section = get_available_dashboard_section_for_route( $request['name'], $request['section'] );
314
315 if ( is_wp_error( $section ) ) {
316 return $section;
317 }
318
319 return rest_ensure_response( $section->get_default_layout() );
320 }
321
322 /**
323 * Registers dashboard section REST routes.
324 *
325 * @return void
326 */
327 function register_dashboard_sections_rest_routes() {
328 register_rest_route(
329 DASHBOARD_REST_NAMESPACE,
330 '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections',
331 array(
332 array(
333 // @phan-suppress-next-line PhanPluginMixedKeyNoKey -- register_rest_route()'s own signature mixes a numerically keyed endpoint list with a route-level `schema` key.
334 'methods' => \WP_REST_Server::READABLE,
335 'callback' => __NAMESPACE__ . '\\get_dashboard_sections_response',
336 'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
337 'args' => array(
338 'name' => array(
339 'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics-pkg' ),
340 'type' => 'string',
341 ),
342 ),
343 ),
344 'schema' => __NAMESPACE__ . '\\get_dashboard_section_schema',
345 )
346 );
347
348 register_rest_route(
349 DASHBOARD_REST_NAMESPACE,
350 '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/sections/(?P<section>' . get_dashboard_section_id_pattern() . ')/default-layout',
351 array(
352 'methods' => \WP_REST_Server::READABLE,
353 'callback' => __NAMESPACE__ . '\\get_dashboard_section_default_layout_response',
354 'permission_callback' => __NAMESPACE__ . '\\check_dashboard_sections_permission',
355 'args' => array(
356 'name' => array(
357 'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics-pkg' ),
358 'type' => 'string',
359 ),
360 'section' => array(
361 'description' => __( 'Dashboard section identifier.', 'jetpack-premium-analytics-pkg' ),
362 'type' => 'string',
363 ),
364 ),
365 )
366 );
367 }
368