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 * Filter through which the widget types a section's inserter offers beyond its default layout
34 * are resolved. Documented where it runs, in Dashboard_Section::get_insertable_widget_types().
35 *
36 * @since 0.13.0
37 */
38const DASHBOARD_INSERTABLE_WIDGET_TYPES_FILTER = 'jetpack_premium_analytics_dashboard_insertable_widget_types';
39
40/**
41 * Builds a widget instance for a section's default layout.
42 *
43 * @param string     $uuid       Widget instance UUID.
44 * @param string     $type       Widget type.
45 * @param int        $order      Widget placement order.
46 * @param int|'full' $width      Column span, or 'full' to span the grid.
47 * @param int        $height     Widget placement height.
48 * @param array      $attributes Optional widget attributes.
49 * @return array Widget instance.
50 */
51function get_dashboard_default_widget_instance(
52    $uuid,
53    $type,
54    $order,
55    $width = 1,
56    $height = 1,
57    $attributes = array()
58) {
59    $widget = array(
60        'uuid' => $uuid,
61        'type' => $type,
62    );
63
64    if ( ! empty( $attributes ) ) {
65        $widget['attributes'] = $attributes;
66    }
67
68    $widget['placement'] = array(
69        'width'  => $width,
70        'height' => $height,
71        'order'  => $order,
72    );
73
74    return $widget;
75}
76
77/**
78 * Drops the widget instances the site cannot serve from a section's default layout.
79 *
80 * A persisted layout keeps such an instance as a removable ghost widget; a default must not
81 * seed one. Hooked late, after the callbacks that add instances, so it covers those too.
82 *
83 * @param array $layout Default widget instances.
84 * @return array The layout minus the unsupported instances.
85 */
86function remove_unsupported_default_layout_items( $layout ) {
87    $layout = remove_unsupported_widget_items(
88        is_array( $layout ) ? $layout : array(),
89        'type',
90        get_widget_support_context()
91    );
92
93    return remove_unregistered_default_layout_items( $layout );
94}
95add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\remove_unsupported_default_layout_items', 100 );
96
97/**
98 * The widget type registry once it can answer, or null before that.
99 *
100 * It cannot answer before `init`, without the widget type API loaded, or with nothing registered,
101 * which is a checkout without a build.
102 *
103 * @since 0.11.0
104 *
105 * @return Widget_Type_Registry|null
106 */
107function get_answering_widget_type_registry() {
108    if ( ! did_action( 'init' ) || ! function_exists( __NAMESPACE__ . '\\ensure_widget_registry_ready' ) ) {
109        return null;
110    }
111
112    ensure_widget_registry_ready();
113    $registry = Widget_Type_Registry::get_instance();
114
115    return $registry->get_all_registered() ? $registry : null;
116}
117
118/**
119 * Renames the widget instances whose type is a former name of a registered widget type.
120 *
121 * Hooked before the unregistered-type check, so an instance a plugin still adds under an old
122 * name survives it under the current one.
123 *
124 * @since 0.11.0
125 *
126 * @param array $layout Default widget instances.
127 * @return array The layout with current type names.
128 */
129function resolve_former_widget_types_in_default_layout( $layout ) {
130    $registry = get_answering_widget_type_registry();
131    if ( ! $registry || ! is_array( $layout ) ) {
132        return $layout;
133    }
134
135    return array_map(
136        static function ( $item ) use ( $registry ) {
137            if ( is_array( $item ) && is_string( $item['type'] ?? null ) ) {
138                $item['type'] = $registry->resolve_name( $item['type'] );
139            }
140            return $item;
141        },
142        $layout
143    );
144}
145add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\resolve_former_widget_types_in_default_layout', 99 );
146
147/**
148 * Drops the widget instances whose type the widget type registry does not know.
149 *
150 * Only once the registry can answer: after `init`, with the widget type API loaded and at least
151 * one type registered. Before that, or on a checkout without a build, the default stays as
152 * declared rather than emptying itself.
153 *
154 * @since 0.9.0
155 *
156 * @param array $layout Default widget instances.
157 * @return array The layout minus the instances of unregistered types.
158 */
159function remove_unregistered_default_layout_items( $layout ) {
160    $registry = get_answering_widget_type_registry();
161    if ( ! $registry ) {
162        return $layout;
163    }
164    $registered = $registry->get_all_registered();
165
166    return array_values(
167        array_filter(
168            $layout,
169            static function ( $item ) use ( $registered ) {
170                if ( ! is_array( $item ) ) {
171                    return true;
172                }
173                // A non-string type, say the Widget_Type object register_widget_type() returns, is an
174                // unknown type, not a TypeError for the whole sections route.
175                $type = $item['type'] ?? '';
176                return is_string( $type ) && isset( $registered[ $type ] );
177            }
178        )
179    );
180}
181
182/**
183 * No-op kept for older copies of the package: they guard their include of this file on this
184 * symbol and call it from boot_routes(), so a newer copy loading first must still define it.
185 *
186 * @since 0.8.0 Registers nothing; the route it registered is gone.
187 *
188 * @return void
189 */
190function register_dashboard_default_layout_route() {}