Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.93% covered (warning)
82.93%
34 / 41
83.33% covered (warning)
83.33%
5 / 6
CRAP
n/a
0 / 0
Automattic\Jetpack\PremiumAnalytics\remove_dev_only_widget_types
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\filter_registrable_widget_types_by_environment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\filter_registrable_widget_types_by_availability
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\remove_plugin_gated_widget_types
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
Automattic\Jetpack\PremiumAnalytics\is_bookings_plugin_active
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\PremiumAnalytics\filter_registrable_widget_types_by_plugin
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Widget availability policy (consumer layer).
4 *
5 * Premium Analytics' policy over the neutral filters in widget-types.php:
6 * availability is the consumer's job, core only offers the hooks.
7 *
8 * Policies are hooked on the registry-time filter (a hard hide, which keeps
9 * every registry consumer correct without a filtered accessor): developer-only
10 * widget types are never registered in production, Simple-only types are only
11 * registered on WPCOM Simple, and commerce categories require their plugins.
12 *
13 * @package automattic/jetpack-premium-analytics
14 */
15
16namespace Automattic\Jetpack\PremiumAnalytics;
17
18require_once __DIR__ . '/widget-types.php';
19require_once __DIR__ . '/widget-type-support.php';
20
21/**
22 * Widget categories that are only meaningful with WooCommerce active.
23 */
24const WOOCOMMERCE_WIDGET_CATEGORIES = array( 'store', 'orders', 'coupons' );
25
26/**
27 * Widget categories that are only meaningful with WooCommerce Bookings active.
28 *
29 * Checked independently of WOOCOMMERCE_WIDGET_CATEGORIES: the Bookings
30 * extension cannot run without WooCommerce, so its presence implies both.
31 */
32const WOOCOMMERCE_BOOKINGS_WIDGET_CATEGORIES = array( 'bookings' );
33
34/**
35 * Removes developer-only candidates in production.
36 *
37 * Split from the hook callback so both branches are testable without touching
38 * the global environment.
39 *
40 * @param array  $widget_candidates Manifest candidates, each with a `name` and `category`.
41 * @param string $environment       Site environment type.
42 * @return array The candidates, minus developer-only types in production.
43 */
44function remove_dev_only_widget_types( $widget_candidates, $environment ) {
45    if ( 'production' !== $environment ) {
46        return $widget_candidates;
47    }
48
49    return array_values(
50        array_filter(
51            $widget_candidates,
52            static function ( $widget ) {
53                return 'developer' !== ( $widget['category'] ?? '' );
54            }
55        )
56    );
57}
58
59/**
60 * Registry-time callback: hides developer-only types in production.
61 *
62 * Defaults to `production`; a site opts in via `WP_ENVIRONMENT_TYPE`
63 * (`local`, `development`, `staging`).
64 *
65 * @param array $widget_candidates Manifest candidates.
66 * @return array The candidates, minus developer-only types in production.
67 */
68function filter_registrable_widget_types_by_environment( $widget_candidates ) {
69    return remove_dev_only_widget_types( $widget_candidates, wp_get_environment_type() );
70}
71
72add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_environment' );
73
74/**
75 * Applies shared type-level availability at registry time.
76 *
77 * @param array $widget_candidates Manifest candidates.
78 * @return array Filtered candidates.
79 */
80function filter_registrable_widget_types_by_availability( $widget_candidates ) {
81    return remove_unsupported_widget_items(
82        $widget_candidates,
83        'name',
84        get_widget_support_context()
85    );
86}
87
88add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_availability' );
89
90/**
91 * Removes candidates whose commerce category lacks its backing plugin.
92 *
93 * Split from the hook callback so the branches are testable without touching
94 * global plugin state.
95 *
96 * @param array $widget_candidates     Manifest candidates, each with a `category`.
97 * @param bool  $woocommerce_available Whether WooCommerce is available.
98 * @param bool  $bookings_available    Whether WooCommerce Bookings is available.
99 * @return array The candidates, minus commerce categories missing their plugin.
100 */
101function remove_plugin_gated_widget_types( $widget_candidates, $woocommerce_available, $bookings_available ) {
102    return array_values(
103        array_filter(
104            $widget_candidates,
105            static function ( $widget ) use ( $woocommerce_available, $bookings_available ) {
106                $category = $widget['category'] ?? '';
107
108                if ( ! $woocommerce_available && in_array( $category, WOOCOMMERCE_WIDGET_CATEGORIES, true ) ) {
109                    return false;
110                }
111
112                if ( ! $bookings_available && in_array( $category, WOOCOMMERCE_BOOKINGS_WIDGET_CATEGORIES, true ) ) {
113                    return false;
114                }
115
116                return true;
117            }
118        )
119    );
120}
121
122/**
123 * Whether the WooCommerce Bookings extension is active.
124 *
125 * Mirrors the detection in woocommerce-analytics' Bookings sync module;
126 * `is_plugin_active()` only exists in admin contexts, hence the guard.
127 *
128 * @return bool Whether WooCommerce Bookings was detected in the current request.
129 */
130function is_bookings_plugin_active() {
131    return class_exists( 'WC_Bookings' )
132        || ( function_exists( 'is_plugin_active' ) && \is_plugin_active( 'woocommerce-bookings/woocommerce-bookings.php' ) );
133}
134
135/**
136 * Registry-time callback: hides commerce widget categories without their plugin.
137 *
138 * WooCommerce availability is read through the store section's signal
139 * (dashboard-sections.php, including its availability filter) so section and
140 * widgets can't disagree — a consumer overriding the section filter gets
141 * matching widget types. Both dashboard entry points load
142 * dashboard-sections.php before the widget registry can hydrate, so the
143 * function is always defined by the time this callback runs.
144 *
145 * @param array $widget_candidates Manifest candidates.
146 * @return array The candidates, minus commerce categories missing their plugin.
147 */
148function filter_registrable_widget_types_by_plugin( $widget_candidates ) {
149    return remove_plugin_gated_widget_types(
150        $widget_candidates,
151        is_woocommerce_dashboard_section_available(),
152        is_bookings_plugin_active()
153    );
154}
155
156add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_plugin' );