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

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