Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.34% covered (success)
94.34%
50 / 53
100.00% covered (success)
100.00%
6 / 6
CRAP
n/a
0 / 0
Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_widget_instance
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\remove_unsupported_default_layout_items
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\get_answering_widget_type_registry
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
Automattic\Jetpack\PremiumAnalytics\resolve_former_widget_types_in_default_layout
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
Automattic\Jetpack\PremiumAnalytics\remove_unregistered_default_layout_items
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
4
Automattic\Jetpack\PremiumAnalytics\register_dashboard_default_layout_route
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Dashboard Layout: the default-layout primitives shared by the section API and its registrants.
4 *
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.
9 *
10 * @package automattic/jetpack-premium-analytics
11 */
12
13namespace Automattic\Jetpack\PremiumAnalytics;
14
15// Availability policy for default layout instances: defaults are read outside
16// the widget registry bootstrap, so the policy must be loaded here explicitly.
17require_once __DIR__ . '/widget-type-support.php';
18
19/**
20 * Identifier of the Premium Analytics dashboard, formatted as `<plugin>_<page>`
21 * to match the underscore form produced by the wp-build pipeline. Used as the
22 * `{name}` segment of the sections REST route.
23 */
24const DASHBOARD_NAME = 'jetpack-premium-analytics_dashboard';
25
26/**
27 * Filter through which a section's default layout is resolved. Documented where it
28 * runs, in Dashboard_Section::get_default_layout().
29 */
30const DASHBOARD_DEFAULT_LAYOUT_FILTER = 'jetpack_premium_analytics_dashboard_default_layout';
31
32/**
33 * Builds a widget instance for a section's default layout.
34 *
35 * @param string     $uuid       Widget instance UUID.
36 * @param string     $type       Widget type.
37 * @param int        $order      Widget placement order.
38 * @param int|'full' $width      Column span, or 'full' to span the grid.
39 * @param int        $height     Widget placement height.
40 * @param array      $attributes Optional widget attributes.
41 * @return array Widget instance.
42 */
43function get_dashboard_default_widget_instance(
44    $uuid,
45    $type,
46    $order,
47    $width = 1,
48    $height = 1,
49    $attributes = array()
50) {
51    $widget = array(
52        'uuid' => $uuid,
53        'type' => $type,
54    );
55
56    if ( ! empty( $attributes ) ) {
57        $widget['attributes'] = $attributes;
58    }
59
60    $widget['placement'] = array(
61        'width'  => $width,
62        'height' => $height,
63        'order'  => $order,
64    );
65
66    return $widget;
67}
68
69/**
70 * Drops the widget instances the site cannot serve from a section's default layout.
71 *
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.
77 */
78function 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()
83    );
84
85    return remove_unregistered_default_layout_items( $layout );
86}
87add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\remove_unsupported_default_layout_items', 100 );
88
89/**
90 * The widget type registry once it can answer, or null before that.
91 *
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
98 */
99function get_answering_widget_type_registry() {
100    if ( ! did_action( 'init' ) || ! function_exists( __NAMESPACE__ . '\\ensure_widget_registry_ready' ) ) {
101        return null;
102    }
103
104    ensure_widget_registry_ready();
105    $registry = Widget_Type_Registry::get_instance();
106
107    return $registry->get_all_registered() ? $registry : null;
108}
109
110/**
111 * Renames the widget instances whose type is a former name of a registered widget type.
112 *
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.
115 *
116 * @since 0.11.0
117 *
118 * @param array $layout Default widget instances.
119 * @return array The layout with current type names.
120 */
121function resolve_former_widget_types_in_default_layout( $layout ) {
122    $registry = get_answering_widget_type_registry();
123    if ( ! $registry || ! is_array( $layout ) ) {
124        return $layout;
125    }
126
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}
137add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\resolve_former_widget_types_in_default_layout', 99 );
138
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 */
151function remove_unregistered_default_layout_items( $layout ) {
152    $registry = get_answering_widget_type_registry();
153    if ( ! $registry ) {
154        return $layout;
155    }
156    $registered = $registry->get_all_registered();
157
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    );
172}
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 */
182function register_dashboard_default_layout_route() {}