Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.74% covered (success)
90.74%
49 / 54
100.00% covered (success)
100.00%
5 / 5
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
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
15if ( ! defined( 'ABSPATH' ) ) {
16    exit( 0 );
17}
18
19// Availability policy for default layout instances: defaults are read outside
20// the widget registry bootstrap, so the policy must be loaded here explicitly.
21require_once __DIR__ . '/widget-type-support.php';
22
23/**
24 * Identifier of the Premium Analytics dashboard, formatted as `<plugin>_<page>`
25 * to match the underscore form produced by the wp-build pipeline. Used as the
26 * `{name}` segment of the sections REST route.
27 */
28const DASHBOARD_NAME = 'jetpack-premium-analytics_dashboard';
29
30/**
31 * Filter through which a section's default layout is resolved. Documented where it
32 * runs, in Dashboard_Section::get_default_layout().
33 */
34const DASHBOARD_DEFAULT_LAYOUT_FILTER = 'jetpack_premium_analytics_dashboard_default_layout';
35
36/**
37 * Filter through which the widget types a section's inserter offers beyond its default layout
38 * are resolved. Documented where it runs, in Dashboard_Section::get_insertable_widget_types().
39 *
40 * @since 0.13.0
41 */
42const DASHBOARD_INSERTABLE_WIDGET_TYPES_FILTER = 'jetpack_premium_analytics_dashboard_insertable_widget_types';
43
44/**
45 * Builds a widget instance for a section's default layout.
46 *
47 * @param string            $uuid       Widget instance UUID.
48 * @param string            $type       Widget type.
49 * @param int               $order      Widget placement order.
50 * @param int|'full'|'fill' $width      Column span, 'full' to span the grid, or 'fill' for the rest of the row.
51 * @param int               $height     Widget placement height.
52 * @param array             $attributes Optional widget attributes.
53 * @return array Widget instance.
54 */
55function get_dashboard_default_widget_instance(
56    $uuid,
57    $type,
58    $order,
59    $width = 1,
60    $height = 1,
61    $attributes = array()
62) {
63    $widget = array(
64        'uuid' => $uuid,
65        'type' => $type,
66    );
67
68    if ( ! empty( $attributes ) ) {
69        $widget['attributes'] = $attributes;
70    }
71
72    $widget['placement'] = array(
73        'width'  => $width,
74        'height' => $height,
75        'order'  => $order,
76    );
77
78    return $widget;
79}
80
81/**
82 * Drops the widget instances the site cannot serve from a section's default layout.
83 *
84 * A persisted layout keeps such an instance as a removable ghost widget; a default must not
85 * seed one. Hooked late, after the callbacks that add instances, so it covers those too.
86 *
87 * @param array $layout Default widget instances.
88 * @return array The layout minus the unsupported instances.
89 */
90function remove_unsupported_default_layout_items( $layout ) {
91    $layout = remove_unsupported_widget_items(
92        is_array( $layout ) ? $layout : array(),
93        'type',
94        get_widget_support_context()
95    );
96
97    return remove_unregistered_default_layout_items( $layout );
98}
99add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\remove_unsupported_default_layout_items', 100 );
100
101/**
102 * The widget type registry once it can answer, or null before that.
103 *
104 * It cannot answer before `init`, without the widget type API loaded, or with nothing registered,
105 * which is a checkout without a build.
106 *
107 * @since 0.11.0
108 *
109 * @return Widget_Type_Registry|null
110 */
111function get_answering_widget_type_registry() {
112    if ( ! did_action( 'init' ) || ! function_exists( __NAMESPACE__ . '\\ensure_widget_registry_ready' ) ) {
113        return null;
114    }
115
116    ensure_widget_registry_ready();
117    $registry = Widget_Type_Registry::get_instance();
118
119    return $registry->get_all_registered() ? $registry : null;
120}
121
122/**
123 * Renames the widget instances whose type is a former name of a registered widget type.
124 *
125 * Hooked before the unregistered-type check, so an instance a plugin still adds under an old
126 * name survives it under the current one.
127 *
128 * @since 0.11.0
129 *
130 * @param array $layout Default widget instances.
131 * @return array The layout with current type names.
132 */
133function resolve_former_widget_types_in_default_layout( $layout ) {
134    $registry = get_answering_widget_type_registry();
135    if ( ! $registry || ! is_array( $layout ) ) {
136        return $layout;
137    }
138
139    return array_map(
140        static function ( $item ) use ( $registry ) {
141            if ( is_array( $item ) && is_string( $item['type'] ?? null ) ) {
142                $item['type'] = $registry->resolve_name( $item['type'] );
143            }
144            return $item;
145        },
146        $layout
147    );
148}
149add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\resolve_former_widget_types_in_default_layout', 99 );
150
151/**
152 * Drops the widget instances whose type the widget type registry does not know.
153 *
154 * Only once the registry can answer: after `init`, with the widget type API loaded and at least
155 * one type registered. Before that, or on a checkout without a build, the default stays as
156 * declared rather than emptying itself.
157 *
158 * @since 0.9.0
159 *
160 * @param array $layout Default widget instances.
161 * @return array The layout minus the instances of unregistered types.
162 */
163function remove_unregistered_default_layout_items( $layout ) {
164    $registry = get_answering_widget_type_registry();
165    if ( ! $registry ) {
166        return $layout;
167    }
168    $registered = $registry->get_all_registered();
169
170    return array_values(
171        array_filter(
172            $layout,
173            static function ( $item ) use ( $registered ) {
174                if ( ! is_array( $item ) ) {
175                    return true;
176                }
177                // A non-string type, say the Widget_Type object register_widget_type() returns, is an
178                // unknown type, not a TypeError for the whole sections route.
179                $type = $item['type'] ?? '';
180                return is_string( $type ) && isset( $registered[ $type ] );
181            }
182        )
183    );
184}