Completed
Push — update/idc-endpoint-migration ( 2ba746...f2e26f )
by
unknown
97:48 queued 87:47
created

Identity_Crisis::sync_idc_optin()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Identity_Crisis package.
4
 *
5
 * @package  automattic/jetpack-identity-crisis
6
 */
7
8
namespace Automattic\Jetpack;
9
10
use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
11
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12
use Automattic\Jetpack\Connection\Urls;
13
use Automattic\Jetpack\Constants as Constants;
14
use Automattic\Jetpack\Status as Status;
15
use Automattic\Jetpack\Tracking as Tracking;
16
use Jetpack_Options;
17
use Jetpack_Tracks_Client;
18
use WP_Error;
19
20
/**
21
 * This class will handle everything involved with fixing an Identity Crisis.
22
 *
23
 * @since 4.4.0
24
 */
25
class Identity_Crisis {
26
27
	/**
28
	 * Package Version
29
	 */
30
	const PACKAGE_VERSION = '0.2.0-alpha';
31
32
	/**
33
	 * Instance of the object.
34
	 *
35
	 * @var Identity_Crisis
36
	 **/
37
	private static $instance = null;
38
39
	/**
40
	 * The wpcom value of the home URL.
41
	 *
42
	 * @var string
43
	 */
44
	public static $wpcom_home_url;
45
46
	/**
47
	 * Has safe mode been confirmed?
48
	 *
49
	 * @var bool
50
	 */
51
	public static $is_safe_mode_confirmed;
52
53
	/**
54
	 * The current screen, which is set if the current user is a non-admin and this is an admin page.
55
	 *
56
	 * @var WP_Screen
57
	 */
58
	public static $current_screen;
59
60
	/**
61
	 * Initializer.
62
	 *
63
	 * @return object
64
	 */
65
	public static function init() {
66
		if ( is_null( self::$instance ) ) {
67
			self::$instance = new Identity_Crisis();
68
		}
69
70
		return self::$instance;
71
	}
72
73
	/**
74
	 * Class constructor.
75
	 *
76
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
77
	 */
78
	private function __construct() {
79
		add_action( 'jetpack_sync_processed_actions', array( $this, 'maybe_clear_migrate_option' ) );
80
		add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\IdentityCrisis\\REST_Endpoints', 'initialize_rest_api' ) );
81
		add_action( 'jetpack_idc_disconnect', array( __CLASS__, 'do_jetpack_idc_disconnect' ) );
82
83
		$urls_in_crisis = self::check_identity_crisis();
84
		if ( false === $urls_in_crisis ) {
85
			return;
86
		}
87
88
		self::$wpcom_home_url = $urls_in_crisis['wpcom_home'];
89
		add_action( 'init', array( $this, 'wordpress_init' ) );
90
	}
91
92
	/**
93
	 * Disconnect current connection and clear IDC options.
94
	 */
95
	public static function do_jetpack_idc_disconnect() {
96
		$connection = new Connection_Manager();
97
98
		// If the site is in an IDC because sync is not allowed,
99
		// let's make sure to not disconnect the production site.
100
		if ( ! self::validate_sync_error_idc_option() ) {
101
			$connection->disconnect_site( true );
102
		} else {
103
			$connection->disconnect_site( false );
104
		}
105
106
		// Clear IDC options.
107
		self::clear_all_idc_options();
108
	}
109
110
	/**
111
	 * Gets the link to the support document used to explain Safe Mode to users.
112
	 *
113
	 * @return string
114
	 */
115
	public static function get_safe_mod_doc_url() {
116
		return Redirect::get_url( 'jetpack-support-safe-mode' );
117
	}
118
119
	/**
120
	 * This method loops through the array of processed items from sync and checks if one of the items was the
121
	 * home_url or site_url callable. If so, then we delete the jetpack_migrate_for_idc option.
122
	 *
123
	 * @param array $processed_items Array of processed items that were synced to WordPress.com.
124
	 */
125
	public function maybe_clear_migrate_option( $processed_items ) {
126
		foreach ( (array) $processed_items as $item ) {
127
128
			// First, is this item a jetpack_sync_callable action? If so, then proceed.
129
			$callable_args = ( is_array( $item ) && isset( $item[0], $item[1] ) && 'jetpack_sync_callable' === $item[0] )
130
				? $item[1]
131
				: null;
132
133
			// Second, if $callable_args is set, check if the callable was home_url or site_url. If so,
134
			// clear the migrate option.
135
			if (
136
				isset( $callable_args, $callable_args[0] )
137
				&& ( 'home_url' === $callable_args[0] || 'site_url' === $callable_args[1] )
138
			) {
139
				Jetpack_Options::delete_option( 'migrate_for_idc' );
140
				break;
141
			}
142
		}
143
	}
144
145
	/**
146
	 * WordPress init.
147
	 *
148
	 * @return void
149
	 */
150
	public function wordpress_init() {
151
		if ( ! current_user_can( 'jetpack_disconnect' ) && is_admin() ) {
152
			add_action( 'admin_notices', array( $this, 'display_non_admin_idc_notice' ) );
153
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_idc_notice_files' ) );
154
			add_action( 'current_screen', array( $this, 'non_admins_current_screen_check' ) );
155
156
			return;
157
		}
158
159
		if (
160
			isset( $_GET['jetpack_idc_clear_confirmation'], $_GET['_wpnonce'] ) &&
161
			wp_verify_nonce( $_GET['_wpnonce'], 'jetpack_idc_clear_confirmation' )
162
		) {
163
			Jetpack_Options::delete_option( 'safe_mode_confirmed' );
164
			self::$is_safe_mode_confirmed = false;
165
		} else {
166
			self::$is_safe_mode_confirmed = (bool) Jetpack_Options::get_option( 'safe_mode_confirmed' );
167
		}
168
169
		// 121 Priority so that it's the most inner Jetpack item in the admin bar.
170
		add_action( 'admin_bar_menu', array( $this, 'display_admin_bar_button' ), 121 );
171
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_bar_css' ) );
172
173
		if ( is_admin() && ! self::$is_safe_mode_confirmed ) {
174
			add_action( 'admin_notices', array( $this, 'display_idc_notice' ) );
175
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_idc_notice_files' ) );
176
		}
177
	}
178
179
	/**
180
	 * Non-admins current screen check.
181
	 *
182
	 * @param object $current_screen Current screen.
183
	 *
184
	 * @return null
185
	 */
186
	public function non_admins_current_screen_check( $current_screen ) {
187
		self::$current_screen = $current_screen;
188
		if ( isset( $current_screen->id ) && 'toplevel_page_jetpack' === $current_screen->id ) {
189
			return null;
190
		}
191
192
		// If the user has dismissed the notice, and we're not currently on a Jetpack page,
193
		// then do not show the non-admin notice.
194
		if ( isset( $_COOKIE, $_COOKIE['jetpack_idc_dismiss_notice'] ) ) {
195
			remove_action( 'admin_notices', array( $this, 'display_non_admin_idc_notice' ) );
196
			remove_action( 'admin_enqueue_scripts', array( $this, 'enqueue_idc_notice_files' ) );
197
		}
198
199
		return null;
200
	}
201
202
	/**
203
	 * Renders the admin bar button.
204
	 *
205
	 * @return void
206
	 */
207
	public function display_admin_bar_button() {
208
		global $wp_admin_bar;
209
210
		$href = is_admin()
211
			? add_query_arg( 'jetpack_idc_clear_confirmation', '1' )
212
			: add_query_arg( 'jetpack_idc_clear_confirmation', '1', admin_url() );
213
214
		$href = wp_nonce_url( $href, 'jetpack_idc_clear_confirmation' );
215
216
		$title = sprintf(
217
			'<span class="jp-idc-admin-bar">%s %s</span>',
218
			'<span class="dashicons dashicons-warning"></span>',
219
			esc_html__( 'Jetpack Safe Mode', 'jetpack' )
220
		);
221
222
		$menu = array(
223
			'id'     => 'jetpack-idc',
224
			'title'  => $title,
225
			'href'   => esc_url( $href ),
226
			'parent' => 'top-secondary',
227
		);
228
229
		if ( ! self::$is_safe_mode_confirmed ) {
230
			$menu['meta'] = array(
231
				'class' => 'hide',
232
			);
233
		}
234
235
		$wp_admin_bar->add_node( $menu );
236
	}
237
238
	/**
239
	 * Checks if the site is currently in an identity crisis.
240
	 *
241
	 * @return array|bool Array of options that are in a crisis, or false if everything is OK.
242
	 */
243
	public static function check_identity_crisis() {
244
		$connection = new Connection_Manager( 'jetpack' );
245
246
		if ( ! $connection->is_connected() || ( new Status() )->is_offline_mode() || ! self::validate_sync_error_idc_option() ) {
247
			return false;
248
		}
249
250
		return Jetpack_Options::get_option( 'sync_error_idc' );
251
	}
252
253
	/**
254
	 * Prepare URL for display.
255
	 *
256
	 * @param string $url URL to display.
257
	 *
258
	 * @return string
259
	 */
260
	public static function prepare_url_for_display( $url ) {
261
		return untrailingslashit( self::normalize_url_protocol_agnostic( $url ) );
262
	}
263
264
	/**
265
	 * Clears all IDC specific options. This method is used on disconnect and reconnect.
266
	 *
267
	 * @return void
268
	 */
269
	public static function clear_all_idc_options() {
270
		// If the site is currently in IDC, let's also clear the VaultPress connection options.
271
		// We have to check if the site is in IDC, otherwise we'd be clearing the VaultPress
272
		// connection any time the Jetpack connection is cycled.
273
		if ( self::validate_sync_error_idc_option() ) {
274
			delete_option( 'vaultpress' );
275
			delete_option( 'vaultpress_auto_register' );
276
		}
277
278
		Jetpack_Options::delete_option(
279
			array(
280
				'sync_error_idc',
281
				'safe_mode_confirmed',
282
				'migrate_for_idc',
283
			)
284
		);
285
	}
286
287
	/**
288
	 * Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
289
	 *
290
	 * @return bool
291
	 * @since 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
292
	 *
293
	 * @since 4.4.0
294
	 */
295
	public static function validate_sync_error_idc_option() {
296
		$is_valid = false;
297
298
		// Is the site opted in and does the stored sync_error_idc option match what we now generate?
299
		$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
300
		if ( $sync_error && self::sync_idc_optin() ) {
301
			$local_options = self::get_sync_error_idc_option();
302
			// Ensure all values are set.
303
			if ( isset( $sync_error['home'] ) && isset( $local_options['home'] ) && isset( $sync_error['siteurl'] ) && isset( $local_options['siteurl'] ) ) {
304
				// If the WP.com expected home and siteurl match local home and siteurl it is not valid IDC.
305
				if (
306
					isset( $sync_error['wpcom_home'] ) &&
307
					isset( $sync_error['wpcom_siteurl'] ) &&
308
					$sync_error['wpcom_home'] === $local_options['home'] &&
309
					$sync_error['wpcom_siteurl'] === $local_options['siteurl']
310
				) {
311
					$is_valid = false;
312
					// Enable migrate_for_idc so that sync actions are accepted.
313
					Jetpack_Options::update_option( 'migrate_for_idc', true );
314
				} elseif ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
315
					$is_valid = true;
316
				}
317
			}
318
		}
319
320
		/**
321
		 * Filters whether the sync_error_idc option is valid.
322
		 *
323
		 * @param bool $is_valid If the sync_error_idc is valid or not.
324
		 *
325
		 * @since 4.4.0
326
		 */
327
		$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
328
329
		if ( ! $is_valid && $sync_error ) {
330
			// Since the option exists, and did not validate, delete it.
331
			Jetpack_Options::delete_option( 'sync_error_idc' );
332
		}
333
334
		return $is_valid;
335
	}
336
337
	/**
338
	 * Normalizes a url by doing three things:
339
	 *  - Strips protocol
340
	 *  - Strips www
341
	 *  - Adds a trailing slash
342
	 *
343
	 * @param string $url URL to parse.
344
	 *
345
	 * @return WP_Error|string
346
	 * @since 4.4.0
347
	 */
348 View Code Duplication
	public static function normalize_url_protocol_agnostic( $url ) {
349
		$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
350
		if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parsed_url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
351
			return new WP_Error(
352
				'cannot_parse_url',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'cannot_parse_url'.

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...
353
				sprintf(
354
				/* translators: %s: URL to parse. */
355
					esc_html__( 'Cannot parse URL %s', 'jetpack' ),
356
					$url
357
				)
358
			);
359
		}
360
361
		// Strip www and protocols.
362
		$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
363
364
		return $url;
365
	}
366
367
	/**
368
	 * Gets the value that is to be saved in the jetpack_sync_error_idc option.
369
	 *
370
	 * @param array $response HTTP response.
371
	 *
372
	 * @return array Array of the local urls, wpcom urls, and error code.
373
	 * @since 4.4.0
374
	 * @since 5.4.0 Add transient since home/siteurl retrieved directly from DB.
375
	 */
376
	public static function get_sync_error_idc_option( $response = array() ) {
377
		// Since the local options will hit the database directly, store the values
378
		// in a transient to allow for autoloading and caching on subsequent views.
379
		$local_options = get_transient( 'jetpack_idc_local' );
380
		if ( false === $local_options ) {
381
			$local_options = array(
382
				'home'    => Urls::home_url(),
383
				'siteurl' => Urls::site_url(),
384
			);
385
			set_transient( 'jetpack_idc_local', $local_options, MINUTE_IN_SECONDS );
386
		}
387
388
		$options = array_merge( $local_options, $response );
389
390
		$returned_values = array();
391
		foreach ( $options as $key => $option ) {
392
			if ( 'error_code' === $key ) {
393
				$returned_values[ $key ] = $option;
394
				continue;
395
			}
396
397
			$normalized_url = self::normalize_url_protocol_agnostic( $option );
398
			if ( is_wp_error( $normalized_url ) ) {
399
				continue;
400
			}
401
402
			$returned_values[ $key ] = $normalized_url;
403
		}
404
405
		set_transient( 'jetpack_idc_option', $returned_values, MINUTE_IN_SECONDS );
406
407
		return $returned_values;
408
	}
409
410
	/**
411
	 * Returns the value of the jetpack_sync_idc_optin filter, or constant.
412
	 * If set to true, the site will be put into staging mode.
413
	 *
414
	 * @return bool
415
	 * @since 4.3.2
416
	 */
417
	public static function sync_idc_optin() {
418
		if ( Constants::is_defined( 'JETPACK_SYNC_IDC_OPTIN' ) ) {
419
			$default = Constants::get_constant( 'JETPACK_SYNC_IDC_OPTIN' );
420
		} else {
421
			$default = ! Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
422
		}
423
424
		/**
425
		 * Allows sites to opt in for IDC mitigation which blocks the site from syncing to WordPress.com when the home
426
		 * URL or site URL do not match what WordPress.com expects. The default value is either true, or the value of
427
		 * JETPACK_SYNC_IDC_OPTIN constant if set.
428
		 *
429
		 * @param bool $default Whether the site is opted in to IDC mitigation.
430
		 *
431
		 * @since 4.3.2
432
		 */
433
		return (bool) apply_filters( 'jetpack_sync_idc_optin', $default );
434
	}
435
436
	/**
437
	 * Does the current admin page have help tabs?
438
	 *
439
	 * @return bool
440
	 */
441
	public function admin_page_has_help_tabs() {
442
		if ( ! function_exists( 'get_current_screen' ) ) {
443
			return false;
444
		}
445
446
		$current_screen = get_current_screen();
447
		$tabs           = $current_screen->get_help_tabs();
448
449
		return ! empty( $tabs );
450
	}
451
452
	/**
453
	 * Renders the non-admin IDC notice.
454
	 *
455
	 * @return void
456
	 */
457
	public function display_non_admin_idc_notice() {
458
		$classes = 'jp-idc-notice inline is-non-admin notice notice-warning';
459
		if ( isset( self::$current_screen ) && 'toplevel_page_jetpack' !== self::$current_screen->id ) {
460
			$classes .= ' is-dismissible';
461
		}
462
463
		if ( $this->admin_page_has_help_tabs() ) {
464
			$classes .= ' has-help-tabs';
465
		}
466
		?>
467
468
		<div class="<?php echo esc_attr( $classes ); ?>">
469
			<?php $this->render_notice_header(); ?>
470
			<div class="jp-idc-notice__content-header">
471
				<h3 class="jp-idc-notice__content-header__lead">
472
					<?php echo $this->get_non_admin_notice_text(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
473
				</h3>
474
475
				<p class="jp-idc-notice__content-header__explanation">
476
					<?php echo $this->get_non_admin_contact_admin_text(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
477
				</p>
478
			</div>
479
		</div>
480
		<?php
481
	}
482
483
	/**
484
	 * First "step" of the IDC mitigation. Will provide some messaging and two options/buttons.
485
	 * "Confirm Staging" - Dismiss the notice and continue on with our lives in staging mode.
486
	 * "Fix Jetpack Connection" - Will disconnect the site and start the mitigation...
487
	 *
488
	 * @return void
489
	 */
490
	public function display_idc_notice() {
491
		$classes = 'jp-idc-notice inline notice notice-warning';
492
		if ( $this->admin_page_has_help_tabs() ) {
493
			$classes .= ' has-help-tabs';
494
		}
495
		?>
496
		<div class="<?php echo esc_attr( $classes ); ?>">
497
			<?php $this->render_notice_header(); ?>
498
			<?php $this->render_notice_first_step(); ?>
499
			<?php $this->render_notice_second_step(); ?>
500
		</div>
501
		<?php
502
	}
503
504
	/**
505
	 * Enqueue CSS for the admin bar.
506
	 *
507
	 * @return void
508
	 */
509
	public function enqueue_admin_bar_css() {
510
511
		$build_assets = require __DIR__ . '/../build/index.asset.php';
512
513
		wp_enqueue_style(
514
			'jetpack-idc-admin-bar-css',
515
			plugin_dir_url( __DIR__ ) . 'build/css/jetpack-idc-admin-bar.css',
516
			array( 'dashicons' ),
517
			$build_assets['version']
518
		);
519
	}
520
521
	/**
522
	 * Enqueue scripts for the notice.
523
	 *
524
	 * @return void
525
	 */
526
	public function enqueue_idc_notice_files() {
527
		$build_assets                   = require __DIR__ . '/../build/index.asset.php';
528
		$build_assets['dependencies'][] = 'jquery';
529
530
		wp_enqueue_script(
531
			'jetpack-idc-js',
532
			plugin_dir_url( __DIR__ ) . 'build/index.js',
533
			$build_assets['dependencies'],
534
			$build_assets['version'],
535
			true
536
		);
537
538
		wp_localize_script(
539
			'jetpack-idc-js',
540
			'idcL10n',
541
			array(
542
				'apiRoot'         => esc_url_raw( rest_url() ),
543
				'nonce'           => wp_create_nonce( 'wp_rest' ),
544
				'tracksUserData'  => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
545
				'currentUrl'      => remove_query_arg( '_wpnonce', remove_query_arg( 'jetpack_idc_clear_confirmation' ) ),
546
				'tracksEventData' => array(
547
					'isAdmin'       => current_user_can( 'jetpack_disconnect' ),
548
					'currentScreen' => self::$current_screen ? self::$current_screen->id : false,
549
				),
550
			)
551
		);
552
553
		if ( ! wp_style_is( 'jetpack-dops-style', 'registered' ) ) {
554
			wp_register_style(
555
				'jetpack-dops-style',
556
				plugin_dir_url( __DIR__ ) . 'src/_inc/admin.css', // TODO Detangle style depenedencies instead of copying whole css file.
557
				array(),
558
				self::PACKAGE_VERSION
559
			);
560
		}
561
562
		wp_enqueue_style(
563
			'jetpack-idc-admin-bar-css',
564
			plugin_dir_url( __DIR__ ) . 'build/css/jetpack-idc-admin-bar.css',
565
			array( 'jetpack-dops-style' ),
566
			self::PACKAGE_VERSION
567
		);
568
		wp_enqueue_style(
569
			'jetpack-idc-css',
570
			plugin_dir_url( __DIR__ ) . 'build/css/jetpack-idc.css',
571
			array( 'jetpack-dops-style' ),
572
			self::PACKAGE_VERSION
573
		);
574
575
		// Register and Enqueue jp-tracks-functions.
576
		Tracking::register_tracks_functions_scripts( true );
577
	}
578
579
	/**
580
	 * Renders the notice header.
581
	 *
582
	 * @return void
583
	 */
584
	public function render_notice_header() {
585
		?>
586
		<div class="jp-idc-notice__header">
587
			<div class="jp-idc-notice__header__emblem">
588
				<?php
589
				$jetpack_logo = new Jetpack_Logo();
590
				echo $jetpack_logo->get_jp_emblem(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
591
				?>
592
			</div>
593
			<p class="jp-idc-notice__header__text">
594
				<?php esc_html_e( 'Jetpack Safe Mode', 'jetpack' ); ?>
595
			</p>
596
		</div>
597
598
		<div class="jp-idc-notice__separator"></div>
599
		<?php
600
	}
601
602
	/**
603
	 * Is a container for the error notices.
604
	 * Will be shown/controlled by jQuery in idc-notice.js.
605
	 *
606
	 * @return void
607
	 */
608
	public function render_error_notice() {
609
		?>
610
		<div class="jp-idc-error__notice dops-notice is-error">
611
			<svg class="gridicon gridicons-notice dops-notice__icon" height="24" width="24" viewBox="0 0 24 24">
612
				<g>
613
					<path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm1 15h-2v-2h2v2zm0-4h-2l-.5-6h3l-.5 6z"></path>
614
				</g>
615
			</svg>
616
			<div class="dops-notice__content">
617
				<span class="dops-notice__text">
618
					<?php esc_html_e( 'Something went wrong:', 'jetpack' ); ?>
619
					<span class="jp-idc-error__desc"></span>
620
				</span>
621
				<a class="dops-notice__action" href="javascript:void(0);">
622
					<span id="jp-idc-error__action">
623
						<?php esc_html_e( 'Try Again', 'jetpack' ); ?>
624
					</span>
625
				</a>
626
			</div>
627
		</div>
628
		<?php
629
	}
630
631
	/**
632
	 * Renders the first step notice.
633
	 *
634
	 * @return void
635
	 */
636
	public function render_notice_first_step() {
637
		?>
638
		<div class="jp-idc-notice__first-step">
639
			<div class="jp-idc-notice__content-header">
640
				<h3 class="jp-idc-notice__content-header__lead">
641
					<?php echo $this->get_first_step_header_lead(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
642
				</h3>
643
644
				<p class="jp-idc-notice__content-header__explanation">
645
					<?php echo $this->get_first_step_header_explanation(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
646
				</p>
647
			</div>
648
649
			<?php $this->render_error_notice(); ?>
650
651
			<div class="jp-idc-notice__actions">
652
				<div class="jp-idc-notice__action">
653
					<p class="jp-idc-notice__action__explanation">
654
						<?php echo $this->get_confirm_safe_mode_action_explanation(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
655
					</p>
656
					<button id="jp-idc-confirm-safe-mode-action" class="dops-button">
657
						<?php echo $this->get_confirm_safe_mode_button_text(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
658
					</button>
659
				</div>
660
661
				<div class="jp-idc-notice__action">
662
					<p class="jp-idc-notice__action__explanation">
663
						<?php echo $this->get_first_step_fix_connection_action_explanation(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
664
					</p>
665
					<button id="jp-idc-fix-connection-action" class="dops-button">
666
						<?php echo $this->get_first_step_fix_connection_button_text(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
667
					</button>
668
				</div>
669
			</div>
670
		</div>
671
		<?php
672
	}
673
674
	/**
675
	 * Renders the second step notice.
676
	 *
677
	 * @return void
678
	 */
679
	public function render_notice_second_step() {
680
		?>
681
		<div class="jp-idc-notice__second-step">
682
			<div class="jp-idc-notice__content-header">
683
				<h3 class="jp-idc-notice__content-header__lead">
684
					<?php echo $this->get_second_step_header_lead(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
685
				</h3>
686
			</div>
687
688
			<?php $this->render_error_notice(); ?>
689
690
			<div class="jp-idc-notice__actions">
691
				<div class="jp-idc-notice__action">
692
					<p class="jp-idc-notice__action__explanation">
693
						<?php echo $this->get_migrate_site_action_explanation(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
694
					</p>
695
					<button id="jp-idc-migrate-action" class="dops-button">
696
						<?php echo $this->get_migrate_site_button_text(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
697
					</button>
698
				</div>
699
700
				<div class="jp-idc-notice__action">
701
					<p class="jp-idc-notice__action__explanation">
702
						<?php echo $this->get_start_fresh_action_explanation(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
703
					</p>
704
					<button id="jp-idc-reconnect-site-action" class="dops-button">
705
						<?php echo $this->get_start_fresh_button_text(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
706
					</button>
707
				</div>
708
709
			</div>
710
711
			<p class="jp-idc-notice__unsure-prompt">
712
				<?php echo $this->get_unsure_prompt(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
713
			</p>
714
		</div>
715
		<?php
716
	}
717
718
	/**
719
	 * Returns the first step header lead.
720
	 *
721
	 * @return string
722
	 */
723 View Code Duplication
	public function get_first_step_header_lead() {
724
		$html = wp_kses(
725
			sprintf(
726
			/* translators: %s: Safe mode docs URL and site URL. */
727
				__( 'Jetpack has been placed into <a href="%1$s">Safe mode</a> because we noticed this is an exact copy of <a href="%2$s">%3$s</a>.', 'jetpack' ),
728
				esc_url( self::get_safe_mod_doc_url() ),
729
				esc_url( self::$wpcom_home_url ),
730
				self::prepare_url_for_display( esc_url_raw( self::$wpcom_home_url ) )
731
			),
732
			array( 'a' => array( 'href' => array() ) )
733
		);
734
735
		/**
736
		 * Allows overriding of the default header text in the first step of the Safe Mode notice.
737
		 *
738
		 * @param string $html The HTML to be displayed.
739
		 *
740
		 * @since 4.4.0
741
		 */
742
		return apply_filters( 'jetpack_idc_first_step_header_lead', $html );
743
	}
744
745
	/**
746
	 * Returns the first step header explanation.
747
	 *
748
	 * @return string
749
	 */
750 View Code Duplication
	public function get_first_step_header_explanation() {
751
		$html = wp_kses(
752
			sprintf(
753
			/* translators: %s: Safe mode docs URL. */
754
				__( 'Please confirm Safe Mode or fix the Jetpack connection. Select one of the options below or <a href="%1$s">learn more about Safe Mode</a>.', 'jetpack' ),
755
				esc_url( self::get_safe_mod_doc_url() )
756
			),
757
			array( 'a' => array( 'href' => array() ) )
758
		);
759
760
		/**
761
		 * Allows overriding of the default header explanation text in the first step of the Safe Mode notice.
762
		 *
763
		 * @param string $html The HTML to be displayed.
764
		 *
765
		 * @since 4.4.0
766
		 */
767
		return apply_filters( 'jetpack_idc_first_step_header_explanation', $html );
768
	}
769
770
	/**
771
	 * Returns the confirm safe mode explanation.
772
	 *
773
	 * @return string
774
	 */
775 View Code Duplication
	public function get_confirm_safe_mode_action_explanation() {
776
		$html = wp_kses(
777
			sprintf(
778
			/* translators: %s: Site URL. */
779
				__( 'Is this website a temporary duplicate of <a href="%1$s">%2$s</a> for the purposes of testing, staging or development? If so, we recommend keeping it in Safe Mode.', 'jetpack' ),
780
				esc_url( untrailingslashit( self::$wpcom_home_url ) ),
781
				self::prepare_url_for_display( esc_url( self::$wpcom_home_url ) )
782
			),
783
			array( 'a' => array( 'href' => array() ) )
784
		);
785
786
		/**
787
		 * Allows overriding of the default text used to explain the confirm safe mode action.
788
		 *
789
		 * @param string $html The HTML to be displayed.
790
		 *
791
		 * @since 4.4.0
792
		 */
793
		return apply_filters( 'jetpack_idc_confirm_safe_mode_explanation', $html );
794
	}
795
796
	/**
797
	 * Returns the confirm safe mode button text.
798
	 *
799
	 * @return string
800
	 */
801
	public function get_confirm_safe_mode_button_text() {
802
		$string = esc_html__( 'Confirm Safe Mode', 'jetpack' );
803
804
		/**
805
		 * Allows overriding of the default text used for the confirm safe mode action button.
806
		 *
807
		 * @param string $string The string to be displayed.
808
		 *
809
		 * @since 4.4.0
810
		 */
811
		return apply_filters( 'jetpack_idc_confirm_safe_mode_button_text', $string );
812
	}
813
814
	/**
815
	 * Returns the first step fix connection action explanation.
816
	 *
817
	 * @return string
818
	 */
819 View Code Duplication
	public function get_first_step_fix_connection_action_explanation() {
820
		$html = wp_kses(
821
			sprintf(
822
			/* translators: %s: Site URL. */
823
				__( 'If this is a separate and new website, or the new home of <a href="%1$s">%2$s</a>, we recommend turning Safe Mode off, and re-establishing your connection to WordPress.com.', 'jetpack' ),
824
				esc_url( untrailingslashit( self::$wpcom_home_url ) ),
825
				self::prepare_url_for_display( esc_url( self::$wpcom_home_url ) )
826
			),
827
			array( 'a' => array( 'href' => array() ) )
828
		);
829
830
		/**
831
		 * Allows overriding of the default text used to explain the fix Jetpack connection action.
832
		 *
833
		 * @param string $html The HTML to be displayed.
834
		 *
835
		 * @since 4.4.0
836
		 */
837
		return apply_filters( 'jetpack_idc_first_fix_connection_explanation', $html );
838
	}
839
840
	/**
841
	 * Returns the first step fix connection button text.
842
	 *
843
	 * @return string
844
	 */
845
	public function get_first_step_fix_connection_button_text() {
846
		$string = esc_html__( "Fix Jetpack's Connection", 'jetpack' );
847
848
		/**
849
		 * Allows overriding of the default text used for the fix Jetpack connection action button.
850
		 *
851
		 * @param string $string The string to be displayed.
852
		 *
853
		 * @since 4.4.0
854
		 */
855
		return apply_filters( 'jetpack_idc_first_step_fix_connection_button_text', $string );
856
	}
857
858
	/**
859
	 * Returns the second step header lead.
860
	 *
861
	 * @return string
862
	 */
863
	public function get_second_step_header_lead() {
864
		$string = sprintf(
865
		/* translators: %s: Site URL. */
866
			esc_html__( 'Is %1$s the new home of %2$s?', 'jetpack' ),
867
			untrailingslashit( self::normalize_url_protocol_agnostic( get_home_url() ) ),
868
			untrailingslashit( self::normalize_url_protocol_agnostic( esc_url_raw( self::$wpcom_home_url ) ) )
869
		);
870
871
		/**
872
		 * Allows overriding of the default header text in the second step of the Safe Mode notice.
873
		 *
874
		 * @param string $html The HTML to be displayed.
875
		 *
876
		 * @since 4.4.0
877
		 */
878
		return apply_filters( 'jetpack_idc_second_step_header_lead', $string );
879
	}
880
881
	/**
882
	 * Returns the site action explanation.
883
	 *
884
	 * @return string
885
	 */
886 View Code Duplication
	public function get_migrate_site_action_explanation() {
887
		$html = wp_kses(
888
			sprintf(
889
			/* translators: %s: Site URL. */
890
				__( 'Yes. <a href="%1$s">%2$s</a> is replacing <a href="%3$s">%4$s</a>. I would like to migrate my stats and subscribers from <a href="%3$s">%4$s</a> to <a href="%1$s">%2$s</a>.', 'jetpack' ),
891
				esc_url( get_home_url() ),
892
				self::prepare_url_for_display( get_home_url() ),
893
				esc_url( self::$wpcom_home_url ),
894
				untrailingslashit( self::normalize_url_protocol_agnostic( esc_url_raw( self::$wpcom_home_url ) ) )
895
			),
896
			array( 'a' => array( 'href' => array() ) )
897
		);
898
899
		/**
900
		 * Allows overriding of the default text for explaining the migrate site action.
901
		 *
902
		 * @param string $html The HTML to be displayed.
903
		 *
904
		 * @since 4.4.0
905
		 */
906
		return apply_filters( 'jetpack_idc_migrate_site_explanation', $html );
907
	}
908
909
	/**
910
	 * Returns the migrate site button text.
911
	 *
912
	 * @return string
913
	 */
914
	public function get_migrate_site_button_text() {
915
		$string = esc_html__( 'Migrate Stats &amp; Subscribers', 'jetpack' );
916
917
		/**
918
		 * Allows overriding of the default text used for the migrate site action button.
919
		 *
920
		 * @param string $string The string to be displayed.
921
		 *
922
		 * @since 4.4.0
923
		 */
924
		return apply_filters( 'jetpack_idc_migrate_site_button_text', $string );
925
	}
926
927
	/**
928
	 * Returns the start fresh explanation.
929
	 *
930
	 * @return string
931
	 */
932 View Code Duplication
	public function get_start_fresh_action_explanation() {
933
		$html = wp_kses(
934
			sprintf(
935
			/* translators: %s: Site URL. */
936
				__( 'No. <a href="%1$s">%2$s</a> is a new and different website that\'s separate from <a href="%3$s">%4$s</a>. It requires  a new connection to WordPress.com for new stats and subscribers.', 'jetpack' ),
937
				esc_url( get_home_url() ),
938
				self::prepare_url_for_display( get_home_url() ),
939
				esc_url( self::$wpcom_home_url ),
940
				untrailingslashit( self::normalize_url_protocol_agnostic( esc_url_raw( self::$wpcom_home_url ) ) )
941
			),
942
			array( 'a' => array( 'href' => array() ) )
943
		);
944
945
		/**
946
		 * Allows overriding of the default text for explaining the start fresh action.
947
		 *
948
		 * @param string $html The HTML to be displayed.
949
		 *
950
		 * @since 4.4.0
951
		 */
952
		return apply_filters( 'jetpack_idc_start_fresh_explanation', $html );
953
	}
954
955
	/**
956
	 * Returns the start fresh button text.
957
	 *
958
	 * @return string
959
	 */
960
	public function get_start_fresh_button_text() {
961
		$string = esc_html__( 'Start Fresh &amp; Create New Connection', 'jetpack' );
962
963
		/**
964
		 * Allows overriding of the default text used for the start fresh action button.
965
		 *
966
		 * @param string $string The string to be displayed.
967
		 *
968
		 * @since 4.4.0
969
		 */
970
		return apply_filters( 'jetpack_idc_start_fresh_button_text', $string );
971
	}
972
973
	/**
974
	 * Returns the unsure prompt text.
975
	 *
976
	 * @return string
977
	 */
978 View Code Duplication
	public function get_unsure_prompt() {
979
		$html = wp_kses(
980
			sprintf(
981
			/* translators: %s: Safe mode docs URL. */
982
				__( 'Unsure what to do? <a href="%1$s">Read more about Jetpack Safe Mode</a>', 'jetpack' ),
983
				esc_url( self::get_safe_mod_doc_url() )
984
			),
985
			array( 'a' => array( 'href' => array() ) )
986
		);
987
988
		/**
989
		 * Allows overriding of the default text using in the "Unsure what to do?" prompt.
990
		 *
991
		 * @param string $html The HTML to be displayed.
992
		 *
993
		 * @since 4.4.0
994
		 */
995
		return apply_filters( 'jetpack_idc_unsure_prompt', $html );
996
	}
997
998
	/**
999
	 * Returns the non-admin notice text.
1000
	 *
1001
	 * @return string
1002
	 */
1003 View Code Duplication
	public function get_non_admin_notice_text() {
1004
		$html = wp_kses(
1005
			sprintf(
1006
			/* translators: %s: Safe mode docs URL. */
1007
				__( 'Jetpack has been placed into Safe Mode. Learn more about <a href="%1$s">Safe Mode</a>.', 'jetpack' ),
1008
				esc_url( self::get_safe_mod_doc_url() )
1009
			),
1010
			array( 'a' => array( 'href' => array() ) )
1011
		);
1012
1013
		/**
1014
		 * Allows overriding of the default text that is displayed to non-admin on the Jetpack admin page.
1015
		 *
1016
		 * @param string $html The HTML to be displayed.
1017
		 *
1018
		 * @since 4.4.0
1019
		 */
1020
		return apply_filters( 'jetpack_idc_non_admin_notice_text', $html );
1021
	}
1022
1023
	/**
1024
	 * Returns the non-admin contact admin text.
1025
	 *
1026
	 * @return string
1027
	 */
1028
	public function get_non_admin_contact_admin_text() {
1029
		$string = esc_html__( 'An administrator of this site can take Jetpack out of Safe Mode.', 'jetpack' );
1030
1031
		/**
1032
		 * Allows overriding of the default text that is displayed to non-admins prompting them to contact an admin.
1033
		 *
1034
		 * @param string $string The string to be displayed.
1035
		 *
1036
		 * @since 4.4.0
1037
		 */
1038
		return apply_filters( 'jetpack_idc_non_admin_contact_admin_text', $string );
1039
	}
1040
}
1041