Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.67% covered (success)
96.67%
58 / 60
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Survicate
98.31% covered (success)
98.31%
58 / 59
85.71% covered (warning)
85.71%
6 / 7
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 should_load
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 is_big_sky_site
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 get_editor_context
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 get_visitor_traits
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 enqueue_scripts
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Survicate survey integration
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8namespace A8C\FSE;
9
10/**
11 * Class Survicate
12 */
13class Survicate {
14    /**
15     * Survicate workspace key.
16     */
17    const WORKSPACE_KEY = 'e4794374cce15378101b63de24117572';
18
19    /**
20     * Class instance.
21     *
22     * @var Survicate
23     */
24    private static $instance = null;
25
26    /**
27     * Survicate constructor.
28     */
29    public function __construct() {
30        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 100 );
31    }
32
33    /**
34     * Creates instance.
35     *
36     * @return void
37     */
38    public static function init() {
39        if ( self::$instance === null ) {
40            self::$instance = new self();
41        }
42    }
43
44    /**
45     * Check whether Survicate should load on the current page.
46     *
47     * @return bool
48     */
49    private function should_load() {
50        if ( ! is_user_logged_in() ) {
51            return false;
52        }
53
54        if ( ! is_admin() ) {
55            return false;
56        }
57
58        // Network and user admin pages are internal tools surfaces; surveys must never reach them.
59        if ( is_network_admin() || is_user_admin() ) {
60            return false;
61        }
62
63        // Only load for English locale users.
64        if ( strpos( strtolower( get_user_locale() ), 'en' ) !== 0 ) {
65            return false;
66        }
67
68        // Atomic powers Automattic's internal P2s; surveys must never reach them.
69        if ( ( new \Automattic\Jetpack\Status\Host() )->is_p2_site() ) {
70            return false;
71        }
72
73        return true;
74    }
75
76    /**
77     * Detect whether the current site is a Big Sky site.
78     *
79     * Used as a visitor trait so Survicate's targeting UI can include or exclude
80     * Big Sky users without a code change.
81     *
82     * @return bool
83     */
84    private function is_big_sky_site() {
85        if ( ! function_exists( 'wpcom_has_blog_sticker' ) ) {
86            return false;
87        }
88
89        $blog_id = get_wpcom_blog_id();
90        if ( ! $blog_id ) {
91            return false;
92        }
93
94        return wpcom_has_blog_sticker( 'big-sky-enabled', $blog_id )
95            || wpcom_has_blog_sticker( 'big-sky-free-trial', $blog_id );
96    }
97
98    /**
99     * Detect the current editor context.
100     *
101     * @return string One of 'site-editor', 'block-editor', or 'wp-admin'.
102     */
103    private function get_editor_context() {
104        global $pagenow;
105
106        if ( $pagenow === 'site-editor.php' ) {
107            return 'site-editor';
108        }
109
110        if ( function_exists( 'get_current_screen' ) ) {
111            $current_screen = get_current_screen();
112            if ( $current_screen && $current_screen->is_block_editor() && $current_screen->id !== 'widgets' ) {
113                return 'block-editor';
114            }
115        }
116
117        return 'wp-admin';
118    }
119
120    /**
121     * Get visitor traits for Survicate.
122     *
123     * @return array
124     */
125    private function get_visitor_traits() {
126        $user_data = get_userdata( get_current_user_id() );
127        $email     = $user_data ? $user_data->user_email : '';
128        $site_id   = get_wpcom_blog_id();
129        $site_type = ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) ? 'atomic' : 'simple';
130
131        return array(
132            'email'           => $email,
133            'site_id'         => $site_id ? (string) $site_id : '',
134            'site_type'       => $site_type,
135            'editor_context'  => $this->get_editor_context(),
136            // Stringified for Survicate's trait targeting UI, which matches on string equality.
137            'is_big_sky_site' => $this->is_big_sky_site() ? 'true' : 'false',
138        );
139    }
140
141    /**
142     * Enqueue Survicate scripts.
143     */
144    public function enqueue_scripts() {
145        if ( ! $this->should_load() ) {
146            return;
147        }
148
149        $traits_json   = wp_json_encode( $this->get_visitor_traits(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
150        $workspace_key = self::WORKSPACE_KEY;
151
152        // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter
153        wp_register_script(
154            'wpcom-survicate',
155            false,
156            array( 'wp-data' ),
157            '1.0',
158            false
159        );
160
161        wp_add_inline_script(
162            'wpcom-survicate',
163            <<<JS
164( function () {
165    if ( window.__wpcomSurvicateInit ) {
166        return;
167    }
168    window.__wpcomSurvicateInit = true;
169    if ( window.innerWidth < 480 ) {
170        return;
171    }
172    var script = document.createElement( 'script' );
173    script.src = 'https://survey.survicate.com/workspaces/{$workspace_key}/web_surveys.js';
174    script.async = true;
175    document.head.appendChild( script );
176    var traits = {$traits_json};
177
178    // The Help Center registers this @wordpress/data store from a separate bundle
179    // loaded from widgets.wp.com; reads are guarded in case it is not yet registered.
180    function isHelpCenterShown() {
181        try {
182            var store = window.wp && window.wp.data && window.wp.data.select( 'automattic/help-center' );
183            return !! ( store && typeof store.isHelpCenterShown === 'function' && store.isHelpCenterShown() );
184        } catch ( e ) {
185            return false;
186        }
187    }
188    function closeAnySurvey() {
189        if ( window._sva && typeof window._sva.closeSurvey === 'function' ) {
190            window._sva.closeSurvey();
191        }
192    }
193
194    if ( window.wp && window.wp.data && typeof window.wp.data.subscribe === 'function' ) {
195        var wasShown = isHelpCenterShown();
196        // Scope the subscription to the Help Center store so the callback does not
197        // fire on every dispatch across all registered stores (e.g. block editor).
198        window.wp.data.subscribe( function () {
199            var shown = isHelpCenterShown();
200            if ( shown && ! wasShown ) {
201                closeAnySurvey();
202            }
203            wasShown = shown;
204        }, 'automattic/help-center' );
205    }
206
207    window.addEventListener( 'SurvicateReady', function () {
208        window._sva.setVisitorTraits( traits );
209
210        // Covers the race where the Help Center opened before the SDK finished loading.
211        if ( isHelpCenterShown() ) {
212            closeAnySurvey();
213        }
214
215        if ( typeof window._sva.addEventListener === 'function' ) {
216            // The SDK does not expose a pre-display hook, so we close on the
217            // post-display event. This causes a brief flash but is the best the
218            // public API allows.
219            window._sva.addEventListener( 'survey_displayed', function () {
220                if ( isHelpCenterShown() ) {
221                    closeAnySurvey();
222                }
223            } );
224        }
225    } );
226} )();
227JS
228        );
229
230        wp_enqueue_script( 'wpcom-survicate' );
231    }
232}
233
234add_action( 'init', array( __NAMESPACE__ . '\Survicate', 'init' ) );