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