Completed
Push — add/creative-mail-compat-jitm ( ea6a8b )
by Jeremy
87:58 queued 79:22
created

jitm_jetpack_creative_mail_install()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
/**
3
 * Jetpack's Post-Connection JITM class.
4
 *
5
 * @package automattic/jetpack-jitm
6
 */
7
8
namespace Automattic\Jetpack\JITMS;
9
10
use Automattic\Jetpack\Connection\Client;
11
use Automattic\Jetpack\Connection\Manager;
12
use Automattic\Jetpack\Partner;
13
use Automattic\Jetpack\Redirect;
14
use Automattic\Jetpack\Tracking;
15
use Automattic\Jetpack\JITMS\JITM;
16
17
/**
18
 * Jetpack just in time messaging through out the admin
19
 *
20
 * @since 5.6.0
21
 */
22
class Post_Connection_JITM extends JITM {
23
24
	/**
25
	 * Tracking object.
26
	 *
27
	 * @var Automattic\Jetpack\Tracking
28
	 *
29
	 * @access private
30
	 */
31
	public $tracking;
32
33
	/**
34
	 * JITM constructor.
35
	 */
36
	public function __construct() {
37
		$this->tracking = new Tracking();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Automattic\Jetpack\Tracking() of type object<Automattic\Jetpack\Tracking> is incompatible with the declared type object<Automattic\Jetpac...attic\Jetpack\Tracking> of property $tracking.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
38
	}
39
40
	/**
41
	 * Prepare actions according to screen and post type.
42
	 *
43
	 * @since 3.8.2
44
	 *
45
	 * @uses Jetpack_Autoupdate::get_possible_failures()
46
	 *
47
	 * @param \WP_Screen $screen WP Core's screen object.
48
	 */
49
	public function prepare_jitms( $screen ) {
50
		parent::prepare_jitms( $screen );
51
		if ( ! in_array(
52
			$screen->id,
53
			array(
54
				'jetpack_page_akismet-key-config',
55
				'admin_page_jetpack_modules',
56
			),
57
			true
58
		) ) {
59
			// Not really a JITM. Don't know where else to put this :) .
60
			add_action( 'admin_notices', array( $this, 'delete_user_update_connection_owner_notice' ) );
61
		}
62
	}
63
64
	/**
65
	 * A special filter for WooCommerce, to set a message based on local state.
66
	 *
67
	 * @param string $content The current message.
68
	 *
69
	 * @return array The new message.
70
	 */
71
	public static function jitm_woocommerce_services_msg( $content ) {
72
		if ( ! function_exists( 'wc_get_base_location' ) ) {
73
			return $content;
74
		}
75
76
		$base_location = wc_get_base_location();
77
78
		switch ( $base_location['country'] ) {
79
			case 'US':
80
				$content->message = esc_html__( 'New free service: Show USPS shipping rates on your store! Added bonus: print shipping labels without leaving WooCommerce.', 'jetpack' );
81
				break;
82
			case 'CA':
83
				$content->message = esc_html__( 'New free service: Show Canada Post shipping rates on your store!', 'jetpack' );
84
				break;
85
			default:
86
				$content->message = '';
87
		}
88
89
		return $content;
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_install() {
98
		return wp_nonce_url(
99
			add_query_arg(
100
				array(
101
					'wc-services-action' => 'install',
102
				),
103
				admin_url( 'admin.php?page=wc-settings' )
104
			),
105
			'wc-services-install'
106
		);
107
	}
108
109
	/**
110
	 * A special filter for WooCommerce Call To Action button.
111
	 *
112
	 * @return string The new CTA
113
	 */
114
	public static function jitm_jetpack_woo_services_activate() {
115
		return wp_nonce_url(
116
			add_query_arg(
117
				array(
118
					'wc-services-action' => 'activate',
119
				),
120
				admin_url( 'admin.php?page=wc-settings' )
121
			),
122
			'wc-services-install'
123
		);
124
	}
125
126
	/**
127
	 * A special filter used in the CTA of a JITM offering to install the Creative Mail plugin.
128
	 *
129
	 * @return string The new CTA
130
	 */
131
	public static function jitm_jetpack_creative_mail_install() {
132
		return wp_nonce_url(
133
			add_query_arg(
134
				array(
135
					'creative-mail-action' => 'install',
136
				),
137
				admin_url( 'edit.php?post_type=feedback' )
138
			),
139
			'creative-mail-install'
140
		);
141
	}
142
143
	/**
144
	 * A special filter used in the CTA of a JITM offering to activate the Creative Mail plugin.
145
	 *
146
	 * @return string The new CTA
147
	 */
148
	public static function jitm_jetpack_creative_mail_activate() {
149
		return wp_nonce_url(
150
			add_query_arg(
151
				array(
152
					'creative-mail-action' => 'activate',
153
				),
154
				admin_url( 'edit.php?post_type=feedback' )
155
			),
156
			'creative-mail-install'
157
		);
158
	}
159
160
	/**
161
	 * This is an entire admin notice dedicated to messaging and handling of the case where a user is trying to delete
162
	 * the connection owner.
163
	 */
164
	public function delete_user_update_connection_owner_notice() {
165
		global $current_screen;
166
167
		/*
168
		 * phpcs:disable WordPress.Security.NonceVerification.Recommended
169
		 *
170
		 * This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
171
		 * page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
172
		 */
173
174
		if ( ! isset( $current_screen->base ) || 'users' !== $current_screen->base ) {
175
			return;
176
		}
177
178
		if ( ! isset( $_REQUEST['action'] ) || 'delete' !== $_REQUEST['action'] ) {
179
			return;
180
		}
181
182
		// Get connection owner or bail.
183
		$connection_manager  = new Manager();
184
		$connection_owner_id = $connection_manager->get_connection_owner_id();
185
		if ( ! $connection_owner_id ) {
186
			return;
187
		}
188
		$connection_owner_userdata = get_userdata( $connection_owner_id );
189
190
		// Bail if we're not trying to delete connection owner.
191
		$user_ids_to_delete = array();
192 View Code Duplication
		if ( isset( $_REQUEST['users'] ) ) {
193
			$user_ids_to_delete = array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['users'] ) );
194
		} elseif ( isset( $_REQUEST['user'] ) ) {
195
			$user_ids_to_delete[] = sanitize_text_field( wp_unslash( $_REQUEST['user'] ) );
196
		}
197
198
		// phpcs:enable
199
		$user_ids_to_delete        = array_map( 'absint', $user_ids_to_delete );
200
		$deleting_connection_owner = in_array( $connection_owner_id, (array) $user_ids_to_delete, true );
201
		if ( ! $deleting_connection_owner ) {
202
			return;
203
		}
204
205
		// Bail if they're trying to delete themselves to avoid confusion.
206
		if ( get_current_user_id() === $connection_owner_id ) {
207
			return;
208
		}
209
210
		// Track it!
211
		if ( method_exists( $this->tracking, 'record_user_event' ) ) {
212
			$this->tracking->record_user_event( 'delete_connection_owner_notice_view' );
213
		}
214
215
		$connected_admins = $connection_manager->get_connected_users( 'jetpack_disconnect' );
216
		$user             = is_a( $connection_owner_userdata, 'WP_User' ) ? esc_html( $connection_owner_userdata->data->user_login ) : '';
217
218
		echo "<div class='notice notice-warning' id='jetpack-notice-switch-connection-owner'>";
219
		echo '<h2>' . esc_html__( 'Important notice about your Jetpack connection:', 'jetpack' ) . '</h2>';
220
		echo '<p>' . sprintf(
221
			/* translators: WordPress User, if available. */
222
			esc_html__( 'Warning! You are about to delete the Jetpack connection owner (%s) for this site, which may cause some of your Jetpack features to stop working.', 'jetpack' ),
223
			esc_html( $user )
224
		) . '</p>';
225
226
		if ( ! empty( $connected_admins ) && count( $connected_admins ) > 1 ) {
227
			echo '<form id="jp-switch-connection-owner" action="" method="post">';
228
			echo "<label for='owner'>" . esc_html__( 'You can choose to transfer connection ownership to one of these already-connected admins:', 'jetpack' ) . ' </label>';
229
230
			$connected_admin_ids = array_map(
231
				function( $connected_admin ) {
232
						return $connected_admin->ID;
233
				},
234
				$connected_admins
235
			);
236
237
			wp_dropdown_users(
238
				array(
239
					'name'    => 'owner',
240
					'include' => array_diff( $connected_admin_ids, array( $connection_owner_id ) ),
241
					'show'    => 'display_name_with_login',
242
				)
243
			);
244
245
			echo '<p>';
246
			submit_button( esc_html__( 'Set new connection owner', 'jetpack' ), 'primary', 'jp-switch-connection-owner-submit', false );
247
			echo '</p>';
248
249
			echo "<div id='jp-switch-user-results'></div>";
250
			echo '</form>';
251
			?>
252
			<script type="text/javascript">
253
				jQuery( document ).ready( function( $ ) {
254
					$( '#jp-switch-connection-owner' ).on( 'submit', function( e ) {
255
						var formData = $( this ).serialize();
256
						var submitBtn = document.getElementById( 'jp-switch-connection-owner-submit' );
257
						var results = document.getElementById( 'jp-switch-user-results' );
258
259
						submitBtn.disabled = true;
260
261
						$.ajax( {
262
							type        : "POST",
263
							url         : "<?php echo esc_url( get_rest_url() . 'jetpack/v4/connection/owner' ); ?>",
264
							data        : formData,
265
							headers     : {
266
								'X-WP-Nonce': "<?php echo esc_js( wp_create_nonce( 'wp_rest' ) ); ?>",
267
							},
268
							success: function() {
269
								results.innerHTML = "<?php esc_html_e( 'Success!', 'jetpack' ); ?>";
270
								setTimeout( function() {
271
									$( '#jetpack-notice-switch-connection-owner' ).hide( 'slow' );
272
								}, 1000 );
273
							}
274
						} ).done( function() {
275
							submitBtn.disabled = false;
276
						} );
277
278
						e.preventDefault();
279
						return false;
280
					} );
281
				} );
282
			</script>
283
			<?php
284
		} else {
285
			echo '<p>' . esc_html__( 'Every Jetpack site needs at least one connected admin for the features to work properly. Please connect to your WordPress.com account via the button below. Once you connect, you may refresh this page to see an option to change the connection owner.', 'jetpack' ) . '</p>';
286
			$connect_url = \Jetpack::init()->build_connect_url( false, false, 'delete_connection_owner_notice' );
287
			echo "<a href='" . esc_url( $connect_url ) . "' target='_blank' rel='noopener noreferrer' class='button-primary'>" . esc_html__( 'Connect to WordPress.com', 'jetpack' ) . '</a>';
288
		}
289
290
		echo '<p>';
291
		printf(
292
			wp_kses(
293
				/* translators: URL to Jetpack support doc regarding the primary user. */
294
				__( "<a href='%s' target='_blank' rel='noopener noreferrer'>Learn more</a> about the connection owner and what will break if you do not have one.", 'jetpack' ),
295
				array(
296
					'a' => array(
297
						'href'   => true,
298
						'target' => true,
299
						'rel'    => true,
300
					),
301
				)
302
			),
303
			esc_url( Redirect::get_url( 'jetpack-support-primary-user' ) )
304
		);
305
		echo '</p>';
306
		echo '<p>';
307
		printf(
308
			wp_kses(
309
				/* translators: URL to contact Jetpack support. */
310
				__( 'As always, feel free to <a href="%s" target="_blank" rel="noopener noreferrer">contact our support team</a> if you have any questions.', 'jetpack' ),
311
				array(
312
					'a' => array(
313
						'href'   => true,
314
						'target' => true,
315
						'rel'    => true,
316
					),
317
				)
318
			),
319
			esc_url( Redirect::get_url( 'jetpack-contact-support' ) )
320
		);
321
		echo '</p>';
322
		echo '</div>';
323
	}
324
325
	/**
326
	 * Dismisses a JITM feature class so that it will no longer be shown.
327
	 *
328
	 * @param string $id The id of the JITM that was dismissed.
329
	 * @param string $feature_class The feature class of the JITM that was dismissed.
330
	 *
331
	 * @return bool Always true.
332
	 */
333
	public function dismiss( $id, $feature_class ) {
334
		$this->tracking->record_user_event(
335
			'jitm_dismiss_client',
336
			array(
337
				'jitm_id'       => $id,
338
				'feature_class' => $feature_class,
339
			)
340
		);
341
		$this->save_dismiss( $feature_class );
342
		return true;
343
	}
344
345
	/**
346
	 * Asks the wpcom API for the current message to display keyed on query string and message path
347
	 *
348
	 * @param string $message_path The message path to ask for.
349
	 * @param string $query The query string originally from the front end.
350
	 * @param bool   $full_jp_logo_exists If there is a full Jetpack logo already on the page.
351
	 *
352
	 * @return array The JITM's to show, or an empty array if there is nothing to show
353
	 */
354
	public function get_messages( $message_path, $query, $full_jp_logo_exists ) {
355
		// WooCommerce Services.
356
		add_filter( 'jitm_woocommerce_services_msg', array( $this, 'jitm_woocommerce_services_msg' ) );
357
		add_filter( 'jitm_jetpack_woo_services_install', array( $this, 'jitm_jetpack_woo_services_install' ) );
358
		add_filter( 'jitm_jetpack_woo_services_activate', array( $this, 'jitm_jetpack_woo_services_activate' ) );
359
360
		// Creative Mail.
361
		add_filter( 'jitm_jetpack_creative_mail_install', array( $this, 'jitm_jetpack_creative_mail_install' ) );
362
		add_filter( 'jitm_jetpack_creative_mail_activate', array( $this, 'jitm_jetpack_creative_mail_activate' ) );
363
364
		$user = wp_get_current_user();
365
366
		// Unauthenticated or invalid requests just bail.
367
		if ( ! $user ) {
368
			return array();
369
		}
370
371
		$user_roles = implode( ',', $user->roles );
372
		$site_id    = \Jetpack_Options::get_option( 'id' );
373
374
		// Build our jitm request.
375
		$path = add_query_arg(
376
			array(
377
				'external_user_id' => urlencode_deep( $user->ID ),
378
				'user_roles'       => urlencode_deep( $user_roles ),
379
				'query_string'     => urlencode_deep( $query ),
380
				'mobile_browser'   => jetpack_is_mobile( 'smart' ) ? 1 : 0,
381
				'_locale'          => get_user_locale(),
382
			),
383
			sprintf( '/sites/%d/jitm/%s', $site_id, $message_path )
384
		);
385
386
		// Attempt to get from cache.
387
		$envelopes = get_transient( 'jetpack_jitm_' . substr( md5( $path ), 0, 31 ) );
388
389
		// If something is in the cache and it was put in the cache after the last sync we care about, use it.
390
		$use_cache = false;
391
392
		/** This filter is documented in class.jetpack.php */
393
		if ( apply_filters( 'jetpack_just_in_time_msg_cache', false ) ) {
394
			$use_cache = true;
395
		}
396
397
		if ( $use_cache ) {
398
			$last_sync  = (int) get_transient( 'jetpack_last_plugin_sync' );
399
			$from_cache = $envelopes && $last_sync > 0 && $last_sync < $envelopes['last_response_time'];
400
		} else {
401
			$from_cache = false;
402
		}
403
404
		// Otherwise, ask again.
405
		if ( ! $from_cache ) {
406
			$wpcom_response = Client::wpcom_json_api_request_as_blog(
407
				$path,
408
				'2',
409
				array(
410
					'user_id'    => $user->ID,
411
					'user_roles' => implode( ',', $user->roles ),
412
				),
413
				null,
414
				'wpcom'
415
			);
416
417
			// silently fail...might be helpful to track it?
418
			if ( is_wp_error( $wpcom_response ) ) {
419
				return array();
420
			}
421
422
			$envelopes = json_decode( $wpcom_response['body'] );
423
424
			if ( ! is_array( $envelopes ) ) {
425
				return array();
426
			}
427
428
			$expiration = isset( $envelopes[0] ) ? $envelopes[0]->ttl : 300;
429
430
			// Do not cache if expiration is 0 or we're not using the cache.
431
			if ( 0 !== $expiration && $use_cache ) {
432
				$envelopes['last_response_time'] = time();
433
434
				set_transient( 'jetpack_jitm_' . substr( md5( $path ), 0, 31 ), $envelopes, $expiration );
435
			}
436
		}
437
438
		$hidden_jitms = \Jetpack_Options::get_option( 'hide_jitm' );
439
		unset( $envelopes['last_response_time'] );
440
441
		/**
442
		 * Allow adding your own custom JITMs after a set of JITMs has been received.
443
		 *
444
		 * @since 6.9.0
445
		 * @since 8.3.0 - Added Message path.
446
		 *
447
		 * @param array  $envelopes    array of existing JITMs.
448
		 * @param string $message_path The message path to ask for.
449
		 */
450
		$envelopes = apply_filters( 'jetpack_jitm_received_envelopes', $envelopes, $message_path );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $message_path.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
451
452
		foreach ( $envelopes as $idx => &$envelope ) {
453
454
			$dismissed_feature = isset( $hidden_jitms[ $envelope->feature_class ] ) && is_array( $hidden_jitms[ $envelope->feature_class ] ) ? $hidden_jitms[ $envelope->feature_class ] : null;
455
456
			// If the this feature class has been dismissed and the request has not passed the ttl, skip it as it's been dismissed.
457
			if ( is_array( $dismissed_feature ) && ( time() - $dismissed_feature['last_dismissal'] < $envelope->expires || $dismissed_feature['number'] >= $envelope->max_dismissal ) ) {
458
				unset( $envelopes[ $idx ] );
459
				continue;
460
			}
461
462
			$this->tracking->record_user_event(
463
				'jitm_view_client',
464
				array(
465
					'jitm_id' => $envelope->id,
466
				)
467
			);
468
469
			$normalized_site_url = \Jetpack::build_raw_urls( get_home_url() );
470
471
			$url_params = array(
472
				'source' => "jitm-$envelope->id",
473
				'site'   => $normalized_site_url,
474
				'u'      => $user->ID,
475
			);
476
477
			// Get affiliate code and add it to the array of URL parameters.
478
			$aff = Partner::init()->get_partner_code( Partner::AFFILIATE_CODE );
479
			if ( '' !== $aff ) {
480
				$url_params['aff'] = $aff;
481
			}
482
483
			$envelope->url = add_query_arg( $url_params, 'https://jetpack.com/redirect/' );
484
485
			$envelope->jitm_stats_url = \Jetpack::build_stats_url( array( 'x_jetpack-jitm' => $envelope->id ) );
486
487
			// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
488
			// $CTA is not valid per PHPCS, but it is part of the return from WordPress.com, so allowing.
489
			if ( $envelope->CTA->hook ) {
490
				$envelope->url = apply_filters( 'jitm_' . $envelope->CTA->hook, $envelope->url );
491
				unset( $envelope->CTA->hook );
492
			}
493
			// phpcs:enable
494
495
			if ( isset( $envelope->content->hook ) ) {
496
				$envelope->content = apply_filters( 'jitm_' . $envelope->content->hook, $envelope->content );
497
				unset( $envelope->content->hook );
498
			}
499
500
			// No point in showing an empty message.
501
			if ( empty( $envelope->content->message ) ) {
502
				unset( $envelopes[ $idx ] );
503
				continue;
504
			}
505
506
			$envelope->content->icon = $this->generate_icon( $envelope->content->icon, $full_jp_logo_exists );
507
508
			$jetpack = \Jetpack::init();
509
			$jetpack->stat( 'jitm', $envelope->id . '-viewed-' . JETPACK__VERSION );
510
			$jetpack->do_stats( 'server_side' );
511
		}
512
513
		return $envelopes;
514
	}
515
516
}
517