PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
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-layout.php +101 -528 16.2 → 16.3 View file →
@@ -1,10 +1,12 @@
1 1 <?php
2 2 /**
3 - * Dashboard Layout: Premium Analytics server-side defaults.
3 + * Dashboard Layout: the default-layout primitives shared by the section API and its registrants.
4 4 *
5 - * Ships its own default layout rather than the core dashboard endpoint (Gutenberg-only, returns
6 - * core's widgets); the frontend reads the default from the section entity, so nothing is server-seeded.
5 + * A section declares its default layout when it registers, and
6 + * Dashboard_Section::get_default_layout() runs it through DASHBOARD_DEFAULT_LAYOUT_FILTER: the
7 + * package drops the widget types the site cannot serve there, and a plugin may add its own
8 + * instances to any section.
7 9 *
8 10 * @package automattic/jetpack-premium-analytics
9 11 */
10 12
@@ -9,10 +11,8 @@
9 11 */
10 12
11 13 namespace Automattic\Jetpack\PremiumAnalytics;
12 14
13 -require_once __DIR__ . '/dashboard-grammar.php';
14 -require_once __DIR__ . '/rest-namespace.php';
15 15 // Availability policy for default layout instances: defaults are read outside
16 16 // the widget registry bootstrap, so the policy must be loaded here explicitly.
17 17 require_once __DIR__ . '/widget-type-support.php';
18 18
@@ -18,134 +18,21 @@
18 18
19 19 /**
20 20 * Identifier of the Premium Analytics dashboard, formatted as `<plugin>_<page>`
21 21 * to match the underscore form produced by the wp-build pipeline. Used as the
22 - * `{name}` segment of the REST route.
22 + * `{name}` segment of the sections REST route.
23 23 */
24 24 const DASHBOARD_NAME = 'jetpack-premium-analytics_dashboard';
25 25
26 26 /**
27 - * Filter through which the default layout for a dashboard is resolved.
27 + * Filter through which a section's default layout is resolved. Documented where it
28 + * runs, in Dashboard_Section::get_default_layout().
28 29 */
29 30 const DASHBOARD_DEFAULT_LAYOUT_FILTER = 'jetpack_premium_analytics_dashboard_default_layout';
30 31
31 32 /**
32 - * Preference keys used by the dashboard route's tabbed sections.
33 - */
34 -const DASHBOARD_TRAFFIC_SECTION_ID = 'traffic';
35 -const DASHBOARD_INSIGHTS_SECTION_ID = 'insights';
36 -const DASHBOARD_SUBSCRIBERS_SECTION_ID = 'subscribers';
37 -const DASHBOARD_STORE_SECTION_ID = 'store';
38 -const DASHBOARD_ADS_SECTION_ID = 'ads';
39 -
40 -/**
41 - * Resolves the default layout registered for a dashboard.
33 + * Builds a widget instance for a section's default layout.
42 34 *
43 - * Returns a fresh evaluation of the filter chain each call, so callers always
44 - * see the current code default rather than a hydrated copy.
45 - *
46 - * @param string $dashboard_name Identifier of the dashboard or dashboard section.
47 - * @return array Array of widget instances (possibly empty).
48 - */
49 -function get_dashboard_default_layout_for( $dashboard_name ) {
50 - /**
51 - * Filters the default dashboard layout served to users who have not
52 - * customized theirs.
53 - *
54 - * Each entry should match the dashboard's widget instance shape: `uuid`,
55 - * `type`, optional `attributes`, optional `placement`.
56 - *
57 - * @param array $default_layout Default array of widget instances.
58 - * @param string $dashboard_name Identifier of the dashboard or dashboard
59 - * section receiving the default. Callbacks
60 - * targeting a specific default should switch
61 - * on this value.
62 - */
63 - $default = apply_filters( DASHBOARD_DEFAULT_LAYOUT_FILTER, array(), $dashboard_name );
64 -
65 - return remove_unsupported_widget_items(
66 - is_array( $default ) ? array_values( $default ) : array(),
67 - 'type',
68 - get_widget_support_context()
69 - );
70 -}
71 -
72 -/**
73 - * Returns availability gates for conditional dashboard tabs.
74 - *
75 - * The callbacks are used directly because the section registry may not be
76 - * initialized when this route runs.
77 - *
78 - * @since 0.3.0
79 - *
80 - * @return callable[] Resolved tab ID mapped to its availability callback.
81 - */
82 -function get_dashboard_default_layout_gates() {
83 - return array(
84 - DASHBOARD_STORE_SECTION_ID => array( Capabilities::class, 'current_user_can_view_store_reports' ),
85 - DASHBOARD_SUBSCRIBERS_SECTION_ID => __NAMESPACE__ . '\\is_subscribers_dashboard_section_available',
86 - DASHBOARD_ADS_SECTION_ID => __NAMESPACE__ . '\\is_ads_dashboard_section_available_to_current_user',
87 - );
88 -}
89 -
90 -/**
91 - * REST callback returning the default layout for the requested dashboard.
92 - *
93 - * Availability is checked after resolving the name because tabs can be
94 - * requested by alias or full section ID.
95 - *
96 - * @param \WP_REST_Request $request REST request carrying the dashboard name.
97 - * @return \WP_REST_Response|\WP_Error Response wrapping the default layout array.
98 - */
99 -function get_dashboard_default_layout_response( $request ) {
100 - $dashboard_name = $request['name'];
101 - $gates = get_dashboard_default_layout_gates();
102 - $section_id = get_dashboard_default_section_id_for( $dashboard_name );
103 -
104 - $out_of_preview = null !== $section_id
105 - && ! is_dashboard_section_in_preview_scope( DASHBOARD_NAME, $section_id );
106 -
107 - $gate_fails = null !== $section_id
108 - && isset( $gates[ $section_id ] )
109 - && ! call_user_func( $gates[ $section_id ] );
110 -
111 - if ( $out_of_preview || $gate_fails ) {
112 - return new \WP_Error(
113 - 'dashboard_section_unavailable',
114 - __( 'Dashboard section is not available.', 'jetpack-premium-analytics-pkg' ),
115 - array( 'status' => 404 )
116 - );
117 - }
118 -
119 - return rest_ensure_response( get_dashboard_default_layout_for( $dashboard_name ) );
120 -}
121 -
122 -/**
123 - * Registers the REST route that exposes per-dashboard default layouts.
124 - *
125 - * @return void
126 - */
127 -function register_dashboard_default_layout_route() {
128 - register_rest_route(
129 - DASHBOARD_REST_NAMESPACE,
130 - '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/default-layout',
131 - array(
132 - 'methods' => \WP_REST_Server::READABLE,
133 - 'callback' => __NAMESPACE__ . '\\get_dashboard_default_layout_response',
134 - 'permission_callback' => array( Capabilities::class, 'current_user_can_view_analytics' ),
135 - 'args' => array(
136 - 'name' => array(
137 - 'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics-pkg' ),
138 - 'type' => 'string',
139 - ),
140 - ),
141 - )
142 - );
143 -}
144 -
145 -/**
146 - * Builds a widget instance for bundled dashboard defaults.
147 - *
148 35 * @param string $uuid Widget instance UUID.
149 36 * @param string $type Widget type.
150 37 * @param int $order Widget placement order.
151 38 * @param int $width Widget placement width.
@@ -179,431 +66,117 @@
179 66 return $widget;
180 67 }
181 68
182 69 /**
183 - * Returns the bundled default widget layouts keyed by dashboard tab.
70 + * Drops the widget instances the site cannot serve from a section's default layout.
184 71 *
185 - * @return array Map of tab IDs to widget layout arrays.
72 + * A persisted layout keeps such an instance as a removable ghost widget; a default must not
73 + * seed one. Hooked late, after the callbacks that add instances, so it covers those too.
74 + *
75 + * @param array $layout Default widget instances.
76 + * @return array The layout minus the unsupported instances.
186 77 */
187 -function get_dashboard_default_section_layouts() {
188 - return array(
189 - DASHBOARD_TRAFFIC_SECTION_ID => array(
190 - // Rows fill the three-column grid in the prototype's order. Plan usage
191 - // is intentionally not a default; it stays available from the widget
192 - // picker.
193 - // Row 1: traffic chart.
194 - get_dashboard_default_widget_instance(
195 - 'default-traffic-chart-widget-instance',
196 - 'jpa/traffic-chart',
197 - 0,
198 - 3,
199 - 2
200 - ),
201 - // Row 2: most-viewed posts + referrers + devices.
202 - get_dashboard_default_widget_instance(
203 - 'default-stats-top-posts-widget-instance',
204 - 'jpa/stats-top-posts',
205 - 1,
206 - 1,
207 - 2
208 - ),
209 - get_dashboard_default_widget_instance(
210 - 'default-referrers-widget-instance',
211 - 'jpa/referrers',
212 - 2,
213 - 1,
214 - 2
215 - ),
216 - get_dashboard_default_widget_instance(
217 - 'default-devices-widget-instance',
218 - 'jpa/devices',
219 - 3,
220 - 1,
221 - 2
222 - ),
223 - // Row 3: locations map + top platforms.
224 - get_dashboard_default_widget_instance(
225 - 'default-locations-widget-instance',
226 - 'jpa/locations',
227 - 4,
228 - 2,
229 - 2
230 - ),
231 - get_dashboard_default_widget_instance(
232 - 'default-top-platforms-widget-instance',
233 - 'jpa/top-platforms',
234 - 5,
235 - 1,
236 - 2
237 - ),
238 - // Row 4: UTM insights + clicks + VideoPress (sites running VideoPress only).
239 - get_dashboard_default_widget_instance(
240 - 'default-utm-insights-widget-instance',
241 - 'jpa/utm-insights',
242 - 6,
243 - 1,
244 - 2,
245 - array(
246 - 'utmDimension' => 'utm_source,utm_medium',
247 - )
248 - ),
249 - get_dashboard_default_widget_instance(
250 - 'default-clicks-widget-instance',
251 - 'jpa/clicks',
252 - 7,
253 - 1,
254 - 2
255 - ),
256 - get_dashboard_default_widget_instance(
257 - 'default-videopress-widget-instance',
258 - 'jpa/videopress',
259 - 8,
260 - 1,
261 - 2
262 - ),
263 - // Row 5: authors + search terms + file downloads (Simple only).
264 - get_dashboard_default_widget_instance(
265 - 'default-authors-widget-instance',
266 - 'jpa/authors',
267 - 9,
268 - 1,
269 - 2
270 - ),
271 - get_dashboard_default_widget_instance(
272 - 'default-search-terms-widget-instance',
273 - 'jpa/search-terms',
274 - 10,
275 - 1,
276 - 2
277 - ),
278 - get_dashboard_default_widget_instance(
279 - 'default-file-downloads-widget-instance',
280 - 'jpa/file-downloads',
281 - 11,
282 - 1,
283 - 2
284 - ),
285 - ),
286 - DASHBOARD_INSIGHTS_SECTION_ID => array(
287 - // Follows the prototype's rows (WOOA7S-1786). Emails lives on the
288 - // Subscribers tab, so that row is absent.
289 - // Row 1: highlights banner.
290 - get_dashboard_default_widget_instance(
291 - 'default-annual-highlights-widget-instance',
292 - 'jpa/annual-highlights',
293 - 0,
294 - 4,
295 - 1
296 - ),
297 - // Row 2: the at-a-glance cards, as the design pairs them. All three are
298 - // two rows tall so their display-sized figures fit without scrolling: a
299 - // 1x1 tile is 200px, which the two-field cards overflow. WOOA7S-2009
300 - // settles the final widths.
301 - get_dashboard_default_widget_instance(
302 - 'default-all-time-stats-widget-instance',
303 - 'jpa/all-time-stats',
304 - 1,
305 - 2,
306 - 2,
307 - array(
308 - // The design shows three totals; the widget's own default adds
309 - // Comments, which the comment leaderboards below already cover.
310 - 'metrics' => array( 'views', 'visitors', 'posts' ),
311 - )
312 - ),
313 - get_dashboard_default_widget_instance(
314 - 'default-most-popular-day-widget-instance',
315 - 'jpa/most-popular-day',
316 - 2,
317 - 1,
318 - 2
319 - ),
320 - get_dashboard_default_widget_instance(
321 - 'default-most-popular-time-widget-instance',
322 - 'jpa/most-popular-time',
323 - 3,
324 - 1,
325 - 2
326 - ),
327 - // Row 3: posting-activity heatmap.
328 - get_dashboard_default_widget_instance(
329 - 'default-posting-activity-widget-instance',
330 - 'jpa/posting-activity',
331 - 4,
332 - 4,
333 - 1
334 - ),
335 - // Row 4: the two post spotlights.
336 - get_dashboard_default_widget_instance(
337 - 'default-latest-post-widget-instance',
338 - 'jpa/latest-post',
339 - 5,
340 - 2,
341 - 2
342 - ),
343 - get_dashboard_default_widget_instance(
344 - 'default-popular-post-widget-instance',
345 - 'jpa/popular-post',
346 - 6,
347 - 2,
348 - 2
349 - ),
350 - // Row 5: the period totals and the weekday and hour-of-day
351 - // distributions.
352 - get_dashboard_default_widget_instance(
353 - 'default-total-views-widget-instance',
354 - 'jpa/total-views',
355 - 7,
356 - 1,
357 - 1
358 - ),
359 - get_dashboard_default_widget_instance(
360 - 'default-total-visitors-widget-instance',
361 - 'jpa/total-visitors',
362 - 8,
363 - 1,
364 - 1
365 - ),
366 - get_dashboard_default_widget_instance(
367 - 'default-popular-days-widget-instance',
368 - 'jpa/popular-days',
369 - 9,
370 - 1,
371 - 1
372 - ),
373 - get_dashboard_default_widget_instance(
374 - 'default-popular-hours-widget-instance',
375 - 'jpa/popular-hours',
376 - 10,
377 - 1,
378 - 1
379 - ),
380 - // Row 6: daily views heatmap. Two rows tall, as in the prototype: cells are sized
381 - // from the tile's height, and only here do they fit each day's view count.
382 - get_dashboard_default_widget_instance(
383 - 'default-traffic-views-activity-widget-instance',
384 - 'jpa/traffic-views-activity',
385 - 11,
386 - 4,
387 - 2
388 - ),
389 - // Row 7: the comment leaderboards, shares, and tags.
390 - get_dashboard_default_widget_instance(
391 - 'default-most-commented-posts-widget-instance',
392 - 'jpa/most-commented-posts',
393 - 12,
394 - 1,
395 - 2
396 - ),
397 - get_dashboard_default_widget_instance(
398 - 'default-most-commented-authors-widget-instance',
399 - 'jpa/most-commented-authors',
400 - 13,
401 - 1,
402 - 2
403 - ),
404 - get_dashboard_default_widget_instance(
405 - 'default-shares-widget-instance',
406 - 'jpa/shares',
407 - 14,
408 - 1,
409 - 2
410 - ),
411 - get_dashboard_default_widget_instance(
412 - 'default-tags-widget-instance',
413 - 'jpa/tags',
414 - 15,
415 - 1,
416 - 2
417 - ),
418 - ),
419 - DASHBOARD_SUBSCRIBERS_SECTION_ID => array(
420 - // Subscriber highlights is intentionally not a default: the design
421 - // opens on the chart. It stays available from the widget picker.
422 - // Row 1: subscribers chart.
423 - get_dashboard_default_widget_instance(
424 - 'default-subscribers-chart-widget-instance',
425 - 'jpa/subscribers-chart',
426 - 0,
427 - 4,
428 - 2
429 - ),
430 - // Row 2: latest subscribers + latest emails sent.
431 - get_dashboard_default_widget_instance(
432 - 'default-subscribers-list-widget-instance',
433 - 'jpa/subscribers-list',
434 - 1,
435 - 2,
436 - 2
437 - ),
438 - get_dashboard_default_widget_instance(
439 - 'default-subscribers-emails-widget-instance',
440 - 'jpa/stats-emails',
441 - 2,
442 - 2,
443 - 2,
444 - array(
445 - 'metric' => 'opens',
446 - )
447 - ),
448 - ),
449 - DASHBOARD_STORE_SECTION_ID => array(
450 - get_dashboard_default_widget_instance(
451 - 'default-store-performance-widget-instance',
452 - 'jpa/store-performance',
453 - 0,
454 - 2,
455 - 1
456 - ),
457 - get_dashboard_default_widget_instance(
458 - 'default-total-sales-over-time-widget-instance',
459 - 'jpa/total-sales-over-time',
460 - 1,
461 - 1,
462 - 1
463 - ),
464 - get_dashboard_default_widget_instance(
465 - 'default-conversion-rate-widget-instance',
466 - 'jpa/conversion-rate',
467 - 2,
468 - 1,
469 - 1
470 - ),
471 - get_dashboard_default_widget_instance(
472 - 'default-orders-over-time-widget-instance',
473 - 'jpa/orders-over-time',
474 - 3,
475 - 1,
476 - 1
477 - ),
478 - get_dashboard_default_widget_instance(
479 - 'default-average-order-value-widget-instance',
480 - 'jpa/average-order-value',
481 - 4,
482 - 1,
483 - 1
484 - ),
485 - get_dashboard_default_widget_instance(
486 - 'default-top-performing-products-widget-instance',
487 - 'jpa/top-performing-products',
488 - 5,
489 - 1,
490 - 1
491 - ),
492 - get_dashboard_default_widget_instance(
493 - 'default-new-vs-returning-customer-widget-instance',
494 - 'jpa/new-vs-returning-customer',
495 - 6,
496 - 1,
497 - 1
498 - ),
499 - get_dashboard_default_widget_instance(
500 - 'default-payment-status-widget-instance',
501 - 'jpa/payment-status',
502 - 7,
503 - 1,
504 - 1
505 - ),
506 - get_dashboard_default_widget_instance(
507 - 'default-orders-fulfillment-widget-instance',
508 - 'jpa/orders-fulfillment',
509 - 8,
510 - 1,
511 - 1
512 - ),
513 - ),
514 - DASHBOARD_ADS_SECTION_ID => array(
515 - // Match the Calypso WordAds widget order.
516 - get_dashboard_default_widget_instance(
517 - 'default-wordads-highlights-widget-instance',
518 - 'jpa/wordads-highlights',
519 - 0,
520 - 4,
521 - 1
522 - ),
523 - get_dashboard_default_widget_instance(
524 - 'default-wordads-chart-tabs-widget-instance',
525 - 'jpa/wordads-chart-tabs',
526 - 1,
527 - 4,
528 - 2
529 - ),
530 - get_dashboard_default_widget_instance(
531 - 'default-wordads-earnings-history-widget-instance',
532 - 'jpa/wordads-earnings-history',
533 - 2,
534 - 4,
535 - 2
536 - ),
537 - get_dashboard_default_widget_instance(
538 - 'default-wordads-sponsored-content-history-widget-instance',
539 - 'jpa/wordads-sponsored-content-history',
540 - 3,
541 - 2,
542 - 2
543 - ),
544 - get_dashboard_default_widget_instance(
545 - 'default-wordads-adjustments-history-widget-instance',
546 - 'jpa/wordads-adjustments-history',
547 - 4,
548 - 2,
549 - 2
550 - ),
551 - ),
78 +function remove_unsupported_default_layout_items( $layout ) {
79 + $layout = remove_unsupported_widget_items(
80 + is_array( $layout ) ? $layout : array(),
81 + 'type',
82 + get_widget_support_context()
552 83 );
84 +
85 + return remove_unregistered_default_layout_items( $layout );
553 86 }
87 +add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\remove_unsupported_default_layout_items', 100 );
554 88
555 89 /**
556 - * Resolves a dashboard or section identifier to one of the bundled tab IDs.
90 + * The widget type registry once it can answer, or null before that.
557 91 *
558 - * @param string $dashboard_name Dashboard or section identifier.
559 - * @return string|null Bundled tab ID, or null when the identifier is unsupported.
92 + * It cannot answer before `init`, without the widget type API loaded, or with nothing registered,
93 + * which is a checkout without a build.
94 + *
95 + * @since 0.11.0
96 + *
97 + * @return Widget_Type_Registry|null
560 98 */
561 -function get_dashboard_default_section_id_for( $dashboard_name ) {
562 - $aliases = array(
563 - DASHBOARD_NAME => DASHBOARD_TRAFFIC_SECTION_ID,
564 - DASHBOARD_TRAFFIC_SECTION_ID => DASHBOARD_TRAFFIC_SECTION_ID,
565 - 'analytics/traffic' => DASHBOARD_TRAFFIC_SECTION_ID,
566 - DASHBOARD_INSIGHTS_SECTION_ID => DASHBOARD_INSIGHTS_SECTION_ID,
567 - 'analytics/insights' => DASHBOARD_INSIGHTS_SECTION_ID,
568 - DASHBOARD_SUBSCRIBERS_SECTION_ID => DASHBOARD_SUBSCRIBERS_SECTION_ID,
569 - 'analytics/subscribers' => DASHBOARD_SUBSCRIBERS_SECTION_ID,
570 - DASHBOARD_STORE_SECTION_ID => DASHBOARD_STORE_SECTION_ID,
571 - 'woocommerce/store' => DASHBOARD_STORE_SECTION_ID,
572 - DASHBOARD_ADS_SECTION_ID => DASHBOARD_ADS_SECTION_ID,
573 - 'analytics/ads' => DASHBOARD_ADS_SECTION_ID,
574 - );
99 +function get_answering_widget_type_registry() {
100 + if ( ! did_action( 'init' ) || ! function_exists( __NAMESPACE__ . '\\ensure_widget_registry_ready' ) ) {
101 + return null;
102 + }
575 103
576 - return $aliases[ $dashboard_name ] ?? null;
104 + ensure_widget_registry_ready();
105 + $registry = Widget_Type_Registry::get_instance();
106 +
107 + return $registry->get_all_registered() ? $registry : null;
577 108 }
578 109
579 110 /**
580 - * Seeds the bundled default layouts for the Premium Analytics dashboard tabs.
111 + * Renames the widget instances whose type is a former name of a registered widget type.
581 112 *
582 - * Only contributes to known Premium Analytics dashboard/tab aliases, leaving other dashboards
583 - * untouched so the filter stays reusable if more dashboards are added later.
113 + * Hooked before the unregistered-type check, so an instance a plugin still adds under an old
114 + * name survives it under the current one.
584 115 *
585 - * @param array $dashboard_layout Default layout from earlier callbacks.
586 - * @param string $dashboard_name Identifier of the dashboard receiving the
587 - * default.
588 - * @return array The layout extended with the bundled widget instances.
116 + * @since 0.11.0
117 + *
118 + * @param array $layout Default widget instances.
119 + * @return array The layout with current type names.
589 120 */
590 -function seed_default_dashboard_layout( $dashboard_layout, $dashboard_name = '' ) {
591 - $section_id = get_dashboard_default_section_id_for( $dashboard_name );
592 -
593 - if ( null === $section_id ) {
594 - return $dashboard_layout;
121 +function resolve_former_widget_types_in_default_layout( $layout ) {
122 + $registry = get_answering_widget_type_registry();
123 + if ( ! $registry || ! is_array( $layout ) ) {
124 + return $layout;
595 125 }
596 126
597 - $uuids = array_column( $dashboard_layout, 'uuid' );
598 - $layouts = get_dashboard_default_section_layouts();
127 + return array_map(
128 + static function ( $item ) use ( $registry ) {
129 + if ( is_array( $item ) && is_string( $item['type'] ?? null ) ) {
130 + $item['type'] = $registry->resolve_name( $item['type'] );
131 + }
132 + return $item;
133 + },
134 + $layout
135 + );
136 +}
137 +add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\resolve_former_widget_types_in_default_layout', 99 );
599 138
600 - foreach ( $layouts[ $section_id ] as $widget ) {
601 - if ( ! in_array( $widget['uuid'], $uuids, true ) ) {
602 - $dashboard_layout[] = $widget;
603 - $uuids[] = $widget['uuid'];
604 - }
139 +/**
140 + * Drops the widget instances whose type the widget type registry does not know.
141 + *
142 + * Only once the registry can answer: after `init`, with the widget type API loaded and at least
143 + * one type registered. Before that, or on a checkout without a build, the default stays as
144 + * declared rather than emptying itself.
145 + *
146 + * @since 0.9.0
147 + *
148 + * @param array $layout Default widget instances.
149 + * @return array The layout minus the instances of unregistered types.
150 + */
151 +function remove_unregistered_default_layout_items( $layout ) {
152 + $registry = get_answering_widget_type_registry();
153 + if ( ! $registry ) {
154 + return $layout;
605 155 }
156 + $registered = $registry->get_all_registered();
606 157
607 - return $dashboard_layout;
158 + return array_values(
159 + array_filter(
160 + $layout,
161 + static function ( $item ) use ( $registered ) {
162 + if ( ! is_array( $item ) ) {
163 + return true;
164 + }
165 + // A non-string type, say the Widget_Type object register_widget_type() returns, is an
166 + // unknown type, not a TypeError for the whole sections route.
167 + $type = $item['type'] ?? '';
168 + return is_string( $type ) && isset( $registered[ $type ] );
169 + }
170 + )
171 + );
608 172 }
609 -add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\seed_default_dashboard_layout', 10, 2 );
173 +
174 +/**
175 + * No-op kept for older copies of the package: they guard their include of this file on this
176 + * symbol and call it from boot_routes(), so a newer copy loading first must still define it.
177 + *
178 + * @since 0.8.0 Registers nothing; the route it registered is gone.
179 + *
180 + * @return void
181 + */
182 +function register_dashboard_default_layout_route() {}