Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 189
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Post_Connection_JITM
0.00% covered (danger)
0.00%
0 / 187
0.00% covered (danger)
0.00%
0 / 12
1560
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_woocommerce_services_msg
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 jitm_jetpack_woo_services_install
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_woo_services_activate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_creative_mail_install
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_creative_mail_activate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_backup_install
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_backup_activate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_boost_install
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 jitm_jetpack_boost_activate
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 dismiss
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 get_messages
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 1
600
1<?php
2/**
3 * Jetpack's Post-Connection JITM class.
4 *
5 * @package automattic/jetpack-jitm
6 */
7
8namespace Automattic\Jetpack\JITMS;
9
10use Automattic\Jetpack\A8c_Mc_Stats;
11use Automattic\Jetpack\Connection\Client;
12use Automattic\Jetpack\Connection\Manager;
13use Automattic\Jetpack\Device_Detection;
14use Automattic\Jetpack\Partner;
15use Automattic\Jetpack\Redirect;
16use Automattic\Jetpack\Tracking;
17
18if ( ! defined( 'ABSPATH' ) ) {
19    exit( 0 );
20}
21
22/**
23 * Jetpack just in time messaging through out the admin
24 *
25 * @since 1.1.0
26 *
27 * @since-jetpack 5.6.0
28 */
29class Post_Connection_JITM extends JITM {
30
31    /**
32     * Tracking object.
33     *
34     * @var \Automattic\Jetpack\Tracking
35     *
36     * @access private
37     */
38    public $tracking;
39
40    /**
41     * JITM constructor.
42     */
43    public function __construct() {
44        $this->tracking = new Tracking();
45    }
46
47    /**
48     * A special filter for WooCommerce, to set a message based on local state.
49     *
50     * @param object $content The current message.
51     *
52     * @return object The new message.
53     */
54    public static function jitm_woocommerce_services_msg( $content ) {
55        if ( ! function_exists( 'wc_get_base_location' ) ) {
56            return $content;
57        }
58
59        $base_location = wc_get_base_location();
60
61        switch ( $base_location['country'] ) {
62            case 'US':
63                $content->message = esc_html__( 'New free service: Show USPS shipping rates on your store! Added bonus: print shipping labels without leaving WooCommerce.', 'jetpack-jitm' );
64                break;
65            case 'CA':
66                $content->message = esc_html__( 'New free service: Show Canada Post shipping rates on your store!', 'jetpack-jitm' );
67                break;
68            default:
69                $content->message = '';
70        }
71
72        return $content;
73    }
74
75    /**
76     * A special filter for WooCommerce Call To Action button
77     *
78     * @return string The new CTA
79     */
80    public static function jitm_jetpack_woo_services_install() {
81        return wp_nonce_url(
82            add_query_arg(
83                array(
84                    'wc-services-action' => 'install',
85                ),
86                admin_url( 'admin.php?page=wc-settings' )
87            ),
88            'wc-services-install'
89        );
90    }
91
92    /**
93     * A special filter for WooCommerce Call To Action button.
94     *
95     * @return string The new CTA
96     */
97    public static function jitm_jetpack_woo_services_activate() {
98        return wp_nonce_url(
99            add_query_arg(
100                array(
101                    'wc-services-action' => 'activate',
102                ),
103                admin_url( 'admin.php?page=wc-settings' )
104            ),
105            'wc-services-install'
106        );
107    }
108
109    /**
110     * A special filter used in the CTA of a JITM offering to install the Creative Mail plugin.
111     *
112     * @return string The new CTA
113     */
114    public static function jitm_jetpack_creative_mail_install() {
115        return wp_nonce_url(
116            add_query_arg(
117                array(
118                    'creative-mail-action' => 'install',
119                ),
120                admin_url( 'admin.php?page=jetpack-forms-admin' )
121            ),
122            'creative-mail-install'
123        );
124    }
125
126    /**
127     * A special filter used in the CTA of a JITM offering to activate the Creative Mail plugin.
128     *
129     * @return string The new CTA
130     */
131    public static function jitm_jetpack_creative_mail_activate() {
132        return wp_nonce_url(
133            add_query_arg(
134                array(
135                    'creative-mail-action' => 'activate',
136                ),
137                admin_url( 'admin.php?page=jetpack-forms-admin' )
138            ),
139            'creative-mail-install'
140        );
141    }
142
143    /**
144     * A special filter used in the CTA of a JITM offering to install the Jetpack Backup plugin.
145     *
146     * @return string The new CTA
147     */
148    public static function jitm_jetpack_backup_install() {
149        return wp_nonce_url(
150            add_query_arg(
151                array(
152                    'jetpack-backup-action' => 'install',
153                ),
154                admin_url( 'admin.php?page=jetpack' )
155            ),
156            'jetpack-backup-install'
157        );
158    }
159
160    /**
161     * A special filter used in the CTA of a JITM offering to activate the Jetpack Backup plugin.
162     *
163     * @return string The new CTA
164     */
165    public static function jitm_jetpack_backup_activate() {
166        return wp_nonce_url(
167            add_query_arg(
168                array(
169                    'jetpack-backup-action' => 'activate',
170                ),
171                admin_url( 'admin.php?page=jetpack' )
172            ),
173            'jetpack-backup-install'
174        );
175    }
176
177    /**
178     * A special filter used in the CTA of a JITM offering to install the Jetpack Boost plugin.
179     *
180     * @return string The new CTA
181     */
182    public static function jitm_jetpack_boost_install() {
183        return wp_nonce_url(
184            add_query_arg(
185                array(
186                    'jetpack-boost-action' => 'install',
187                ),
188                admin_url( 'admin.php?page=jetpack' )
189            ),
190            'jetpack-boost-install'
191        );
192    }
193
194    /**
195     * A special filter used in the CTA of a JITM offering to activate the Jetpack Boost plugin.
196     *
197     * @return string The new CTA
198     */
199    public static function jitm_jetpack_boost_activate() {
200        return wp_nonce_url(
201            add_query_arg(
202                array(
203                    'jetpack-boost-action' => 'activate',
204                ),
205                admin_url( 'admin.php?page=jetpack' )
206            ),
207            'jetpack-boost-install'
208        );
209    }
210
211    /**
212     * Dismisses a JITM feature class so that it will no longer be shown.
213     *
214     * @param string $id The id of the JITM that was dismissed.
215     * @param string $feature_class The feature class of the JITM that was dismissed.
216     *
217     * @return bool Always true.
218     */
219    public function dismiss( $id, $feature_class ) {
220        $this->tracking->record_user_event(
221            'jitm_dismiss_client',
222            array(
223                'jitm_id'       => $id,
224                'feature_class' => $feature_class,
225            )
226        );
227        $this->save_dismiss( $feature_class );
228        return true;
229    }
230
231    /**
232     * Asks the wpcom API for the current message to display keyed on query string and message path.
233     *
234     * For sites running on the Dotcom Simple codebase, the network request is bypassed
235     * via Client::wpcom_json_api_request_as_blog allowing for the JITM\Engine to be called
236     * directly.
237     *
238     * @param string $message_path The message path to ask for.
239     * @param array  $query Query parameters as an associative array.
240     * @param bool   $full_jp_logo_exists If there is a full Jetpack logo already on the page.
241     *
242     * @return array The JITM's to show, or an empty array if there is nothing to show
243     */
244    public function get_messages( $message_path, $query, $full_jp_logo_exists ) {
245        // WooCommerce Services.
246        add_filter( 'jitm_woocommerce_services_msg', array( $this, 'jitm_woocommerce_services_msg' ) );
247        add_filter( 'jitm_jetpack_woo_services_install', array( $this, 'jitm_jetpack_woo_services_install' ) );
248        add_filter( 'jitm_jetpack_woo_services_activate', array( $this, 'jitm_jetpack_woo_services_activate' ) );
249
250        // Creative Mail.
251        add_filter( 'jitm_jetpack_creative_mail_install', array( $this, 'jitm_jetpack_creative_mail_install' ) );
252        add_filter( 'jitm_jetpack_creative_mail_activate', array( $this, 'jitm_jetpack_creative_mail_activate' ) );
253
254        // Jetpack Backup.
255        add_filter( 'jitm_jetpack_backup_install', array( $this, 'jitm_jetpack_backup_install' ) );
256        add_filter( 'jitm_jetpack_backup_activate', array( $this, 'jitm_jetpack_backup_activate' ) );
257
258        // Jetpack Boost.
259        add_filter( 'jitm_jetpack_boost_install', array( $this, 'jitm_jetpack_boost_install' ) );
260        add_filter( 'jitm_jetpack_boost_activate', array( $this, 'jitm_jetpack_boost_activate' ) );
261
262        $user = wp_get_current_user();
263
264        // Unauthenticated or invalid requests just bail.
265        if ( ! $user ) {
266            return array();
267        }
268
269        $user_roles = implode( ',', $user->roles );
270        $site_id    = \Jetpack_Options::get_option( 'id' );
271
272        // Build our jitm request.
273        $path = add_query_arg(
274            array(
275                'external_user_id' => urlencode_deep( $user->ID ),
276                'user_roles'       => urlencode_deep( $user_roles ),
277                'query_string'     => urlencode_deep( build_query( $query ) ),
278                'mobile_browser'   => Device_Detection::is_smartphone() ? 1 : 0,
279                '_locale'          => get_user_locale(),
280            ),
281            sprintf( '/sites/%d/jitm/%s', $site_id, $message_path )
282        );
283
284        $cache_key = 'jetpack_jitm_' . substr( md5( $path ), 0, 31 );
285
286        // Attempt to get from cache.
287        $envelopes = get_transient( $cache_key );
288
289        // If something is in the cache and it was put in the cache after the last sync we care about, use it.
290        $use_cache = false;
291
292        /**
293         * Filter to turn off jitm caching
294         *
295         * @since 1.1.0
296         * @since-jetpack 5.4.0
297         *
298         * @param bool true Whether to cache just in time messages
299         */
300        if ( apply_filters( 'jetpack_just_in_time_msg_cache', true ) ) {
301            $use_cache = true;
302        }
303
304        if ( $use_cache ) {
305            $last_sync = (int) get_transient( 'jetpack_last_plugin_sync' );
306            // The sync timestamp indicates when a plugin change was last sent to Jetpack, however,
307            // it's stored in a transient and doesn't stay forever. Therefore, an admin who last changed
308            // a plugin a month ago is likely to have $last_sync=0.
309            //
310            // If the sync timestamp is missing (value 0): use the cache.
311            // If the timestamp exists and is older than the cached envelope: use the cache.
312            // If the timestamp exists and is newer: bypass and refresh.
313            // (This case means the JITM was created before the last plugin activate/deactivate and is invalid).
314            $from_cache = $envelopes && ( 0 === $last_sync || $last_sync < $envelopes['last_response_time'] );
315        } else {
316            $from_cache = false;
317        }
318
319        // Otherwise, ask again.
320        if ( ! $from_cache ) {
321            $wpcom_response = Client::wpcom_json_api_request_as_blog(
322                $path,
323                '2',
324                array(
325                    'user_id'    => $user->ID,
326                    'user_roles' => implode( ',', $user->roles ),
327                ),
328                null,
329                'wpcom'
330            );
331
332            // silently fail...might be helpful to track it?
333            if ( is_wp_error( $wpcom_response ) ) {
334                return array();
335            }
336
337            $envelopes = json_decode( $wpcom_response['body'] );
338
339            if ( ! is_array( $envelopes ) ) {
340                return array();
341            }
342
343            $expiration = isset( $envelopes[0] ) ? $envelopes[0]->ttl : 300;
344
345            // Do not cache if expiration is 0 or we're not using the cache.
346            if ( 0 !== $expiration && $use_cache ) {
347                $envelopes['last_response_time'] = time();
348                set_transient( $cache_key, $envelopes, $expiration );
349            }
350        }
351
352        $hidden_jitms = \Jetpack_Options::get_option( 'hide_jitm' );
353        unset( $envelopes['last_response_time'] );
354
355        /**
356         * Allow adding your own custom JITMs after a set of JITMs has been received.
357         *
358         * @since 1.1.0
359         * @since-jetpack 6.9.0
360         * @since-jetpack 8.3.0 - Added Message path.
361         *
362         * @param array  $envelopes    array of existing JITMs.
363         * @param string $message_path The message path to ask for.
364         */
365        $envelopes = apply_filters( 'jetpack_jitm_received_envelopes', $envelopes, $message_path );
366
367        foreach ( $envelopes as $idx => &$envelope ) {
368
369            $dismissed_feature = isset( $hidden_jitms[ $envelope->feature_class ] ) && is_array( $hidden_jitms[ $envelope->feature_class ] ) ? $hidden_jitms[ $envelope->feature_class ] : null;
370
371            // If the this feature class has been dismissed and the request has not passed the ttl, skip it as it's been dismissed.
372            if ( is_array( $dismissed_feature ) && ( time() - $dismissed_feature['last_dismissal'] < $envelope->expires || $dismissed_feature['number'] >= $envelope->max_dismissal ) ) {
373                unset( $envelopes[ $idx ] );
374                continue;
375            }
376
377            $this->tracking->record_user_event(
378                'jitm_view_client',
379                array(
380                    'jitm_id'           => $envelope->id,
381                    'jitm_message_path' => $message_path,
382                )
383            );
384
385            $url_params = array(
386                'u' => $user->ID,
387            );
388
389            // Get affiliate code and add it to the array of URL parameters.
390            $aff = Partner::init()->get_partner_code( Partner::AFFILIATE_CODE );
391            if ( '' !== $aff ) {
392                $url_params['aff'] = $aff;
393            }
394
395            // Check if the current user has connected their WP.com account
396            // and if not add this information to the the array of URL parameters.
397            if ( ! ( new Manager() )->is_user_connected( $user->ID ) ) {
398                $url_params['query'] = 'unlinked=1';
399            }
400            $envelope->url = esc_url( Redirect::get_url( "jitm-$envelope->id", $url_params ) );
401
402            $stats = new A8c_Mc_Stats();
403
404            $envelope->jitm_stats_url = $stats->build_stats_url( array( 'x_jetpack-jitm' => $envelope->id ) );
405
406            // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
407            // $CTA is not valid per PHPCS, but it is part of the return from WordPress.com, so allowing.
408            if ( $envelope->CTA->hook ) {
409                $envelope->url = apply_filters( 'jitm_' . $envelope->CTA->hook, $envelope->url );
410                unset( $envelope->CTA->hook );
411            }
412            // phpcs:enable
413
414            if ( isset( $envelope->content->hook ) ) {
415                $envelope->content = apply_filters( 'jitm_' . $envelope->content->hook, $envelope->content );
416                unset( $envelope->content->hook );
417            }
418
419            // No point in showing an empty message.
420            if ( empty( $envelope->content->message ) ) {
421                unset( $envelopes[ $idx ] );
422                continue;
423            }
424
425            $envelope->content->icon = $this->generate_icon( $envelope->content->icon, $full_jp_logo_exists );
426            $envelope->message_path  = esc_attr( $message_path );
427
428            $stats->add( 'jitm', $envelope->id . '-viewed' );
429            $stats->do_server_side_stats();
430        }
431
432        return $envelopes;
433    }
434}