Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.00% covered (danger)
50.00%
28 / 56
50.00% covered (danger)
50.00%
2 / 4
CRAP
n/a
0 / 0
wpcom_site_launch_uses_publish_copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
wpcom_site_launch_peek_publish_copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
wpcom_site_launch_get_publish_copy
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
7.04
wpcom_site_launch_resolve_publish_copy
44.44% covered (danger)
44.44%
16 / 36
0.00% covered (danger)
0.00%
0 / 1
66.55
1<?php
2/**
3 * The "Publish" copy experiment for site launch entry points.
4 *
5 * Calypso reads the same experiment, so one user sees the same verb on every surface.
6 *
7 * @package automattic/jetpack-mu-wpcom
8 */
9
10use Automattic\Jetpack\Connection\Client;
11use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12use Automattic\Jetpack\Status\Host;
13
14require_once __DIR__ . '/../utils.php';
15
16if ( ! defined( 'WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT' ) ) {
17    /**
18     * The ExPlat experiment. It is registered on the calypso platform, which the server-side assigner
19     * also accepts.
20     */
21    define( 'WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT', 'calypso_site_launch_publish_copy_202610_v1' );
22}
23
24if ( ! function_exists( 'wpcom_site_launch_uses_publish_copy' ) ) {
25    /**
26     * Whether a site launch entry point says "Publish" instead of "Launch" for the current user.
27     *
28     * Call it only where an entry point renders: it assigns the user, and rendering is the exposure.
29     * Code that runs on most requests, such as the launchpad task titles built on `init`, must use
30     * wpcom_site_launch_peek_publish_copy() instead.
31     *
32     * @return bool
33     */
34    function wpcom_site_launch_uses_publish_copy() {
35        return wpcom_site_launch_get_publish_copy( true );
36    }
37}
38
39if ( ! function_exists( 'wpcom_site_launch_peek_publish_copy' ) ) {
40    /**
41     * Like wpcom_site_launch_uses_publish_copy(), but reads an existing assignment without making one.
42     *
43     * A user with no assignment yet gets the control.
44     *
45     * @return bool
46     */
47    function wpcom_site_launch_peek_publish_copy() {
48        return wpcom_site_launch_get_publish_copy( false );
49    }
50}
51
52if ( ! function_exists( 'wpcom_site_launch_get_publish_copy' ) ) {
53    /**
54     * Resolves the experiment arm once per request, blog, and user.
55     *
56     * @param bool $assign Whether to assign a user who has no assignment yet.
57     * @return bool
58     */
59    function wpcom_site_launch_get_publish_copy( $assign ) {
60        /**
61         * Overrides the site launch "Publish" copy experiment assignment, for testing.
62         *
63         * @param bool|null $override True to force the treatment, false to force control, or null to ask ExPlat.
64         */
65        $override = apply_filters( 'wpcom_site_launch_publish_copy_override_is_treatment', null );
66        if ( null !== $override ) {
67            return (bool) $override;
68        }
69
70        static $results = array();
71
72        $key = get_current_blog_id() . ':' . get_current_user_id();
73        if ( $assign && ! isset( $results[ $key ]['assign'] ) ) {
74            $results[ $key ]['assign'] = wpcom_site_launch_resolve_publish_copy( true );
75            // A peek later in this request must see the assignment just made.
76            $results[ $key ]['peek'] = $results[ $key ]['assign'];
77        } elseif ( ! $assign && ! isset( $results[ $key ]['peek'] ) ) {
78            $results[ $key ]['peek'] = wpcom_site_launch_resolve_publish_copy( false );
79        }
80
81        return $results[ $key ][ $assign ? 'assign' : 'peek' ];
82    }
83}
84
85if ( ! function_exists( 'wpcom_site_launch_resolve_publish_copy' ) ) {
86    /**
87     * Resolves the experiment arm. Use the cached wrappers above.
88     *
89     * Only admins of an unlaunched site are assigned; everyone else gets the control. Agencies launch
90     * their dev sites through their own flow, so Calypso keeps those out too.
91     *
92     * @param bool $assign Whether to assign a user who has no assignment yet.
93     * @return bool
94     */
95    function wpcom_site_launch_resolve_publish_copy( $assign ) {
96        if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
97            return false;
98        }
99
100        if ( get_option( 'launch-status' ) !== 'unlaunched' ) {
101            return false;
102        }
103
104        if ( wpcom_has_blog_sticker( 'a4a-is-dev-site', get_current_blog_id() ) ) {
105            return false;
106        }
107
108        $user_id   = get_current_user_id();
109        $cache_key = 'wpcom-site-launch-publish-copy-' . $user_id . '-' . WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT;
110        $cached    = get_transient( $cache_key );
111        if ( false !== $cached ) {
112            return (bool) $cached;
113        }
114
115        // ExPlat returns null both for the control and for a failed assignment. On Simple the call is
116        // local, so a null is not cached. On Atomic it is a request, so any 200 counts as answered and is
117        // cached, the control included; only a failed request is retried.
118        $variation = null;
119        $answered  = false;
120
121        if ( ( new Host() )->is_wpcom_simple() ) {
122            if ( $assign && function_exists( '\ExPlat\assign_current_user' ) ) {
123                $variation = \ExPlat\assign_current_user( WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT );
124            } elseif ( ! $assign && function_exists( '\ExPlat\get_current_user_assignment' ) ) {
125                // @phan-suppress-next-line PhanUndeclaredFunction -- Defined on WordPress.com, missing from the wpcom stubs.
126                $variation = \ExPlat\get_current_user_assignment( WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT );
127            }
128            $answered = null !== $variation;
129        } elseif ( $assign && ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) {
130            // The assignments endpoint always assigns, so a peek on Atomic relies on the cache alone.
131            $response = Client::wpcom_json_api_request_as_user(
132                add_query_arg(
133                    array( 'experiment_names' => WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT ),
134                    '/experiments/0.1.0/assignments/wpcom'
135                ),
136                '2'
137            );
138
139            if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
140                $data      = json_decode( wp_remote_retrieve_body( $response ), true );
141                $variation = $data['variations'][ WPCOM_SITE_LAUNCH_PUBLISH_COPY_EXPERIMENT ] ?? null;
142                $answered  = true;
143            }
144        }
145
146        if ( ! $answered ) {
147            return false;
148        }
149
150        $is_treatment = 'treatment' === $variation;
151        set_transient( $cache_key, $is_treatment ? 1 : 0, HOUR_IN_SECONDS );
152
153        return $is_treatment;
154    }
155}