| 1 |
<?php |
| 2 |
/** |
| 3 |
* Widget availability policy (consumer layer): hides developer-only, Simple-only, and |
| 4 |
* plugin-gated widget types at registry time — a hard hide, so every consumer of the |
| 5 |
* registry sees the same set, over the neutral hooks in widget-types.php. |
| 6 |
* |
| 7 |
* @package automattic/jetpack-premium-analytics |
| 8 |
*/ |
| 9 |
|
| 10 |
namespace Automattic\Jetpack\PremiumAnalytics; |
| 11 |
|
| 12 |
require_once __DIR__ . '/widget-types.php'; |
| 13 |
require_once __DIR__ . '/widget-type-support.php'; |
| 14 |
|
| 15 |
/** |
| 16 |
* Widget categories that are only meaningful with WooCommerce active. |
| 17 |
*/ |
| 18 |
const WOOCOMMERCE_WIDGET_CATEGORIES = array( 'store', 'orders', 'coupons' ); |
| 19 |
|
| 20 |
/** |
| 21 |
* Widget categories that are only meaningful with WooCommerce Bookings active. |
| 22 |
* |
| 23 |
* Checked independently of WOOCOMMERCE_WIDGET_CATEGORIES: the Bookings |
| 24 |
* extension cannot run without WooCommerce, so its presence implies both. |
| 25 |
*/ |
| 26 |
const WOOCOMMERCE_BOOKINGS_WIDGET_CATEGORIES = array( 'bookings' ); |
| 27 |
|
| 28 |
/** |
| 29 |
* Widget categories whose data counts as a store report — by data source, not subject |
| 30 |
* matter: each reaches WPCOM via the proxy's `analytics` prefix (gated on |
| 31 |
* `view_woocommerce_reports`), including `visitors`, which reads `sessions/…` from it. |
| 32 |
*/ |
| 33 |
const STORE_REPORT_WIDGET_CATEGORIES = array( 'store', 'orders', 'coupons', 'bookings', 'visitors' ); |
| 34 |
|
| 35 |
/** |
| 36 |
* Removes developer-only candidates in production. |
| 37 |
* |
| 38 |
* Split from the hook callback so both branches are testable without touching |
| 39 |
* the global environment. |
| 40 |
* |
| 41 |
* @param array $widget_candidates Manifest candidates, each with a `name` and `category`. |
| 42 |
* @param string $environment Site environment type. |
| 43 |
* @return array The candidates, minus developer-only types in production. |
| 44 |
*/ |
| 45 |
function remove_dev_only_widget_types( $widget_candidates, $environment ) { |
| 46 |
if ( 'production' !== $environment ) { |
| 47 |
return $widget_candidates; |
| 48 |
} |
| 49 |
|
| 50 |
return array_values( |
| 51 |
array_filter( |
| 52 |
$widget_candidates, |
| 53 |
static function ( $widget ) { |
| 54 |
return 'developer' !== ( $widget['category'] ?? '' ); |
| 55 |
} |
| 56 |
) |
| 57 |
); |
| 58 |
} |
| 59 |
|
| 60 |
/** |
| 61 |
* Registry-time callback: hides developer-only types in production. |
| 62 |
* |
| 63 |
* Defaults to `production`; a site opts in via `WP_ENVIRONMENT_TYPE` |
| 64 |
* (`local`, `development`, `staging`). |
| 65 |
* |
| 66 |
* @param array $widget_candidates Manifest candidates. |
| 67 |
* @return array The candidates, minus developer-only types in production. |
| 68 |
*/ |
| 69 |
function filter_registrable_widget_types_by_environment( $widget_candidates ) { |
| 70 |
return remove_dev_only_widget_types( $widget_candidates, wp_get_environment_type() ); |
| 71 |
} |
| 72 |
|
| 73 |
add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_environment' ); |
| 74 |
|
| 75 |
/** |
| 76 |
* Applies shared type-level availability at registry time. |
| 77 |
* |
| 78 |
* @param array $widget_candidates Manifest candidates. |
| 79 |
* @return array Filtered candidates. |
| 80 |
*/ |
| 81 |
function filter_registrable_widget_types_by_availability( $widget_candidates ) { |
| 82 |
return remove_unsupported_widget_items( |
| 83 |
$widget_candidates, |
| 84 |
'name', |
| 85 |
get_widget_support_context() |
| 86 |
); |
| 87 |
} |
| 88 |
|
| 89 |
add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_availability' ); |
| 90 |
|
| 91 |
/** |
| 92 |
* Removes candidates whose commerce category lacks its backing plugin. |
| 93 |
* |
| 94 |
* Split from the hook callback so the branches are testable without touching |
| 95 |
* global plugin state. |
| 96 |
* |
| 97 |
* @param array $widget_candidates Manifest candidates, each with a `category`. |
| 98 |
* @param bool $woocommerce_available Whether WooCommerce is available. |
| 99 |
* @param bool $bookings_available Whether WooCommerce Bookings is available. |
| 100 |
* @return array The candidates, minus commerce categories missing their plugin. |
| 101 |
*/ |
| 102 |
function remove_plugin_gated_widget_types( $widget_candidates, $woocommerce_available, $bookings_available ) { |
| 103 |
return array_values( |
| 104 |
array_filter( |
| 105 |
$widget_candidates, |
| 106 |
static function ( $widget ) use ( $woocommerce_available, $bookings_available ) { |
| 107 |
$category = $widget['category'] ?? ''; |
| 108 |
|
| 109 |
if ( ! $woocommerce_available && in_array( $category, WOOCOMMERCE_WIDGET_CATEGORIES, true ) ) { |
| 110 |
return false; |
| 111 |
} |
| 112 |
|
| 113 |
if ( ! $bookings_available && in_array( $category, WOOCOMMERCE_BOOKINGS_WIDGET_CATEGORIES, true ) ) { |
| 114 |
return false; |
| 115 |
} |
| 116 |
|
| 117 |
return true; |
| 118 |
} |
| 119 |
) |
| 120 |
); |
| 121 |
} |
| 122 |
|
| 123 |
/** |
| 124 |
* Whether the WooCommerce Bookings extension is active. |
| 125 |
* |
| 126 |
* Mirrors the detection in woocommerce-analytics' Bookings sync module; |
| 127 |
* `is_plugin_active()` only exists in admin contexts, hence the guard. |
| 128 |
* |
| 129 |
* @return bool Whether WooCommerce Bookings was detected in the current request. |
| 130 |
*/ |
| 131 |
function is_bookings_plugin_active() { |
| 132 |
return class_exists( 'WC_Bookings' ) |
| 133 |
|| ( function_exists( 'is_plugin_active' ) && \is_plugin_active( 'woocommerce-bookings/woocommerce-bookings.php' ) ); |
| 134 |
} |
| 135 |
|
| 136 |
/** |
| 137 |
* Registry-time callback: hides commerce categories without their plugin, reading |
| 138 |
* WooCommerce availability through the store section's signal so section and widgets |
| 139 |
* agree; both entry points load dashboard-sections.php before the registry hydrates. |
| 140 |
* |
| 141 |
* @param array $widget_candidates Manifest candidates. |
| 142 |
* @return array The candidates, minus commerce categories missing their plugin. |
| 143 |
*/ |
| 144 |
function filter_registrable_widget_types_by_plugin( $widget_candidates ) { |
| 145 |
return remove_plugin_gated_widget_types( |
| 146 |
$widget_candidates, |
| 147 |
is_woocommerce_dashboard_section_available(), |
| 148 |
is_bookings_plugin_active() |
| 149 |
); |
| 150 |
} |
| 151 |
|
| 152 |
add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_plugin' ); |
| 153 |
|
| 154 |
// Subscriber widgets stay registered even when their section is hidden: their data |
| 155 |
// doesn't depend on the local module, and unregistering would break instances placed elsewhere. |
| 156 |
|
| 157 |
/** |
| 158 |
* Removes candidates the reader could not load data for anyway. |
| 159 |
* |
| 160 |
* Split from the hook callback so both branches are testable without a user. |
| 161 |
* |
| 162 |
* @since 0.1.0 |
| 163 |
* |
| 164 |
* @param array $widget_candidates Manifest candidates, each with a `category`. |
| 165 |
* @param bool $can_view_store_reports Whether the reader may see the store reports. |
| 166 |
* @return array The candidates, minus the store-report categories for readers who can't. |
| 167 |
*/ |
| 168 |
function remove_capability_gated_widget_types( $widget_candidates, $can_view_store_reports ) { |
| 169 |
if ( $can_view_store_reports ) { |
| 170 |
return $widget_candidates; |
| 171 |
} |
| 172 |
|
| 173 |
return array_values( |
| 174 |
array_filter( |
| 175 |
$widget_candidates, |
| 176 |
static function ( $widget ) { |
| 177 |
return ! in_array( $widget['category'] ?? '', STORE_REPORT_WIDGET_CATEGORIES, true ); |
| 178 |
} |
| 179 |
) |
| 180 |
); |
| 181 |
} |
| 182 |
|
| 183 |
/** |
| 184 |
* Registry-time callback: hides widgets whose data the reader cannot fetch — a |
| 185 |
* `view_stats` reader would only collect 403s from the proxy's `analytics` prefix. |
| 186 |
* The registry is request-scoped, so filtering on the current user is safe here. |
| 187 |
* |
| 188 |
* @since 0.1.0 |
| 189 |
* |
| 190 |
* @param array $widget_candidates Manifest candidates. |
| 191 |
* @return array The candidates, minus the store-report categories for readers who can't see them. |
| 192 |
*/ |
| 193 |
function filter_registrable_widget_types_by_capability( $widget_candidates ) { |
| 194 |
return remove_capability_gated_widget_types( |
| 195 |
$widget_candidates, |
| 196 |
Capabilities::current_user_can_view_store_reports() |
| 197 |
); |
| 198 |
} |
| 199 |
|
| 200 |
add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_capability' ); |
| 201 |
|