Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
60.87% covered (warning)
60.87%
56 / 92
47.37% covered (danger)
47.37%
9 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Stats
61.11% covered (warning)
61.11%
55 / 90
47.37% covered (danger)
47.37%
9 / 19
164.10
0.00% covered (danger)
0.00%
0 / 1
 get_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_title
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_description
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_long_description
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_features
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 get_pricing_for_ui
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 get_wpcom_product_slug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_wpcom_pwyw_product_slug
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_wpcom_free_product_slug
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_status
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 is_upgradable
23.53% covered (danger)
23.53%
4 / 17
0.00% covered (danger)
0.00%
0 / 1
65.11
 get_paid_plan_product_slugs
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 get_url_product_type
22.22% covered (danger)
22.22%
4 / 18
0.00% covered (danger)
0.00%
0 / 1
57.05
 has_trial_support
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_premium_analytics_enabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 get_purchase_url
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 get_post_activation_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_manage_url
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 is_upgradable_by_bundle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Jetpack Stats product
4 *
5 * @package my-jetpack
6 */
7
8namespace Automattic\Jetpack\My_Jetpack\Products;
9
10use Automattic\Jetpack\My_Jetpack\Initializer;
11use Automattic\Jetpack\My_Jetpack\Module_Product;
12use Automattic\Jetpack\My_jetpack\Products;
13use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
14use Automattic\Jetpack\Status\Host;
15use Jetpack_Options;
16
17if ( ! defined( 'ABSPATH' ) ) {
18    exit( 0 );
19}
20
21/**
22 * Class responsible for handling the Jetpack Stats product
23 */
24class Stats extends Module_Product {
25    /**
26     * The product slug
27     *
28     * @var string
29     */
30    public static $slug = 'stats';
31
32    /**
33     * The Jetpack module name associated with this product
34     *
35     * @var string|null
36     */
37    public static $module_name = 'stats';
38
39    /**
40     * The category of the product
41     *
42     * @var string
43     */
44    public static $category = 'growth';
45
46    /**
47     * The Plugin slug associated with stats
48     *
49     * @var string|null
50     */
51    public static $plugin_slug = self::JETPACK_PLUGIN_SLUG;
52
53    /**
54     * The Plugin file associated with stats
55     *
56     * @var string|null
57     */
58    public static $plugin_filename = self::JETPACK_PLUGIN_FILENAME;
59
60    /**
61     * Stats only requires site connection, not user connection
62     *
63     * @var bool
64     */
65    public static $requires_user_connection = false;
66
67    /**
68     * Stats does not have a standalone plugin (yet?)
69     *
70     * @var bool
71     */
72    public static $has_standalone_plugin = false;
73
74    /**
75     * Whether this product has a free offering
76     *
77     * @var bool
78     */
79    public static $has_free_offering = true;
80
81    /**
82     * The feature slug that identifies the paid plan
83     *
84     * @var string
85     */
86    public static $feature_identifying_paid_plan = 'stats-paid';
87
88    /**
89     * Get the product name
90     *
91     * @return string
92     */
93    public static function get_name() {
94        return 'Stats';
95    }
96
97    /**
98     * Get the product title
99     *
100     * @return string
101     */
102    public static function get_title() {
103        return 'Jetpack Stats';
104    }
105
106    /**
107     * Get the internationalized product description
108     *
109     * @return string
110     */
111    public static function get_description() {
112        return __( 'Clear, concise, and actionable analysis of your site performance.', 'jetpack-my-jetpack' );
113    }
114
115    /**
116     * Get the internationalized product long description
117     *
118     * @return string
119     */
120    public static function get_long_description() {
121        return __( 'With Jetpack Stats, you don’t need to be a data scientist to see how your site is performing, understand your visitors, and grow your site.', 'jetpack-my-jetpack' );
122    }
123
124    /**
125     * Get the internationalized features list
126     *
127     * @return array Stats features list
128     */
129    public static function get_features() {
130        return array(
131            __( 'Real-time data on visitors', 'jetpack-my-jetpack' ),
132            __( 'Traffic stats and trends for post and pages', 'jetpack-my-jetpack' ),
133            __( 'Detailed statistics about links leading to your site', 'jetpack-my-jetpack' ),
134            __( 'GDPR compliant', 'jetpack-my-jetpack' ),
135            /* translators: UTM refers to the Urchin Tracking Module campaign parameters appended to a URL. */
136            __( 'UTM tracking', 'jetpack-my-jetpack' ),
137            __( 'Device stats', 'jetpack-my-jetpack' ),
138            __( 'Region and city locations', 'jetpack-my-jetpack' ),
139            __( 'Priority support', 'jetpack-my-jetpack' ),
140        );
141    }
142
143    /**
144     * Get the product pricing details
145     * Only showing the pricing details for the commercial product
146     *
147     * @return array Pricing details
148     */
149    public static function get_pricing_for_ui() {
150        return array_merge(
151            array(
152                'available'          => true,
153                'wpcom_product_slug' => static::get_wpcom_product_slug(),
154            ),
155            Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() )
156        );
157    }
158
159    /**
160     * Get the WPCOM product slug used to make the purchase
161     *
162     * @return ?string
163     */
164    public static function get_wpcom_product_slug() {
165        return 'jetpack_stats_yearly';
166    }
167
168    /**
169     * Get the WPCOM Pay Whatever You Want product slug used to make the purchase
170     *
171     * @return ?string
172     */
173    public static function get_wpcom_pwyw_product_slug() {
174        return 'jetpack_stats_pwyw_yearly';
175    }
176
177    /**
178     * Get the WPCOM free product slug
179     *
180     * @return ?string
181     */
182    public static function get_wpcom_free_product_slug() {
183        return 'jetpack_stats_free_yearly';
184    }
185
186    /**
187     * Gets the 'status' of the Stats product
188     *
189     * @return string
190     */
191    public static function get_status() {
192        $status = parent::get_status();
193        if ( Products::STATUS_MODULE_DISABLED === $status && ! Initializer::is_registered() ) {
194            // If the site has never been connected before, show the "Learn more" CTA,
195            // that points to the add Stats product interstitial.
196            $status = Products::STATUS_NEEDS_FIRST_SITE_CONNECTION;
197        }
198        return $status;
199    }
200    /**
201     * Checks whether the product can be upgraded to a different product.
202     * Stats Commercial plan (highest tier) & Jetpack Complete are not upgradable.
203     * Also we don't push PWYW users to upgrade.
204     *
205     * @return boolean
206     */
207    public static function is_upgradable() {
208        // For now, atomic sites with stats are not in a position to upgrade
209        if ( ( new Host() )->is_woa_site() ) {
210            return false;
211        }
212
213        $purchases_data = Wpcom_Products::get_site_current_purchases();
214        if ( ! is_wp_error( $purchases_data ) && is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
215            foreach ( $purchases_data as $purchase ) {
216                // Jetpack complete includes Stats commercial & cannot be upgraded
217                if (
218                    str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ||
219                    str_starts_with( $purchase->product_slug, 'jetpack_growth' )
220                ) {
221                    return false;
222                } elseif (
223                    // Stats commercial purchased with highest tier cannot be upgraded.
224                    in_array(
225                        $purchase->product_slug,
226                        array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
227                        true
228                    ) && $purchase->current_price_tier_slug === 'more_than_1m_views'
229                ) {
230                    return false;
231                } elseif (
232                    // If user already has Stats PWYW, we won't push them to upgrade.
233                    $purchase->product_slug === 'jetpack_stats_pwyw_yearly'
234                ) {
235                    return false;
236                }
237            }
238        }
239        return true;
240    }
241
242    /**
243     * Get the product-slugs of the paid plans for this product (not including bundles)
244     *
245     * @return array
246     */
247    public static function get_paid_plan_product_slugs() {
248        return array(
249            'jetpack_stats_yearly',
250            'jetpack_stats_monthly',
251            'jetpack_stats_bi_yearly',
252            'jetpack_stats_pwyw_yearly',
253        );
254    }
255
256    /**
257     * Returns a productType parameter for an upgrade URL, determining whether
258     * to show the PWYW upgrade interstitial or commercial upgrade interstitial.
259     *
260     * @return string
261     */
262    public static function get_url_product_type() {
263        $purchases_data     = Wpcom_Products::get_site_current_purchases();
264        $is_commercial_site = Initializer::is_commercial_site();
265        if ( is_wp_error( $purchases_data ) ) {
266            return $is_commercial_site ? '&productType=commercial' : '';
267        }
268        if ( $is_commercial_site ) {
269            return '&productType=commercial';
270        }
271        if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) {
272            foreach ( $purchases_data as $purchase ) {
273                if (
274                    str_starts_with( $purchase->product_slug, static::get_wpcom_free_product_slug() )
275                ) {
276                    return '&productType=personal';
277                } elseif (
278                    in_array(
279                        $purchase->product_slug,
280                        array( 'jetpack_stats_yearly', 'jetpack_stats_monthly', 'jetpack_stats_bi_yearly' ),
281                        true
282                    ) &&
283                    ! empty( $purchase->current_price_tier_slug )
284                ) {
285                    return '&productType=commercial';
286                }
287            }
288        }
289        return '';
290    }
291
292    /**
293     * Checks whether the product supports trial or not.
294     * Since Jetpack Stats has been widely available as a free product in the past, it "supports" a trial.
295     *
296     * @return boolean
297     */
298    public static function has_trial_support() {
299        return true;
300    }
301
302    /**
303     * Mirrors `Analytics::MENU_PAGE_SLUG`, spelled out because My Jetpack does not
304     * depend on the premium-analytics package.
305     *
306     * @since 5.42.0
307     */
308    const PREMIUM_ANALYTICS_PAGE_SLUG = 'jetpack-premium-analytics-wp-admin';
309
310    /**
311     * Whether the Premium Analytics dashboard is the site's analytics UI.
312     *
313     * Guarded like the other `class_exists( 'Jetpack' )` checks here: My Jetpack
314     * also ships in plugins without the Jetpack plugin. Public so the UI flags
315     * report the same answer the URLs are built from.
316     *
317     * @since 5.42.0
318     *
319     * @return bool
320     */
321    public static function is_premium_analytics_enabled() {
322        return class_exists( 'Jetpack' )
323            && method_exists( 'Jetpack', 'is_premium_analytics_enabled' )
324            && \Jetpack::is_premium_analytics_enabled();
325    }
326
327    /**
328     * Get the WordPress.com URL for purchasing Jetpack Stats for the current site.
329     *
330     * Null once Premium Analytics is the analytics UI: the tier purchase
331     * screen was a Calypso route inside the Odyssey bundle, so it left with that
332     * dashboard. Null is also the base-class default, which falls the action
333     * button back to the existing `#/add-stats` interstitial.
334     *
335     * @return ?string
336     */
337    public static function get_purchase_url() {
338        if ( self::is_premium_analytics_enabled() ) {
339            return null;
340        }
341
342        $status = static::get_status();
343        if ( $status === Products::STATUS_NEEDS_FIRST_SITE_CONNECTION ) {
344            return null;
345        }
346        // The returning URL could be customized by changing the `redirect_uri` param with relative path.
347        return sprintf(
348            '%s#!/stats/purchase/%d?from=jetpack-my-jetpack%s&redirect_uri=%s',
349            admin_url( 'admin.php?page=stats' ),
350            Jetpack_Options::get_option( 'id' ),
351            static::get_url_product_type(),
352            rawurlencode( 'admin.php?page=stats' )
353        );
354    }
355
356    /**
357     * Get the URL the user is taken to after activating the product
358     *
359     * @return ?string
360     */
361    public static function get_post_activation_url() {
362        // Names the plan the Free-vs-Paid question was already answered with here, so the Stats
363        // dashboard's own pricing grid renders the dashboard instead of asking it a second time,
364        // and records the choice as free rather than as one it cannot name.
365        return add_query_arg( 'stats_plan_chosen', 'free', static::get_manage_url() );
366    }
367
368    /**
369     * Get the URL where the user manages the product
370     *
371     * @return ?string
372     */
373    public static function get_manage_url() {
374        if ( self::is_premium_analytics_enabled() ) {
375            return admin_url( 'admin.php?page=' . self::PREMIUM_ANALYTICS_PAGE_SLUG );
376        }
377
378        return admin_url( 'admin.php?page=stats' );
379    }
380
381    /**
382     * Return product bundles list
383     * that supports the product.
384     *
385     * @return boolean|array Products bundle list.
386     */
387    public static function is_upgradable_by_bundle() {
388        return array( 'growth', 'complete' );
389    }
390}