Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.38% covered (warning)
84.38%
27 / 32
100.00% covered (success)
100.00%
5 / 5
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_capability_gated_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_capability
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Widget availability policy (consumer layer): drops developer-only, platform-unsupported,
4 * 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
11namespace Automattic\Jetpack\PremiumAnalytics;
12
13require_once __DIR__ . '/widget-types.php';
14require_once __DIR__ . '/widget-type-support.php';
15
16/**
17 * Widget categories whose data counts as a Stats report: each reaches WPCOM through a proxy
18 * prefix gated on `view_stats`.
19 */
20const STATS_REPORT_WIDGET_CATEGORIES = array( 'stats', 'traffic', 'subscribers' );
21
22/**
23 * Removes developer-only candidates in production.
24 *
25 * Split from the hook callback so both branches are testable without touching
26 * the global environment.
27 *
28 * @param array  $widget_candidates Manifest candidates, each with a `name` and `category`.
29 * @param string $environment       Site environment type.
30 * @return array The candidates, minus developer-only types in production.
31 */
32function remove_dev_only_widget_types( $widget_candidates, $environment ) {
33    if ( 'production' !== $environment ) {
34        return $widget_candidates;
35    }
36
37    return array_values(
38        array_filter(
39            $widget_candidates,
40            static function ( $widget ) {
41                return 'developer' !== ( $widget['category'] ?? '' );
42            }
43        )
44    );
45}
46
47/**
48 * Registry-time callback: hides developer-only types in production.
49 *
50 * Defaults to `production`; a site opts in via `WP_ENVIRONMENT_TYPE`
51 * (`local`, `development`, `staging`).
52 *
53 * @param array $widget_candidates Manifest candidates.
54 * @return array The candidates, minus developer-only types in production.
55 */
56function filter_registrable_widget_types_by_environment( $widget_candidates ) {
57    return remove_dev_only_widget_types( $widget_candidates, wp_get_environment_type() );
58}
59
60add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_environment' );
61
62/**
63 * Applies shared type-level availability at registry time.
64 *
65 * @param array $widget_candidates Manifest candidates.
66 * @return array Filtered candidates.
67 */
68function filter_registrable_widget_types_by_availability( $widget_candidates ) {
69    return remove_unsupported_widget_items(
70        $widget_candidates,
71        'name',
72        get_widget_support_context()
73    );
74}
75
76add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_availability' );
77
78// Subscriber widgets stay registered even when their section is hidden: their data
79// doesn't depend on the local module, and unregistering would break instances placed elsewhere.
80
81/**
82 * Removes candidates the reader could not load data for anyway.
83 *
84 * Split from the hook callback so both branches are testable without a user.
85 *
86 * @since 0.1.0
87 * @since $$next-version$$ Gates the Stats categories alone: the WooCommerce Stats package gates its own.
88 *
89 * @param array $widget_candidates Manifest candidates, each with a `category`.
90 * @param bool  $can_view_stats    Whether the reader may see the Stats reports.
91 * @return array The candidates, minus the Stats categories when the reader can't see them.
92 */
93function remove_capability_gated_widget_types( $widget_candidates, $can_view_stats ) {
94    if ( $can_view_stats ) {
95        return $widget_candidates;
96    }
97
98    return array_values(
99        array_filter(
100            $widget_candidates,
101            static function ( $widget ) {
102                return ! in_array( $widget['category'] ?? '', STATS_REPORT_WIDGET_CATEGORIES, true );
103            }
104        )
105    );
106}
107
108/**
109 * Registry-time callback: hides widgets whose data the reader cannot fetch, since a shop manager
110 * would only collect 403s from the Stats prefixes. The registry is request-scoped, so filtering
111 * on the current user is safe here.
112 *
113 * @since 0.1.0
114 *
115 * @param array $widget_candidates Manifest candidates.
116 * @return array The candidates, minus the Stats categories when the reader can't see them.
117 */
118function filter_registrable_widget_types_by_capability( $widget_candidates ) {
119    return remove_capability_gated_widget_types( $widget_candidates, Capabilities::current_user_can_view_stats() );
120}
121
122add_filter( REGISTRABLE_WIDGET_TYPES_FILTER, __NAMESPACE__ . '\\filter_registrable_widget_types_by_capability' );