Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
10.26% covered (danger)
10.26%
4 / 39
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Free_Domain_Upsell_Experiment
10.26% covered (danger)
10.26%
4 / 39
25.00% covered (danger)
25.00%
2 / 8
594.66
0.00% covered (danger)
0.00%
0 / 1
 get_upsell_source
10.00% covered (danger)
10.00%
2 / 20
0.00% covered (danger)
0.00%
0 / 1
136.20
 is_eligible
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 should_show_upsell
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 should_suppress_sidebar_notice
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_current_plan
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 get_variation
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 fetch_variation
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 normalize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Resolves the calypso_omnibar_free_domain_upsell_20260825 experiment variation server-side.
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
9
10use Automattic\Jetpack\Status\Host;
11
12/**
13 * Reads the omnibar free-domain upsell variation for the current user.
14 */
15class Free_Domain_Upsell_Experiment {
16
17    const EXPERIMENT_NAME = 'calypso_omnibar_upsell_202610';
18
19    /**
20     * Which sidebar JITM the current user/site would qualify for, or null.
21     *
22     * @return string|null 'free_to_paid_plan', 'monthly_to_annual_plan', or null.
23     */
24    public static function get_upsell_source() {
25        if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
26            return null;
27        }
28
29        if ( ! ( new Host() )->is_wpcom_simple() ) {
30            return null;
31        }
32
33        if ( (bool) get_option( 'wpcom_is_staging_site' ) ) {
34            return null;
35        }
36
37        // Unlaunched sites get the "Launch site" button instead.
38        if ( 'unlaunched' === get_option( 'launch-status' ) ) {
39            return null;
40        }
41
42        $host = wp_parse_url( home_url(), PHP_URL_HOST );
43        if ( ! is_string( $host ) || ! str_ends_with( $host, '.wordpress.com' ) ) {
44            return null;
45        }
46
47        $current_plan = self::get_current_plan();
48        if ( ! is_array( $current_plan ) ) {
49            return null;
50        }
51
52        if ( ! empty( $current_plan['is_free'] ) && empty( get_option( 'options' )['is_domain_only'] ) ) {
53            return 'free_to_paid_plan';
54        }
55
56        $product_slug = (string) ( $current_plan['product_slug'] ?? '' );
57        if ( preg_match( '/[-_]monthly$/', $product_slug ) && ! str_contains( $product_slug, 'trial' ) ) {
58            return 'monthly_to_annual_plan';
59        }
60
61        return null;
62    }
63
64    /**
65     * Whether the current user/site is eligible for the omnibar free-domain upsell.
66     *
67     * @return bool
68     */
69    public static function is_eligible() {
70        return null !== self::get_upsell_source();
71    }
72
73    /**
74     * Whether the current user should see the omnibar free-domain upsell chip.
75     *
76     * @return bool
77     */
78    public static function should_show_upsell() {
79        return self::is_eligible() && 'treatment' === self::get_variation();
80    }
81
82    /**
83     * Whether the winning sidebar notice should be suppressed for this user.
84     *
85     * @param string $notice_id The id of the winning sidebar notice.
86     * @return bool
87     */
88    public static function should_suppress_sidebar_notice( $notice_id ) {
89        if ( ! in_array( $notice_id, array( 'free_to_paid_plan', 'monthly_to_annual_plan' ), true ) ) {
90            return false;
91        }
92
93        return self::should_show_upsell();
94    }
95
96    /**
97     * The current plan data for this blog, or null when unavailable.
98     *
99     * @return array|null
100     */
101    private static function get_current_plan() {
102        if ( ! class_exists( '\WPCOM_Store_API' ) ) {
103            return null;
104        }
105
106        $plan = \WPCOM_Store_API::get_current_plan( get_current_blog_id() );
107
108        return is_array( $plan ) ? $plan : null;
109    }
110
111    /**
112     * The current user's variation: 'control' or 'treatment'.
113     *
114     * @return string
115     */
116    public static function get_variation() {
117        $user_id = get_current_user_id();
118        if ( ! $user_id ) {
119            return 'control';
120        }
121
122        return self::normalize( self::fetch_variation() );
123    }
124
125    /**
126     * Fetch the raw variation name from ExPlat, or null.
127     *
128     * @return string|null
129     */
130    private static function fetch_variation() {
131        if ( ! ( new Host() )->is_wpcom_simple() ) {
132            return null;
133        }
134
135        if ( function_exists( '\ExPlat\assign_current_user' ) ) {
136            return \ExPlat\assign_current_user( self::EXPERIMENT_NAME );
137        }
138
139        return null;
140    }
141
142    /**
143     * Map any variation onto a known value; unknown/null becomes 'control'.
144     *
145     * @param string|null $variation The raw variation name.
146     * @return string
147     */
148    private static function normalize( $variation ) {
149        return 'treatment' === $variation ? 'treatment' : 'control';
150    }
151}