Completed
Push — try/remote-ui ( 69fd78 )
by
unknown
06:35
created

Jetpack_CLI::test_connection()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 34

Duplication

Lines 4
Ratio 11.76 %

Importance

Changes 0
Metric Value
cc 5
nc 16
nop 2
dl 4
loc 34
rs 9.0648
c 0
b 0
f 0
1
<?php
2
3
WP_CLI::add_command( 'jetpack', 'Jetpack_CLI' );
4
5
use Automattic\Jetpack\Connection\Client;
6
use Automattic\Jetpack\Sync\Actions;
7
use Automattic\Jetpack\Sync\Listener;
8
use Automattic\Jetpack\Sync\Queue;
9
use Automattic\Jetpack\Sync\Settings;
10
11
/**
12
 * Control your local Jetpack installation.
13
 *
14
 * Minimum PHP requirement for WP-CLI is PHP 5.3, so ignore PHP 5.2 compatibility issues.
15
 * @phpcs:disable PHPCompatibility.PHP.NewLanguageConstructs.t_ns_separatorFound
16
 */
17
class Jetpack_CLI extends WP_CLI_Command {
18
	// Aesthetics
19
	public $green_open  = "\033[32m";
20
	public $red_open    = "\033[31m";
21
	public $yellow_open = "\033[33m";
22
	public $color_close = "\033[0m";
23
24
	/**
25
	 * Get Jetpack Details
26
	 *
27
	 * ## OPTIONS
28
	 *
29
	 * empty: Leave it empty for basic stats
30
	 *
31
	 * full: View full stats.  It's the data from the heartbeat
32
	 *
33
	 * ## EXAMPLES
34
	 *
35
	 * wp jetpack status
36
	 * wp jetpack status full
37
	 *
38
	 */
39
	public function status( $args, $assoc_args ) {
40
		jetpack_require_lib( 'debugger' );
41
42
		/* translators: %s is the site URL */
43
		WP_CLI::line( sprintf( __( 'Checking status for %s', 'jetpack' ), esc_url( get_home_url() ) ) );
44
45 View Code Duplication
		if ( isset( $args[0] ) && 'full' !== $args[0] ) {
46
			/* translators: %s is a command like "prompt" */
47
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $args[0] ) );
48
		}
49
50
		$master_user_email = Jetpack::get_master_user_email();
51
52
		$cxntests = new Jetpack_Cxn_Tests();
53
54
		if ( $cxntests->pass() ) {
55
			$cxntests->output_results_for_cli();
56
57
			WP_CLI::success( __( 'Jetpack is currently connected to WordPress.com', 'jetpack' ) );
58
		} else {
59
			$error = array();
60
			foreach ( $cxntests->list_fails() as $fail ) {
0 ignored issues
show
Bug introduced by
The expression $cxntests->list_fails() of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
61
				$error[] = $fail['name'] . ': ' . $fail['message'];
62
			}
63
			WP_CLI::error_multi_line( $error );
64
65
			$cxntests->output_results_for_cli();
66
67
			WP_CLI::error( __('Jetpack connection is broken.', 'jetpack' ) ); // Exit CLI.
68
		}
69
70
		/* translators: %s is current version of Jetpack, for example 7.3 */
71
		WP_CLI::line( sprintf( __( 'The Jetpack Version is %s', 'jetpack' ), JETPACK__VERSION ) );
72
		/* translators: %d is WP.com ID of this blog */
73
		WP_CLI::line( sprintf( __( 'The WordPress.com blog_id is %d', 'jetpack' ), Jetpack_Options::get_option( 'id' ) ) );
74
		/* translators: %s is the email address of the connection owner */
75
		WP_CLI::line( sprintf( __( 'The WordPress.com account for the primary connection is %s', 'jetpack' ), $master_user_email ) );
76
77
		/*
78
		 * Are they asking for all data?
79
		 *
80
		 * Loop through heartbeat data and organize by priority.
81
		 */
82
		$all_data = ( isset( $args[0] ) && 'full' == $args[0] ) ? 'full' : false;
83
		if ( $all_data ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $all_data of type string|false is loosely compared to true; 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...
84
			// Heartbeat data
85
			WP_CLI::line( "\n" . __( 'Additional data: ', 'jetpack' ) );
86
87
			// Get the filtered heartbeat data.
88
			// Filtered so we can color/list by severity
89
			$stats = Jetpack::jetpack_check_heartbeat_data();
90
91
			// Display red flags first
92
			foreach ( $stats['bad'] as $stat => $value ) {
93
				printf( "$this->red_open%-'.16s %s $this->color_close\n", $stat, $value );
94
			}
95
96
			// Display caution warnings next
97
			foreach ( $stats['caution'] as $stat => $value ) {
98
				printf( "$this->yellow_open%-'.16s %s $this->color_close\n", $stat, $value );
99
			}
100
101
			// The rest of the results are good!
102
			foreach ( $stats['good'] as $stat => $value ) {
103
104
				// Modules should get special spacing for aestetics
105
				if ( strpos( $stat, 'odule-' ) ) {
106
					printf( "%-'.30s %s\n", $stat, $value );
107
					usleep( 4000 ); // For dramatic effect lolz
108
					continue;
109
				}
110
				printf( "%-'.16s %s\n", $stat, $value );
111
				usleep( 4000 ); // For dramatic effect lolz
112
			}
113
		} else {
114
			// Just the basics
115
			WP_CLI::line( "\n" . _x( "View full status with 'wp jetpack status full'", '"wp jetpack status full" is a command - do not translate', 'jetpack' ) );
116
		}
117
	}
118
119
	/**
120
	 * Tests the active connection
121
	 *
122
	 * Does a two-way test to verify that the local site can communicate with remote Jetpack/WP.com servers and that Jetpack/WP.com servers can talk to the local site.
123
	 *
124
	 * ## EXAMPLES
125
	 *
126
	 * wp jetpack test-connection
127
	 *
128
	 * @subcommand test-connection
129
	 */
130
	public function test_connection( $args, $assoc_args ) {
131
132
		/* translators: %s is the site URL */
133
		WP_CLI::line( sprintf( __( 'Testing connection for %s', 'jetpack' ), esc_url( get_site_url() ) ) );
134
135
		if ( ! Jetpack::is_active() ) {
136
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
137
		}
138
139
		$response = Client::wpcom_json_api_request_as_blog(
140
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
141
			Client::WPCOM_JSON_API_VERSION
142
		);
143
144 View Code Duplication
		if ( is_wp_error( $response ) ) {
145
			/* translators: %1$s is the error code, %2$s is the error message */
146
			WP_CLI::error( sprintf( __( 'Failed to test connection (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ) );
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
147
		}
148
149
		$body = wp_remote_retrieve_body( $response );
150
		if ( ! $body ) {
151
			WP_CLI::error( __( 'Failed to test connection (empty response body)', 'jetpack' ) );
152
		}
153
154
		$result = json_decode( $body );
155
		$is_connected = (bool) $result->connected;
156
		$message = $result->message;
157
158
		if ( $is_connected ) {
159
			WP_CLI::success( $message );
160
		} else {
161
			WP_CLI::error( $message );
162
		}
163
	}
164
165
	/**
166
	 * Disconnect Jetpack Blogs or Users
167
	 *
168
	 * ## OPTIONS
169
	 *
170
	 * blog: Disconnect the entire blog.
171
	 *
172
	 * user <user_identifier>: Disconnect a specific user from WordPress.com.
173
	 *
174
	 * Please note, the primary account that the blog is connected
175
	 * to WordPress.com with cannot be disconnected without
176
	 * disconnecting the entire blog.
177
	 *
178
	 * ## EXAMPLES
179
	 *
180
	 * wp jetpack disconnect blog
181
	 * wp jetpack disconnect user 13
182
	 * wp jetpack disconnect user username
183
	 * wp jetpack disconnect user [email protected]
184
	 *
185
	 * @synopsis <blog|user> [<user_identifier>]
186
	 */
187
	public function disconnect( $args, $assoc_args ) {
188
		if ( ! Jetpack::is_active() ) {
189
			WP_CLI::error( __( 'You cannot disconnect, without having first connected.', 'jetpack' ) );
190
		}
191
192
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
193
		if ( ! in_array( $action, array( 'blog', 'user', 'prompt' ) ) ) {
194
			/* translators: %s is a command like "prompt" */
195
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
196
		}
197
198
		if ( in_array( $action, array( 'user' ) ) ) {
199
			if ( isset( $args[1] ) ) {
200
				$user_id = $args[1];
201
				if ( ctype_digit( $user_id ) ) {
202
					$field = 'id';
203
					$user_id = (int) $user_id;
204
				} elseif ( is_email( $user_id ) ) {
205
					$field = 'email';
206
					$user_id = sanitize_user( $user_id, true );
207
				} else {
208
					$field = 'login';
209
					$user_id = sanitize_user( $user_id, true );
210
				}
211
				if ( ! $user = get_user_by( $field, $user_id ) ) {
212
					WP_CLI::error( __( 'Please specify a valid user.', 'jetpack' ) );
213
				}
214
			} else {
215
				WP_CLI::error( __( 'Please specify a user by either ID, username, or email.', 'jetpack' ) );
216
			}
217
		}
218
219
		switch ( $action ) {
220
			case 'blog':
221
				Jetpack::log( 'disconnect' );
222
				Jetpack::disconnect();
223
				WP_CLI::success( sprintf(
224
					/* translators: %s is the site URL */
225
					__( 'Jetpack has been successfully disconnected for %s.', 'jetpack' ),
226
					esc_url( get_site_url() )
227
				) );
228
				break;
229
			case 'user':
230
				if ( Jetpack::unlink_user( $user->ID ) ) {
231
					Jetpack::log( 'unlink', $user->ID );
0 ignored issues
show
Bug introduced by
The variable $user does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
232
					WP_CLI::success( __( 'User has been successfully disconnected.', 'jetpack' ) );
233
				} else {
234
					/* translators: %s is a username */
235
					WP_CLI::error( sprintf( __( "User %s could not be disconnected. Are you sure they're connected currently?", 'jetpack' ), "{$user->login} <{$user->email}>" ) );
236
				}
237
				break;
238
			case 'prompt':
239
				WP_CLI::error( __( 'Please specify if you would like to disconnect a blog or user.', 'jetpack' ) );
240
				break;
241
		}
242
	}
243
244
	/**
245
	 * Reset Jetpack options and settings to default
246
	 *
247
	 * ## OPTIONS
248
	 *
249
	 * modules: Resets modules to default state ( get_default_modules() )
250
	 *
251
	 * options: Resets all Jetpack options except:
252
	 *  - All private options (Blog token, user token, etc...)
253
	 *  - id (The Client ID/WP.com Blog ID of this site)
254
	 *  - master_user
255
	 *  - version
256
	 *  - activated
257
	 *
258
	 * ## EXAMPLES
259
	 *
260
	 * wp jetpack reset options
261
	 * wp jetpack reset modules
262
	 * wp jetpack reset sync-checksum --dry-run --offset=0
263
	 *
264
	 * @synopsis <modules|options|sync-checksum> [--dry-run] [--offset=<offset>]
265
	 *
266
	 */
267
	public function reset( $args, $assoc_args ) {
268
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
269 View Code Duplication
		if ( ! in_array( $action, array( 'options', 'modules', 'sync-checksum' ), true ) ) {
270
			/* translators: %s is a command like "prompt" */
271
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
272
		}
273
274
		$is_dry_run = ! empty( $assoc_args['dry-run'] );
275
276 View Code Duplication
		if ( $is_dry_run ) {
277
			WP_CLI::warning(
278
				__( "\nThis is a dry run.\n", 'jetpack' ) .
279
				__( "No actions will be taken.\n", 'jetpack' ) .
280
				__( "The following messages will give you preview of what will happen when you run this command.\n\n", 'jetpack' )
281
			);
282
		} else {
283
			// We only need to confirm "Are you sure?" when we are not doing a dry run.
284
			jetpack_cli_are_you_sure();
285
		}
286
287
		switch ( $action ) {
288
			case 'options':
289
				$options_to_reset = Jetpack_Options::get_options_for_reset();
290
				// Reset the Jetpack options
291
				WP_CLI::line( sprintf(
292
					/* translators: %s is the site URL */
293
					__( "Resetting Jetpack Options for %s...\n", "jetpack" ),
294
					esc_url( get_site_url() )
295
				) );
296
				sleep(1); // Take a breath
297 View Code Duplication
				foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
298
					if ( ! $is_dry_run ) {
299
						Jetpack_Options::delete_option( $option_to_reset );
300
						usleep( 100000 );
301
					}
302
303
					/* translators: This is the result of an action. The option named %s was reset */
304
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
305
				}
306
307
				// Reset the WP options
308
				WP_CLI::line( __( "Resetting the jetpack options stored in wp_options...\n", "jetpack" ) );
309
				usleep( 500000 ); // Take a breath
310 View Code Duplication
				foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
311
					if ( ! $is_dry_run ) {
312
						delete_option( $option_to_reset );
313
						usleep( 100000 );
314
					}
315
					/* translators: This is the result of an action. The option named %s was reset */
316
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
317
				}
318
319
				// Reset to default modules
320
				WP_CLI::line( __( "Resetting default modules...\n", "jetpack" ) );
321
				usleep( 500000 ); // Take a breath
322
				$default_modules = Jetpack::get_default_modules();
323
				if ( ! $is_dry_run ) {
324
					Jetpack::update_active_modules( $default_modules );
325
				}
326
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
327
				break;
328 View Code Duplication
			case 'modules':
329
				if ( ! $is_dry_run ) {
330
					$default_modules = Jetpack::get_default_modules();
331
					Jetpack::update_active_modules( $default_modules );
332
				}
333
334
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
335
				break;
336
			case 'prompt':
337
				WP_CLI::error( __( 'Please specify if you would like to reset your options, modules or sync-checksum', 'jetpack' ) );
338
				break;
339
			case 'sync-checksum':
340
				$option = 'jetpack_callables_sync_checksum';
341
342
				if ( is_multisite() ) {
343
					$offset = isset( $assoc_args['offset'] ) ? (int) $assoc_args['offset'] : 0;
344
345
					/*
346
					 * 1000 is a good limit since we don't expect the number of sites to be more than 1000
347
					 * Offset can be used to paginate and try to clean up more sites.
348
					 */
349
					$sites       = get_sites( array( 'number' => 1000, 'offset' => $offset ) );
350
					$count_fixes = 0;
351
					foreach ( $sites as $site ) {
352
						switch_to_blog( $site->blog_id );
353
						$count = self::count_option( $option );
354
						if ( $count > 1 ) {
355
							if ( ! $is_dry_run ) {
356
								delete_option( $option );
357
							}
358
							WP_CLI::line(
359
								sprintf(
360
									/* translators: %1$d is a number, %2$s is the name of an option, %2$s is the site URL. */
361
									__( 'Deleted %1$d %2$s options from %3$s', 'jetpack' ),
362
									$count,
363
									$option,
364
									"{$site->domain}{$site->path}"
365
								)
366
							);
367
							$count_fixes++;
368
							if ( ! $is_dry_run ) {
369
								/*
370
								 * We could be deleting a lot of options rows at the same time.
371
								 * Allow some time for replication to catch up.
372
								 */
373
								sleep( 3 );
374
							}
375
						}
376
377
						restore_current_blog();
378
					}
379
					if ( $count_fixes ) {
380
						WP_CLI::success(
381
							sprintf(
382
								/* translators: %1$s is the name of an option, %2$d is a number of sites. */
383
								__( 'Successfully reset %1$s on %2$d sites.', 'jetpack' ),
384
								$option,
385
								$count_fixes
386
							)
387
						);
388
					} else {
389
						WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
390
					}
391
					return;
392
				}
393
394
				$count = self::count_option( $option );
395
				if ( $count > 1 ) {
396
					if ( ! $is_dry_run ) {
397
						delete_option( $option );
398
					}
399
					WP_CLI::success(
400
						sprintf(
401
							/* translators: %1$d is a number, %2$s is the name of an option. */
402
							__( 'Deleted %1$d %2$s options', 'jetpack' ),
403
							$count,
404
							$option
405
						)
406
					);
407
					return;
408
				}
409
410
				WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
411
				break;
412
413
		}
414
	}
415
416
	/**
417
	 * Return the number of times an option appears
418
	 * Normally an option would only appear 1 since the option key is supposed to be unique
419
	 * but if a site hasn't updated the DB schema then that would not be the case.
420
	 *
421
	 * @param string $option Option name.
422
	 *
423
	 * @return int
424
	 */
425
	private static function count_option( $option ) {
426
		global $wpdb;
427
		return (int) $wpdb->get_var(
428
			$wpdb->prepare(
429
				"SELECT COUNT(*) FROM $wpdb->options WHERE option_name = %s",
430
				$option
431
			)
432
		);
433
434
	}
435
436
	/**
437
	 * Manage Jetpack Modules
438
	 *
439
	 * ## OPTIONS
440
	 *
441
	 * <list|activate|deactivate|toggle>
442
	 * : The action to take.
443
	 * ---
444
	 * default: list
445
	 * options:
446
	 *  - list
447
	 *  - activate
448
	 *  - deactivate
449
	 *  - toggle
450
	 * ---
451
	 *
452
	 * [<module_slug>]
453
	 * : The slug of the module to perform an action on.
454
	 *
455
	 * [--format=<format>]
456
	 * : Allows overriding the output of the command when listing modules.
457
	 * ---
458
	 * default: table
459
	 * options:
460
	 *  - table
461
	 *  - json
462
	 *  - csv
463
	 *  - yaml
464
	 *  - ids
465
	 *  - count
466
	 * ---
467
	 *
468
	 * ## EXAMPLES
469
	 *
470
	 * wp jetpack module list
471
	 * wp jetpack module list --format=json
472
	 * wp jetpack module activate stats
473
	 * wp jetpack module deactivate stats
474
	 * wp jetpack module toggle stats
475
	 * wp jetpack module activate all
476
	 * wp jetpack module deactivate all
477
	 */
478
	public function module( $args, $assoc_args ) {
479
		$action = isset( $args[0] ) ? $args[0] : 'list';
480
481
		if ( isset( $args[1] ) ) {
482
			$module_slug = $args[1];
483
			if ( 'all' !== $module_slug && ! Jetpack::is_module( $module_slug ) ) {
484
				/* translators: %s is a module slug like "stats" */
485
				WP_CLI::error( sprintf( __( '%s is not a valid module.', 'jetpack' ), $module_slug ) );
486
			}
487
			if ( 'toggle' === $action ) {
488
				$action = Jetpack::is_module_active( $module_slug )
489
					? 'deactivate'
490
					: 'activate';
491
			}
492
			if ( 'all' === $args[1] ) {
493
				$action = ( 'deactivate' === $action )
494
					? 'deactivate_all'
495
					: 'activate_all';
496
			}
497
		} elseif ( 'list' !== $action ) {
498
			WP_CLI::line( __( 'Please specify a valid module.', 'jetpack' ) );
499
			$action = 'list';
500
		}
501
502
		switch ( $action ) {
503
			case 'list':
504
				$modules_list = array();
505
				$modules      = Jetpack::get_available_modules();
506
				sort( $modules );
507
				foreach ( (array) $modules as $module_slug ) {
508
					if ( 'vaultpress' === $module_slug ) {
509
						continue;
510
					}
511
					$modules_list[] = array(
512
						'slug'   => $module_slug,
513
						'status' => Jetpack::is_module_active( $module_slug )
514
							? __( 'Active', 'jetpack' )
515
							: __( 'Inactive', 'jetpack' ),
516
					);
517
				}
518
				WP_CLI\Utils\format_items( $assoc_args['format'], $modules_list, array( 'slug', 'status' ) );
519
				break;
520
			case 'activate':
521
				$module = Jetpack::get_module( $module_slug );
0 ignored issues
show
Bug introduced by
The variable $module_slug does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
522
				Jetpack::log( 'activate', $module_slug );
523
				if ( Jetpack::activate_module( $module_slug, false, false ) ) {
524
					/* translators: %s is the name of a Jetpack module */
525
					WP_CLI::success( sprintf( __( '%s has been activated.', 'jetpack' ), $module['name'] ) );
526
				} else {
527
					/* translators: %s is the name of a Jetpack module */
528
					WP_CLI::error( sprintf( __( '%s could not be activated.', 'jetpack' ), $module['name'] ) );
529
				}
530
				break;
531 View Code Duplication
			case 'activate_all':
532
				$modules = Jetpack::get_available_modules();
533
				Jetpack::update_active_modules( $modules );
534
				WP_CLI::success( __( 'All modules activated!', 'jetpack' ) );
535
				break;
536
			case 'deactivate':
537
				$module = Jetpack::get_module( $module_slug );
538
				Jetpack::log( 'deactivate', $module_slug );
539
				Jetpack::deactivate_module( $module_slug );
540
				/* translators: %s is the name of a Jetpack module */
541
				WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) );
542
				break;
543
			case 'deactivate_all':
544
				Jetpack::delete_active_modules();
545
				WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) );
546
				break;
547
			case 'toggle':
548
				// Will never happen, should have been handled above and changed to activate or deactivate.
549
				break;
550
		}
551
	}
552
553
	/**
554
	 * Manage Protect Settings
555
	 *
556
	 * ## OPTIONS
557
	 *
558
	 * whitelist: Whitelist an IP address.  You can also read or clear the whitelist.
559
	 *
560
	 *
561
	 * ## EXAMPLES
562
	 *
563
	 * wp jetpack protect whitelist <ip address>
564
	 * wp jetpack protect whitelist list
565
	 * wp jetpack protect whitelist clear
566
	 *
567
	 * @synopsis <whitelist> [<ip|ip_low-ip_high|list|clear>]
568
	 */
569
	public function protect( $args, $assoc_args ) {
570
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
571
		if ( ! in_array( $action, array( 'whitelist' ) ) ) {
572
			/* translators: %s is a command like "prompt" */
573
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
574
		}
575
		// Check if module is active
576
		if ( ! Jetpack::is_module_active( __FUNCTION__ ) ) {
577
			/* translators: %s is a module name */
578
			WP_CLI::error( sprintf( _x( '%s is not active. You can activate it with "wp jetpack module activate %s"', '"wp jetpack module activate" is a command - do not translate', 'jetpack' ), __FUNCTION__, __FUNCTION__ ) );
579
		}
580
		if ( in_array( $action, array( 'whitelist' ) ) ) {
581
			if ( isset( $args[1] ) ) {
582
				$action = 'whitelist';
583
			} else {
584
				$action = 'prompt';
585
			}
586
		}
587
		switch ( $action ) {
588
			case 'whitelist':
589
				$whitelist         = array();
590
				$new_ip            = $args[1];
591
				$current_whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
592
593
				// Build array of IPs that are already whitelisted.
594
				// Re-build manually instead of using jetpack_protect_format_whitelist() so we can easily get
595
				// low & high range params for jetpack_protect_ip_address_is_in_range();
596
				foreach( $current_whitelist as $whitelisted ) {
597
598
					// IP ranges
599
					if ( $whitelisted->range ) {
600
601
						// Is it already whitelisted?
602
						if ( jetpack_protect_ip_address_is_in_range( $new_ip, $whitelisted->range_low, $whitelisted->range_high ) ) {
603
							/* translators: %s is an IP address */
604
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
605
							break;
606
						}
607
						$whitelist[] = $whitelisted->range_low . " - " . $whitelisted->range_high;
608
609
					} else { // Individual IPs
610
611
						// Check if the IP is already whitelisted (single IP only)
612
						if ( $new_ip == $whitelisted->ip_address ) {
613
							/* translators: %s is an IP address */
614
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
615
							break;
616
						}
617
						$whitelist[] = $whitelisted->ip_address;
618
619
					}
620
				}
621
622
				/*
623
				 * List the whitelist
624
				 * Done here because it's easier to read the $whitelist array after it's been rebuilt
625
				 */
626
				if ( isset( $args[1] ) && 'list' == $args[1] ) {
627
					if ( ! empty( $whitelist ) ) {
628
						WP_CLI::success( __( 'Here are your whitelisted IPs:', 'jetpack' ) );
629
						foreach ( $whitelist as $ip ) {
630
							WP_CLI::line( "\t" . str_pad( $ip, 24 ) ) ;
631
						}
632
					} else {
633
						WP_CLI::line( __( 'Whitelist is empty.', "jetpack" ) ) ;
634
					}
635
					break;
636
				}
637
638
				/*
639
				 * Clear the whitelist
640
				 */
641
				if ( isset( $args[1] ) && 'clear' == $args[1] ) {
642
					if ( ! empty( $whitelist ) ) {
643
						$whitelist = array();
644
						jetpack_protect_save_whitelist( $whitelist );
645
						WP_CLI::success( __( 'Cleared all whitelisted IPs', 'jetpack' ) );
646
					} else {
647
						WP_CLI::line( __( 'Whitelist is empty.', "jetpack" ) ) ;
648
					}
649
					break;
650
				}
651
652
				// Append new IP to whitelist array
653
				array_push( $whitelist, $new_ip );
654
655
				// Save whitelist if there are no errors
656
				$result = jetpack_protect_save_whitelist( $whitelist );
657
				if ( is_wp_error( $result ) ) {
658
					WP_CLI::error( $result );
659
				}
660
661
				/* translators: %s is an IP address */
662
				WP_CLI::success( sprintf( __( '%s has been whitelisted.', 'jetpack' ), $new_ip ) );
663
				break;
664
			case 'prompt':
665
				WP_CLI::error(
666
					__( 'No command found.', 'jetpack' ) . "\n" .
667
					__( 'Please enter the IP address you want to whitelist.', 'jetpack' ) . "\n" .
668
					_x( 'You can save a range of IPs {low_range}-{high_range}. No spaces allowed.  (example: 1.1.1.1-2.2.2.2)', 'Instructions on how to whitelist IP ranges - low_range/high_range should be translated.', 'jetpack' ) . "\n" .
669
					_x( "You can also 'list' or 'clear' the whitelist.", "'list' and 'clear' are commands and should not be translated", 'jetpack' ) . "\n"
670
				);
671
				break;
672
		}
673
	}
674
675
	/**
676
	 * Manage Jetpack Options
677
	 *
678
	 * ## OPTIONS
679
	 *
680
	 * list   : List all jetpack options and their values
681
	 * delete : Delete an option
682
	 *          - can only delete options that are white listed.
683
	 * update : update an option
684
	 *          - can only update option strings
685
	 * get    : get the value of an option
686
	 *
687
	 * ## EXAMPLES
688
	 *
689
	 * wp jetpack options list
690
	 * wp jetpack options get    <option_name>
691
	 * wp jetpack options delete <option_name>
692
	 * wp jetpack options update <option_name> [<option_value>]
693
	 *
694
	 * @synopsis <list|get|delete|update> [<option_name>] [<option_value>]
695
	 */
696
	public function options( $args, $assoc_args ) {
697
		$action = isset( $args[0] ) ? $args[0] : 'list';
698
		$safe_to_modify = Jetpack_Options::get_options_for_reset();
699
700
		// Is the option flagged as unsafe?
701
		$flagged = ! in_array( $args[1], $safe_to_modify );
702
703 View Code Duplication
		if ( ! in_array( $action, array( 'list', 'get', 'delete', 'update' ) ) ) {
704
			/* translators: %s is a command like "prompt" */
705
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
706
		}
707
708
		if ( isset( $args[0] ) ) {
709
			if ( 'get' == $args[0] && isset( $args[1] ) ) {
710
				$action = 'get';
711
			} else if ( 'delete' == $args[0] && isset( $args[1] ) ) {
712
				$action = 'delete';
713
			} else if ( 'update' == $args[0] && isset( $args[1] ) ) {
714
				$action = 'update';
715
			} else {
716
				$action = 'list';
717
			}
718
		}
719
720
		// Bail if the option isn't found
721
		$option = isset( $args[1] ) ? Jetpack_Options::get_option( $args[1] ) : false;
722 View Code Duplication
		if ( isset( $args[1] ) && ! $option && 'update' !== $args[0] ) {
723
			WP_CLI::error( __( 'Option not found or is empty.  Use "list" to list option names', 'jetpack' ) );
724
		}
725
726
		// Let's print_r the option if it's an array
727
		// Used in the 'get' and 'list' actions
728
		$option = is_array( $option ) ? print_r( $option ) : $option;
729
730
		switch ( $action ) {
731
			case 'get':
732
				WP_CLI::success( "\t" . $option );
733
				break;
734
			case 'delete':
735
				jetpack_cli_are_you_sure( $flagged );
736
737
				Jetpack_Options::delete_option( $args[1] );
738
				/* translators: %s is the option name */
739
				WP_CLI::success( sprintf( __( 'Deleted option: %s', 'jetpack' ), $args[1] ) );
740
				break;
741
			case 'update':
742
				jetpack_cli_are_you_sure( $flagged );
743
744
				// Updating arrays would get pretty tricky...
745
				$value = Jetpack_Options::get_option( $args[1] );
746
				if ( $value && is_array( $value ) ) {
747
					WP_CLI::error( __( 'Sorry, no updating arrays at this time', 'jetpack' ) );
748
				}
749
750
				Jetpack_Options::update_option( $args[1], $args[2] );
751
				/* translators: %1$s is the previous value, %2$s is the new value */
752
				WP_CLI::success( sprintf( _x( 'Updated option: %1$s to "%2$s"', 'Updating an option from "this" to "that".', 'jetpack' ), $args[1], $args[2] ) );
753
				break;
754
			case 'list':
755
				$options_compact     = Jetpack_Options::get_option_names();
756
				$options_non_compact = Jetpack_Options::get_option_names( 'non_compact' );
757
				$options_private     = Jetpack_Options::get_option_names( 'private' );
758
				$options             = array_merge( $options_compact, $options_non_compact, $options_private );
759
760
				// Table headers
761
				WP_CLI::line( "\t" . str_pad( __( 'Option', 'jetpack' ), 30 ) . __( 'Value', 'jetpack' ) );
762
763
				// List out the options and their values
764
				// Tell them if the value is empty or not
765
				// Tell them if it's an array
766
				foreach ( $options as $option ) {
767
					$value = Jetpack_Options::get_option( $option );
768
					if ( ! $value ) {
769
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Empty' );
770
						continue;
771
					}
772
773
					if ( ! is_array( $value ) ) {
774
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . $value );
775
					} else if ( is_array( $value ) ) {
776
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Array - Use "get <option>" to read option array.' );
777
					}
778
				}
779
				$option_text = '{' . _x( 'option', 'a variable command that a user can write, provided in the printed instructions', 'jetpack' ) . '}';
780
				$value_text  = '{' . _x( 'value', 'the value that they want to update the option to', 'jetpack' ) . '}';
781
782
				WP_CLI::success(
783
					_x( "Above are your options. You may 'get', 'delete', and 'update' them.", "'get', 'delete', and 'update' are commands - do not translate.", 'jetpack' ) . "\n" .
784
					str_pad( 'wp jetpack options get', 26 )    . $option_text . "\n" .
785
					str_pad( 'wp jetpack options delete', 26 ) . $option_text . "\n" .
786
					str_pad( 'wp jetpack options update', 26 ) . "$option_text $value_text" . "\n" .
787
					_x( "Type 'wp jetpack options' for more info.", "'wp jetpack options' is a command - do not translate.", 'jetpack' ) . "\n"
788
				);
789
				break;
790
		}
791
	}
792
793
	/**
794
	 * Get the status of or start a new Jetpack sync.
795
	 *
796
	 * ## OPTIONS
797
	 *
798
	 * status   : Print the current sync status
799
	 * settings : Prints the current sync settings
800
	 * start    : Start a full sync from this site to WordPress.com
801
	 * enable   : Enables sync on the site
802
	 * disable  : Disable sync on a site
803
	 * reset    : Disables sync and Resets the sync queues on a site
804
	 *
805
	 * ## EXAMPLES
806
	 *
807
	 * wp jetpack sync status
808
	 * wp jetpack sync settings
809
	 * wp jetpack sync start --modules=functions --sync_wait_time=5
810
	 * wp jetpack sync enable
811
	 * wp jetpack sync disable
812
	 * wp jetpack sync reset
813
	 * wp jetpack sync reset --queue=full or regular
814
	 *
815
	 * @synopsis <status|start> [--<field>=<value>]
816
	 */
817
	public function sync( $args, $assoc_args ) {
818
819
		$action = isset( $args[0] ) ? $args[0] : 'status';
820
821
		switch ( $action ) {
822
			case 'status':
823
				$status = Actions::get_sync_status();
824
				$collection = array();
825
				foreach ( $status as $key => $item ) {
826
					$collection[]  = array(
827
						'option' => $key,
828
						'value' => is_scalar( $item ) ? $item : json_encode( $item )
829
					);
830
				}
831
				WP_CLI::log( __( 'Sync Status:', 'jetpack' ) );
832
				WP_CLI\Utils\format_items( 'table', $collection, array( 'option', 'value' ) );
833
				break;
834
			case 'settings':
835
				WP_CLI::log( __( 'Sync Settings:', 'jetpack' ) );
836
				foreach( Settings::get_settings() as $setting => $item ) {
837
					$settings[]  = array(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$settings was never initialized. Although not strictly required by PHP, it is generally a good practice to add $settings = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
838
						'setting' => $setting,
839
						'value' => is_scalar( $item ) ? $item : json_encode( $item )
840
					);
841
				}
842
				WP_CLI\Utils\format_items( 'table', $settings, array( 'setting', 'value' ) );
0 ignored issues
show
Bug introduced by
The variable $settings does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
843
844
			case 'disable':
845
				// Don't set it via the Settings since that also resets the queues.
846
				update_option( 'jetpack_sync_settings_disable', 1 );
847
				/* translators: %s is the site URL */
848
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s', 'jetpack' ), get_site_url() ) );
849
				break;
850
			case 'enable':
851
				Settings::update_settings( array( 'disable' => 0 ) );
852
				/* translators: %s is the site URL */
853
				WP_CLI::log( sprintf( __( 'Sync Enabled on %s', 'jetpack' ), get_site_url() ) );
854
				break;
855
			case 'reset':
856
				// Don't set it via the Settings since that also resets the queues.
857
				update_option( 'jetpack_sync_settings_disable', 1 );
858
859
				/* translators: %s is the site URL */
860
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s. Use `wp jetpack sync enable` to enable syncing again.', 'jetpack' ), get_site_url() ) );
861
				$listener = Listener::get_instance();
862
				if ( empty( $assoc_args['queue'] ) ) {
863
					$listener->get_sync_queue()->reset();
864
					$listener->get_full_sync_queue()->reset();
865
					/* translators: %s is the site URL */
866
					WP_CLI::log( sprintf( __( 'Reset Full Sync and Regular Queues Queue on %s', 'jetpack' ), get_site_url() ) );
867
					break;
868
				}
869
870
				if ( ! empty( $assoc_args['queue'] ) ) {
871
					switch ( $assoc_args['queue'] ) {
872 View Code Duplication
						case 'regular':
873
							$listener->get_sync_queue()->reset();
874
							/* translators: %s is the site URL */
875
							WP_CLI::log( sprintf( __( 'Reset Regular Sync Queue on %s', 'jetpack' ), get_site_url() ) );
876
							break;
877 View Code Duplication
						case 'full':
878
							$listener->get_full_sync_queue()->reset();
879
							/* translators: %s is the site URL */
880
							WP_CLI::log( sprintf( __( 'Reset Full Sync Queue on %s', 'jetpack' ), get_site_url() ) );
881
							break;
882
						default:
883
							WP_CLI::error( __( 'Please specify what type of queue do you want to reset: `full` or `regular`.', 'jetpack' ) );
884
							break;
885
					}
886
				}
887
888
				break;
889
			case 'start':
890
				if ( ! Actions::sync_allowed() ) {
891
					if( ! Settings::get_setting( 'disable' ) ) {
892
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. It is currently disabled. Run `wp jetpack sync enable` to enable it.', 'jetpack' ) );
893
						return;
894
					}
895
					if ( doing_action( 'jetpack_user_authorized' ) || Jetpack::is_active() ) {
896
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. Jetpack is not connected.', 'jetpack' ) );
897
						return;
898
					}
899
					if ( Jetpack::is_development_mode() ) {
900
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in development mode.', 'jetpack' ) );
901
						return;
902
					}
903
					if (  Jetpack::is_staging_site() ) {
904
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in staging mode.', 'jetpack' ) );
905
						return;
906
					}
907
908
				}
909
				// Get the original settings so that we can restore them later
910
				$original_settings = Settings::get_settings();
911
912
				// Initialize sync settigns so we can sync as quickly as possible
913
				$sync_settings = wp_parse_args(
914
					array_intersect_key( $assoc_args, Settings::$valid_settings ),
915
					array(
916
						'sync_wait_time' => 0,
917
						'enqueue_wait_time' => 0,
918
						'queue_max_writes_sec' => 10000,
919
						'max_queue_size_full_sync' => 100000
920
					)
921
				);
922
				Settings::update_settings( $sync_settings );
923
924
				// Convert comma-delimited string of modules to an array
925 View Code Duplication
				if ( ! empty( $assoc_args['modules'] ) ) {
926
					$modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
927
928
					// Convert the array so that the keys are the module name and the value is true to indicate
929
					// that we want to sync the module
930
					$modules = array_map( '__return_true', array_flip( $modules ) );
931
				}
932
933 View Code Duplication
				foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
934
					if (
935
						'users' === $module_name &&
936
						isset( $assoc_args[ $module_name ] ) &&
937
						'initial' === $assoc_args[ $module_name ]
938
					) {
939
						$modules[ 'users' ] = 'initial';
0 ignored issues
show
Bug introduced by
The variable $modules does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
940
					} elseif ( isset( $assoc_args[ $module_name ] ) ) {
941
						$ids = explode( ',', $assoc_args[ $module_name ] );
942
						if ( count( $ids ) > 0 ) {
943
							$modules[ $module_name ] = $ids;
944
						}
945
					}
946
				}
947
948
				if ( empty( $modules ) ) {
949
					$modules = null;
950
				}
951
952
				// Kick off a full sync
953
				if ( Actions::do_full_sync( $modules ) ) {
954
					if ( $modules ) {
955
						/* translators: %s is a comma separated list of Jetpack modules */
956
						WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
957
					} else {
958
						WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
959
					}
960 View Code Duplication
				} else {
961
962
					// Reset sync settings to original.
963
					Settings::update_settings( $original_settings );
964
965
					if ( $modules ) {
966
						/* translators: %s is a comma separated list of Jetpack modules */
967
						WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
968
					} else {
969
						WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
970
					}
971
				}
972
973
				// Keep sending to WPCOM until there's nothing to send
974
				$i = 1;
975
				do {
976
					$result = Actions::$sender->do_full_sync();
977
					if ( is_wp_error( $result ) ) {
978
						$queue_empty_error = ( 'empty_queue_full_sync' == $result->get_error_code() );
979
						if ( ! $queue_empty_error || ( $queue_empty_error && ( 1 == $i ) ) ) {
980
							/* translators: %s is an error code  */
981
							WP_CLI::error( sprintf( __( 'Sync errored with code: %s', 'jetpack' ), $result->get_error_code() ) );
982
						}
983
					} else {
984
						if ( 1 == $i ) {
985
							WP_CLI::log( __( 'Sent data to WordPress.com', 'jetpack' ) );
986
						} else {
987
							WP_CLI::log( __( 'Sent more data to WordPress.com', 'jetpack' ) );
988
						}
989
					}
990
					$i++;
991
				} while ( $result && ! is_wp_error( $result ) );
992
993
				// Reset sync settings to original.
994
				Settings::update_settings( $original_settings );
995
996
				WP_CLI::success( __( 'Finished syncing to WordPress.com', 'jetpack' ) );
997
				break;
998
		}
999
	}
1000
1001
	/**
1002
	 * List the contents of a specific Jetpack sync queue.
1003
	 *
1004
	 * ## OPTIONS
1005
	 *
1006
	 * peek : List the 100 front-most items on the queue.
1007
	 *
1008
	 * ## EXAMPLES
1009
	 *
1010
	 * wp jetpack sync_queue full_sync peek
1011
	 *
1012
	 * @synopsis <incremental|full_sync> <peek>
1013
	 */
1014
	public function sync_queue( $args, $assoc_args ) {
1015
		if ( ! Actions::sync_allowed() ) {
1016
			WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
1017
		}
1018
1019
		$queue_name = isset( $args[0] ) ? $args[0] : 'sync';
1020
		$action = isset( $args[1] ) ? $args[1] : 'peek';
1021
1022
		// We map the queue name that way we can support more friendly queue names in the commands, but still use
1023
		// the queue name that the code expects.
1024
		$queue_name_map = $allowed_queues = array(
0 ignored issues
show
Unused Code introduced by
$allowed_queues is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1025
			'incremental' => 'sync',
1026
			'full'        => 'full_sync',
1027
		);
1028
		$mapped_queue_name = isset( $queue_name_map[ $queue_name ] ) ? $queue_name_map[ $queue_name ] : $queue_name;
1029
1030
		switch( $action ) {
1031
			case 'peek':
1032
				$queue = new Queue( $mapped_queue_name );
1033
				$items = $queue->peek( 100 );
1034
1035
				if ( empty( $items ) ) {
1036
					/* translators: %s is the name of the queue, either 'incremental' or 'full' */
1037
					WP_CLI::log( sprintf( __( 'Nothing is in the queue: %s', 'jetpack' ), $queue_name  ) );
1038
				} else {
1039
					$collection = array();
1040
					foreach ( $items as $item ) {
1041
						$collection[] = array(
1042
							'action'          => $item[0],
1043
							'args'            => json_encode( $item[1] ),
1044
							'current_user_id' => $item[2],
1045
							'microtime'       => $item[3],
1046
							'importing'       => (string) $item[4],
1047
						);
1048
					}
1049
					WP_CLI\Utils\format_items(
1050
						'table',
1051
						$collection,
1052
						array(
1053
							'action',
1054
							'args',
1055
							'current_user_id',
1056
							'microtime',
1057
							'importing',
1058
						)
1059
					);
1060
				}
1061
				break;
1062
		}
1063
	}
1064
1065
	/**
1066
	 * Cancel's the current Jetpack plan granted by this partner, if applicable
1067
	 *
1068
	 * Returns success or error JSON
1069
	 *
1070
	 * <token_json>
1071
	 * : JSON blob of WPCOM API token
1072
	 *  [--partner_tracking_id=<partner_tracking_id>]
1073
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1074
	 *
1075
	 *  * @synopsis <token_json> [--partner_tracking_id=<partner_tracking_id>]
1076
	 */
1077
	public function partner_cancel( $args, $named_args ) {
1078
		list( $token_json ) = $args;
1079
1080 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1081
			/* translators: %s is the invalid JSON string */
1082
			$this->partner_provision_error( new WP_Error( 'missing_access_token',  sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_access_token'.

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...
1083
		}
1084
1085
		if ( isset( $token->error ) ) {
1086
			$this->partner_provision_error( new WP_Error( $token->error, $token->message ) );
0 ignored issues
show
Bug introduced by
The variable $token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $token->error.

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...
1087
		}
1088
1089
		if ( ! isset( $token->access_token ) ) {
1090
			$this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_access_token'.

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...
1091
		}
1092
1093
		if ( Jetpack::validate_sync_error_idc_option() ) {
1094
			$this->partner_provision_error( new WP_Error(
1095
				'site_in_safe_mode',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'site_in_safe_mode'.

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...
1096
				esc_html__( 'Can not cancel a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
1097
			) );
1098
		}
1099
1100
		$site_identifier = Jetpack_Options::get_option( 'id' );
1101
1102
		if ( ! $site_identifier ) {
1103
			$site_identifier = Jetpack::build_raw_urls( get_home_url() );
1104
		}
1105
1106
		$request = array(
1107
			'headers' => array(
1108
				'Authorization' => "Bearer " . $token->access_token,
1109
				'Host'          => 'public-api.wordpress.com',
1110
			),
1111
			'timeout' => 60,
1112
			'method'  => 'POST',
1113
		);
1114
1115
		$url = sprintf( 'https://%s/rest/v1.3/jpphp/%s/partner-cancel', $this->get_api_host(), $site_identifier );
1116 View Code Duplication
		if ( ! empty( $named_args ) && ! empty( $named_args['partner_tracking_id'] ) ) {
1117
			$url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
1118
		}
1119
1120
		$result = Client::_wp_remote_request( $url, $request );
1121
1122
		Jetpack_Options::delete_option( 'onboarding' );
1123
1124
		if ( is_wp_error( $result ) ) {
1125
			$this->partner_provision_error( $result );
1126
		}
1127
1128
		WP_CLI::log( wp_remote_retrieve_body( $result ) );
1129
	}
1130
1131
	/**
1132
	 * Provision a site using a Jetpack Partner license
1133
	 *
1134
	 * Returns JSON blob
1135
	 *
1136
	 * ## OPTIONS
1137
	 *
1138
	 * <token_json>
1139
	 * : JSON blob of WPCOM API token
1140
	 * [--plan=<plan_name>]
1141
	 * : Slug of the requested plan, e.g. premium
1142
	 * [--wpcom_user_id=<user_id>]
1143
	 * : WordPress.com ID of user to connect as (must be whitelisted against partner key)
1144
	 * [--wpcom_user_email=<wpcom_user_email>]
1145
	 * : Override the email we send to WordPress.com for registration
1146
	 * [--onboarding=<onboarding>]
1147
	 * : Guide the user through an onboarding wizard
1148
	 * [--force_register=<register>]
1149
	 * : Whether to force a site to register
1150
	 * [--force_connect=<force_connect>]
1151
	 * : Force JPS to not reuse existing credentials
1152
	 * [--home_url=<home_url>]
1153
	 * : Overrides the home option via the home_url filter, or the WP_HOME constant
1154
	 * [--site_url=<site_url>]
1155
	 * : Overrides the siteurl option via the site_url filter, or the WP_SITEURL constant
1156
	 * [--partner_tracking_id=<partner_tracking_id>]
1157
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1158
	 *
1159
	 * ## EXAMPLES
1160
	 *
1161
	 *     $ wp jetpack partner_provision '{ some: "json" }' premium 1
1162
	 *     { success: true }
1163
	 *
1164
	 * @synopsis <token_json> [--wpcom_user_id=<user_id>] [--plan=<plan_name>] [--onboarding=<onboarding>] [--force_register=<register>] [--force_connect=<force_connect>] [--home_url=<home_url>] [--site_url=<site_url>] [--wpcom_user_email=<wpcom_user_email>] [--partner_tracking_id=<partner_tracking_id>]
1165
	 */
1166
	public function partner_provision( $args, $named_args ) {
1167
		list( $token_json ) = $args;
1168
1169 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1170
			/* translators: %s is the invalid JSON string */
1171
			$this->partner_provision_error( new WP_Error( 'missing_access_token',  sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_access_token'.

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...
1172
		}
1173
1174
		if ( isset( $token->error ) ) {
1175
			$message = isset( $token->message )
0 ignored issues
show
Bug introduced by
The variable $token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1176
				? $token->message
1177
				: '';
1178
			$this->partner_provision_error( new WP_Error( $token->error, $message ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $token->error.

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...
1179
		}
1180
1181
		if ( ! isset( $token->access_token ) ) {
1182
			$this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_access_token'.

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...
1183
		}
1184
1185
		require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
1186
1187
		$body_json = Jetpack_Provision::partner_provision( $token->access_token, $named_args );
1188
1189
		if ( is_wp_error( $body_json ) ) {
1190
			error_log( json_encode( array(
1191
				'success'       => false,
1192
				'error_code'    => $body_json->get_error_code(),
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1193
				'error_message' => $body_json->get_error_message()
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1194
			) ) );
1195
			exit( 1 );
1196
		}
1197
1198
		WP_CLI::log( json_encode( $body_json ) );
1199
	}
1200
1201
	/**
1202
	 * Manages your Jetpack sitemap
1203
	 *
1204
	 * ## OPTIONS
1205
	 *
1206
	 * rebuild : Rebuild all sitemaps
1207
	 * --purge : if set, will remove all existing sitemap data before rebuilding
1208
	 *
1209
	 * ## EXAMPLES
1210
	 *
1211
	 * wp jetpack sitemap rebuild
1212
	 *
1213
	 * @subcommand sitemap
1214
	 * @synopsis <rebuild> [--purge]
1215
	 */
1216
	public function sitemap( $args, $assoc_args ) {
1217
		if ( ! Jetpack::is_active() ) {
1218
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1219
		}
1220
		if ( ! Jetpack::is_module_active( 'sitemaps' ) ) {
1221
			WP_CLI::error( __( 'Jetpack Sitemaps module is not currently active. Activate it first if you want to work with sitemaps.', 'jetpack' ) );
1222
		}
1223
		if ( ! class_exists( 'Jetpack_Sitemap_Builder' ) ) {
1224
			WP_CLI::error( __( 'Jetpack Sitemaps module is active, but unavailable. This can happen if your site is set to discourage search engine indexing. Please enable search engine indexing to allow sitemap generation.', 'jetpack' ) );
1225
		}
1226
1227
		if ( isset( $assoc_args['purge'] ) && $assoc_args['purge'] ) {
1228
			$librarian = new Jetpack_Sitemap_Librarian();
1229
			$librarian->delete_all_stored_sitemap_data();
1230
		}
1231
1232
		$sitemap_builder = new Jetpack_Sitemap_Builder();
1233
		$sitemap_builder->update_sitemap();
1234
	}
1235
1236
	/**
1237
	 * Allows authorizing a user via the command line and will activate
1238
	 *
1239
	 * ## EXAMPLES
1240
	 *
1241
	 * wp jetpack authorize_user --token=123456789abcdef
1242
	 *
1243
	 * @synopsis --token=<value>
1244
	 */
1245
	public function authorize_user( $args, $named_args ) {
1246
		if ( ! is_user_logged_in() ) {
1247
			WP_CLI::error( __( 'Please select a user to authorize via the --user global argument.', 'jetpack' ) );
1248
		}
1249
1250
		if ( empty( $named_args['token'] ) ) {
1251
			WP_CLI::error( __( 'A non-empty token argument must be passed.', 'jetpack' ) );
1252
		}
1253
1254
		$is_master_user  = ! Jetpack::is_active();
1255
		$current_user_id = get_current_user_id();
1256
1257
		Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_master_user );
1258
1259
		WP_CLI::log( wp_json_encode( $named_args ) );
1260
1261
		if ( $is_master_user ) {
1262
			/**
1263
			 * Auto-enable SSO module for new Jetpack Start connections
1264
			*
1265
			* @since 5.0.0
1266
			*
1267
			* @param bool $enable_sso Whether to enable the SSO module. Default to true.
1268
			*/
1269
			$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1270
			Jetpack::handle_post_authorization_actions( $enable_sso, false );
1271
1272
			/* translators: %d is a user ID */
1273
			WP_CLI::success( sprintf( __( 'Authorized %d and activated default modules.', 'jetpack' ), $current_user_id ) );
1274
		} else {
1275
			/* translators: %d is a user ID */
1276
			WP_CLI::success( sprintf( __( 'Authorized %d.', 'jetpack' ), $current_user_id ) );
1277
		}
1278
	}
1279
1280
	/**
1281
	 * Allows calling a WordPress.com API endpoint using the current blog's token.
1282
	 *
1283
	 * ## OPTIONS
1284
	 * --resource=<resource>
1285
	 * : The resource to call with the current blog's token, where `%d` represents the current blog's ID.
1286
	 *
1287
	 * [--api_version=<api_version>]
1288
	 * : The API version to query against.
1289
	 *
1290
	 * [--base_api_path=<base_api_path>]
1291
	 * : The base API path to query.
1292
	 * ---
1293
	 * default: rest
1294
	 * ---
1295
	 *
1296
	 * [--body=<body>]
1297
	 * : A JSON encoded string representing arguments to send in the body.
1298
	 *
1299
	 * [--field=<value>]
1300
	 * : Any number of arguments that should be passed to the resource.
1301
	 *
1302
	 * [--pretty]
1303
	 * : Will pretty print the results of a successful API call.
1304
	 *
1305
	 * [--strip-success]
1306
	 * : Will remove the green success label from successful API calls.
1307
	 *
1308
	 * ## EXAMPLES
1309
	 *
1310
	 * wp jetpack call_api --resource='/sites/%d'
1311
	 */
1312
	public function call_api( $args, $named_args ) {
1313
		if ( ! Jetpack::is_active() ) {
1314
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1315
		}
1316
1317
		$consumed_args = array(
1318
			'resource',
1319
			'api_version',
1320
			'base_api_path',
1321
			'body',
1322
			'pretty',
1323
		);
1324
1325
		// Get args that should be passed to resource.
1326
		$other_args = array_diff_key( $named_args, array_flip( $consumed_args ) );
1327
1328
		$decoded_body = ! empty( $named_args['body'] )
1329
			? json_decode( $named_args['body'], true )
1330
			: false;
1331
1332
		$resource_url = ( false === strpos( $named_args['resource'], '%d' ) )
1333
			? $named_args['resource']
1334
			: sprintf( $named_args['resource'], Jetpack_Options::get_option( 'id' ) );
1335
1336
		$response = Client::wpcom_json_api_request_as_blog(
1337
			$resource_url,
1338
			empty( $named_args['api_version'] ) ? Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
1339
			$other_args,
1340
			empty( $decoded_body ) ? null : $decoded_body,
1341
			empty( $named_args['base_api_path'] ) ? 'rest' : $named_args['base_api_path']
1342
		);
1343
1344 View Code Duplication
		if ( is_wp_error( $response ) ) {
1345
			WP_CLI::error( sprintf(
1346
				/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
1347
				__( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
1348
				$resource_url,
1349
				$response->get_error_code(),
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1350
				$response->get_error_message()
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1351
			) );
1352
		}
1353
1354
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1355
			WP_CLI::error( sprintf(
1356
				/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
1357
				__( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
1358
				$resource_url,
1359
				wp_remote_retrieve_response_code( $response )
1360
			) );
1361
		}
1362
1363
		$output = wp_remote_retrieve_body( $response );
1364
		if ( isset( $named_args['pretty'] ) ) {
1365
			$decoded_output = json_decode( $output );
1366
			if ( $decoded_output ) {
1367
				$output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
1368
			}
1369
		}
1370
1371
		if ( isset( $named_args['strip-success'] ) ) {
1372
			WP_CLI::log( $output );
1373
			WP_CLI::halt( 0 );
1374
		}
1375
1376
		WP_CLI::success( $output );
1377
	}
1378
1379
	/**
1380
	 * Allows uploading SSH Credentials to the current site for backups, restores, and security scanning.
1381
	 *
1382
	 * ## OPTIONS
1383
	 *
1384
	 * [--host=<host>]
1385
	 * : The SSH server's address.
1386
	 *
1387
	 * [--ssh-user=<user>]
1388
	 * : The username to use to log in to the SSH server.
1389
	 *
1390
	 * [--pass=<pass>]
1391
	 * : The password used to log in, if using a password. (optional)
1392
	 *
1393
	 * [--kpri=<kpri>]
1394
	 * : The private key used to log in, if using a private key. (optional)
1395
	 *
1396
	 * [--pretty]
1397
	 * : Will pretty print the results of a successful API call. (optional)
1398
	 *
1399
	 * [--strip-success]
1400
	 * : Will remove the green success label from successful API calls. (optional)
1401
	 *
1402
	 * ## EXAMPLES
1403
	 *
1404
	 * wp jetpack upload_ssh_creds --host=example.com --ssh-user=example --pass=password
1405
	 * wp jetpack updload_ssh_creds --host=example.com --ssh-user=example --kpri=key
1406
	 */
1407
	public function upload_ssh_creds( $args, $named_args ) {
1408
		if ( ! Jetpack::is_active() ) {
1409
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1410
		}
1411
1412
		$required_args = array(
1413
			'host',
1414
			'ssh-user',
1415
		);
1416
1417
		foreach ( $required_args as $arg ) {
1418
			if ( empty( $named_args[ $arg ] ) ) {
1419
				WP_CLI::error(
1420
					sprintf(
1421
						/* translators: %s is a slug, such as 'host'. */
1422
						__( '`%s` cannot be empty.', 'jetpack' ),
1423
						$arg
1424
					)
1425
				);
1426
			}
1427
		}
1428
1429
		if ( empty( $named_args['pass'] ) && empty( $named_args['kpri'] ) ) {
1430
			WP_CLI::error( __( 'Both `pass` and `kpri` fields cannot be blank.', 'jetpack' ) );
1431
		}
1432
1433
		$values = array(
1434
			'credentials' => array(
1435
				'site_url' => get_site_url(),
1436
				'abspath'  => ABSPATH,
1437
				'protocol' => 'ssh',
1438
				'port'     => 22,
1439
				'role'     => 'main',
1440
				'host'     => $named_args['host'],
1441
				'user'     => $named_args['ssh-user'],
1442
				'pass'     => empty( $named_args['pass'] ) ? '' : $named_args['pass'],
1443
				'kpri'     => empty( $named_args['kpri'] ) ? '' : $named_args['kpri'],
1444
			),
1445
		);
1446
1447
		$named_args = wp_parse_args(
1448
			array(
1449
				'resource'    => '/activity-log/%d/update-credentials',
1450
				'method'      => 'POST',
1451
				'api_version' => '1.1',
1452
				'body'        => wp_json_encode( $values ),
1453
				'timeout'     => 30,
1454
			),
1455
			$named_args
1456
		);
1457
1458
		self::call_api( $args, $named_args );
1459
	}
1460
1461
	/**
1462
	 * API wrapper for getting stats from the WordPress.com API for the current site.
1463
	 *
1464
	 * ## OPTIONS
1465
	 *
1466
	 * [--quantity=<quantity>]
1467
	 * : The number of units to include.
1468
	 * ---
1469
	 * default: 30
1470
	 * ---
1471
	 *
1472
	 * [--period=<period>]
1473
	 * : The unit of time to query stats for.
1474
	 * ---
1475
	 * default: day
1476
	 * options:
1477
	 *  - day
1478
	 *  - week
1479
	 *  - month
1480
	 *  - year
1481
	 * ---
1482
	 *
1483
	 * [--date=<date>]
1484
	 * : The latest date to return stats for. Ex. - 2018-01-01.
1485
	 *
1486
	 * [--pretty]
1487
	 * : Will pretty print the results of a successful API call.
1488
	 *
1489
	 * [--strip-success]
1490
	 * : Will remove the green success label from successful API calls.
1491
	 *
1492
	 * ## EXAMPLES
1493
	 *
1494
	 * wp jetpack get_stats
1495
	 */
1496
	public function get_stats( $args, $named_args ) {
1497
		$selected_args = array_intersect_key(
1498
			$named_args,
1499
			array_flip( array(
1500
				'quantity',
1501
				'date',
1502
			) )
1503
		);
1504
1505
		// The API expects unit, but period seems to be more correct.
1506
		$selected_args['unit'] = $named_args['period'];
1507
1508
		$command = sprintf(
1509
			'jetpack call_api --resource=/sites/%d/stats/%s',
1510
			Jetpack_Options::get_option( 'id' ),
1511
			add_query_arg( $selected_args, 'visits' )
1512
		);
1513
1514
		if ( isset( $named_args['pretty'] ) ) {
1515
			$command .= ' --pretty';
1516
		}
1517
1518
		if ( isset( $named_args['strip-success'] ) ) {
1519
			$command .= ' --strip-success';
1520
		}
1521
1522
		WP_CLI::runcommand(
1523
			$command,
1524
			array(
1525
				'launch' => false, // Use the current process.
1526
			)
1527
		);
1528
	}
1529
1530
	/**
1531
	 * Allows management of publicize connections.
1532
	 *
1533
	 * ## OPTIONS
1534
	 *
1535
	 * <list|disconnect>
1536
	 * : The action to perform.
1537
	 * ---
1538
	 * options:
1539
	 *   - list
1540
	 *   - disconnect
1541
	 * ---
1542
	 *
1543
	 * [<identifier>]
1544
	 * : The connection ID or service to perform an action on.
1545
	 *
1546
	 * [--format=<format>]
1547
	 * : Allows overriding the output of the command when listing connections.
1548
	 * ---
1549
	 * default: table
1550
	 * options:
1551
	 *   - table
1552
	 *   - json
1553
	 *   - csv
1554
	 *   - yaml
1555
	 *   - ids
1556
	 *   - count
1557
	 * ---
1558
	 *
1559
	 * ## EXAMPLES
1560
	 *
1561
	 *     # List all publicize connections.
1562
	 *     $ wp jetpack publicize list
1563
	 *
1564
	 *     # List publicize connections for a given service.
1565
	 *     $ wp jetpack publicize list twitter
1566
	 *
1567
	 *     # List all publicize connections for a given user.
1568
	 *     $ wp --user=1 jetpack publicize list
1569
	 *
1570
	 *     # List all publicize connections for a given user and service.
1571
	 *     $ wp --user=1 jetpack publicize list twitter
1572
	 *
1573
	 *     # Display details for a given connection.
1574
	 *     $ wp jetpack publicize list 123456
1575
	 *
1576
	 *     # Diconnection a given connection.
1577
	 *     $ wp jetpack publicize disconnect 123456
1578
	 *
1579
	 *     # Disconnect all connections.
1580
	 *     $ wp jetpack publicize disconnect all
1581
	 *
1582
	 *     # Disconnect all connections for a given service.
1583
	 *     $ wp jetpack publicize disconnect twitter
1584
	 */
1585
	public function publicize( $args, $named_args ) {
1586
		if ( ! Jetpack::is_active() ) {
1587
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1588
		}
1589
1590
		if ( ! Jetpack::is_module_active( 'publicize' ) ) {
1591
			WP_CLI::error( __( 'The publicize module is not active.', 'jetpack' ) );
1592
		}
1593
1594
		if ( Jetpack::is_development_mode() ) {
1595
			if (
1596
				! defined( 'JETPACK_DEV_DEBUG' ) &&
1597
				! has_filter( 'jetpack_development_mode' ) &&
1598
				false === strpos( site_url(), '.' )
1599
			) {
1600
				WP_CLI::error( __( "Jetpack is current in development mode because the site url does not contain a '.', which often occurs when dynamically setting the WP_SITEURL constant. While in development mode, the publicize module will not load.", 'jetpack' ) );
1601
			}
1602
1603
			WP_CLI::error( __( 'Jetpack is currently in development mode, so the publicize module will not load.', 'jetpack' ) );
1604
		}
1605
1606
		if ( ! class_exists( 'Publicize' ) ) {
1607
			WP_CLI::error( __( 'The publicize module is not loaded.', 'jetpack' ) );
1608
		}
1609
1610
		$action        = $args[0];
1611
		$publicize     = new Publicize();
1612
		$identifier    = ! empty( $args[1] ) ? $args[1] : false;
1613
		$services      = array_keys( $publicize->get_services() );
1614
		$id_is_service = in_array( $identifier, $services, true );
1615
1616
		switch ( $action ) {
1617
			case 'list':
1618
				$connections_to_return = array();
1619
1620
				// For the CLI command, let's return all connections when a user isn't specified. This
1621
				// differs from the logic in the Publicize class.
1622
				$option_connections = is_user_logged_in()
1623
					? (array) $publicize->get_all_connections_for_user()
1624
					: (array) $publicize->get_all_connections();
1625
1626
				foreach ( $option_connections as $service_name => $connections ) {
1627
					foreach ( (array) $connections as $id => $connection ) {
1628
						$connection['id']        = $id;
1629
						$connection['service']   = $service_name;
1630
						$connections_to_return[] = $connection;
1631
					}
1632
				}
1633
1634
				if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
1635
					$temp_connections      = $connections_to_return;
1636
					$connections_to_return = array();
1637
1638
					foreach ( $temp_connections as $connection ) {
1639
						if ( $identifier === $connection['service'] ) {
1640
							$connections_to_return[] = $connection;
1641
						}
1642
					}
1643
				}
1644
1645
				if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
1646
					$connections_to_return = wp_list_filter( $connections_to_return, array( 'id' => $identifier ) );
1647
				}
1648
1649
				$expected_keys = array(
1650
					'id',
1651
					'service',
1652
					'user_id',
1653
					'provider',
1654
					'issued',
1655
					'expires',
1656
					'external_id',
1657
					'external_name',
1658
					'external_display',
1659
					'type',
1660
					'connection_data',
1661
				);
1662
1663
				// Somehow, a test site ended up in a state where $connections_to_return looked like:
1664
				// array( array( array( 'id' => 0, 'service' => 0 ) ) ) // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1665
				// This caused the CLI command to error when running WP_CLI\Utils\format_items() below. So
1666
				// to minimize future issues, this nested loop will remove any connections that don't contain
1667
				// any keys that we expect.
1668
				foreach ( (array) $connections_to_return as $connection_key => $connection ) {
1669
					foreach ( $expected_keys as $expected_key ) {
1670
						if ( ! isset( $connection[ $expected_key ] ) ) {
1671
							unset( $connections_to_return[ $connection_key ] );
1672
							continue;
1673
						}
1674
					}
1675
				}
1676
1677
				if ( empty( $connections_to_return ) ) {
1678
					return false;
1679
				}
1680
1681
				WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
1682
				break; // list.
1683
			case 'disconnect':
1684
				if ( ! $identifier ) {
1685
					WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
1686
				}
1687
1688
				// If the connection ID is 'all' then delete all connections. If the connection ID
1689
				// matches a service, delete all connections for that service.
1690
				if ( 'all' === $identifier || $id_is_service ) {
1691
					if ( 'all' === $identifier ) {
1692
						WP_CLI::log( __( "You're about to delete all publicize connections.", 'jetpack' ) );
1693
					} else {
1694
						/* translators: %s is a lowercase string for a social network. */
1695
						WP_CLI::log( sprintf( __( "You're about to delete all publicize connections to %s.", 'jetpack' ), $identifier ) );
1696
					}
1697
1698
					jetpack_cli_are_you_sure();
1699
1700
					$connections = array();
1701
					$service     = $identifier;
1702
1703
					$option_connections = is_user_logged_in()
1704
						? (array) $publicize->get_all_connections_for_user()
1705
						: (array) $publicize->get_all_connections();
1706
1707
					if ( 'all' === $service ) {
1708
						foreach ( (array) $option_connections as $service_name => $service_connections ) {
1709
							foreach ( $service_connections as $id => $connection ) {
1710
								$connections[ $id ] = $connection;
1711
							}
1712
						}
1713
					} elseif ( ! empty( $option_connections[ $service ] ) ) {
1714
						$connections = $option_connections[ $service ];
1715
					}
1716
1717
					if ( ! empty( $connections ) ) {
1718
						$count    = count( $connections );
1719
						$progress = \WP_CLI\Utils\make_progress_bar(
1720
							/* translators: %s is a lowercase string for a social network. */
1721
							sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
1722
							$count
1723
						);
1724
1725
						foreach ( $connections as $id => $connection ) {
1726
							if ( false === $publicize->disconnect( false, $id ) ) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1727
								WP_CLI::error( sprintf(
1728
									/* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
1729
									__( 'Publicize connection %d could not be disconnected', 'jetpack' ),
1730
									$id
1731
								) );
1732
							}
1733
1734
							$progress->tick();
1735
						}
1736
1737
						$progress->finish();
1738
1739
						if ( 'all' === $service ) {
1740
							WP_CLI::success( __( 'All publicize connections were successfully disconnected.', 'jetpack' ) );
1741
						} else {
1742
							/* translators: %s is a lowercase string for a social network. */
1743
							WP_CLI::success( __( 'All publicize connections to %s were successfully disconnected.', 'jetpack' ), $service );
1744
						}
1745
					}
1746
				} else {
1747
					if ( false !== $publicize->disconnect( false, $identifier ) ) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1748
						/* translators: %d is a numeric ID. Example: 1234. */
1749
						WP_CLI::success( sprintf( __( 'Publicize connection %d has been disconnected.', 'jetpack' ), $identifier ) );
1750
					} else {
1751
						/* translators: %d is a numeric ID. Example: 1234. */
1752
						WP_CLI::error( sprintf( __( 'Publicize connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
1753
					}
1754
				}
1755
				break; // disconnect.
1756
		}
1757
	}
1758
1759
	private function get_api_host() {
1760
		$env_api_host = getenv( 'JETPACK_START_API_HOST', true );
1761
		return $env_api_host ? $env_api_host : JETPACK__WPCOM_JSON_API_HOST;
1762
	}
1763
1764
	private function partner_provision_error( $error ) {
1765
		WP_CLI::log( json_encode( array(
1766
			'success'       => false,
1767
			'error_code'    => $error->get_error_code(),
1768
			'error_message' => $error->get_error_message()
1769
		) ) );
1770
		exit( 1 );
1771
	}
1772
1773
	/**
1774
	 * Creates the essential files in Jetpack to start building a Gutenberg block or plugin.
1775
	 *
1776
	 * ## TYPES
1777
	 *
1778
	 * block: it creates a Jetpack block. All files will be created in a directory under extensions/blocks named based on the block title or a specific given slug.
1779
	 *
1780
	 * ## BLOCK TYPE OPTIONS
1781
	 *
1782
	 * The first parameter is the block title and it's not associative. Add it wrapped in quotes.
1783
	 * The title is also used to create the slug and the edit PHP class name. If it's something like "Logo gallery", the slug will be 'logo-gallery' and the class name will be LogoGalleryEdit.
1784
	 * --slug: Specific slug to identify the block that overrides the one generated based on the title.
1785
	 * --description: Allows to provide a text description of the block.
1786
	 * --keywords: Provide up to three keywords separated by comma so users can find this block when they search in Gutenberg's inserter.
1787
	 *
1788
	 * ## BLOCK TYPE EXAMPLES
1789
	 *
1790
	 * wp jetpack scaffold block "Cool Block"
1791
	 * wp jetpack scaffold block "Amazing Rock" --slug="good-music" --description="Rock the best music on your site"
1792
	 * wp jetpack scaffold block "Jukebox" --keywords="music, audio, media"
1793
	 *
1794
	 * @subcommand scaffold block
1795
	 * @synopsis <type> <title> [--slug] [--description] [--keywords]
1796
	 *
1797
	 * @param array $args       Positional parameters, when strings are passed, wrap them in quotes.
1798
	 * @param array $assoc_args Associative parameters like --slug="nice-block".
1799
	 */
1800
	public function scaffold( $args, $assoc_args ) {
1801
		// It's ok not to check if it's set, because otherwise WPCLI exits earlier.
1802
		switch ( $args[0] ) {
1803
			case 'block':
1804
				$this->block( $args, $assoc_args );
1805
				break;
1806
			default:
1807
				/* translators: %s is the subcommand */
1808
				WP_CLI::error( sprintf( esc_html__( 'Invalid subcommand %s.', 'jetpack' ), $args[0] ) . ' 👻' );
1809
				exit( 1 );
1810
		}
1811
	}
1812
1813
	/**
1814
	 * Creates the essential files in Jetpack to build a Gutenberg block.
1815
	 *
1816
	 * @param array $args       Positional parameters. Only one is used, that corresponds to the block title.
1817
	 * @param array $assoc_args Associative parameters defined in the scaffold() method.
1818
	 */
1819
	public function block( $args, $assoc_args ) {
1820 View Code Duplication
		if ( isset( $args[1] ) ) {
1821
			$title = ucwords( $args[1] );
1822
		} else {
1823
			WP_CLI::error( esc_html__( 'The title parameter is required.', 'jetpack' ) . ' 👻' );
1824
			exit( 1 );
1825
		}
1826
1827
		$slug = isset( $assoc_args['slug'] )
1828
			? $assoc_args['slug']
1829
			: sanitize_title( $title );
1830
1831
		if ( preg_match( '#^jetpack/#', $slug ) ) {
1832
			$slug = preg_replace( '#^jetpack/#', '', $slug );
1833
		}
1834
1835
		if ( ! preg_match( '/^[a-z][a-z0-9\-]*$/', $slug ) ) {
1836
			WP_CLI::error( esc_html__( 'Invalid block slug. They can contain only lowercase alphanumeric characters or dashes, and start with a letter', 'jetpack' ) . ' 👻' );
1837
		}
1838
1839
		global $wp_filesystem;
1840
		if ( ! WP_Filesystem() ) {
1841
			WP_CLI::error( esc_html__( "Can't write files", 'jetpack' ) . ' 😱' );
1842
		}
1843
1844
		$path = JETPACK__PLUGIN_DIR . "extensions/blocks/$slug";
1845
1846
		if ( $wp_filesystem->exists( $path ) && $wp_filesystem->is_dir( $path ) ) {
1847
			/* translators: %s is path to the conflicting block */
1848
			WP_CLI::error( sprintf( esc_html__( 'Name conflicts with the existing block %s', 'jetpack' ), $path ) . ' ⛔️' );
1849
			exit( 1 );
1850
		}
1851
1852
		$wp_filesystem->mkdir( $path );
1853
1854
		$hasKeywords = isset( $assoc_args['keywords'] );
1855
1856
		$files = array(
1857
			"$path/$slug.php" => $this->render_block_file( 'block-register-php', array(
1858
				'slug' => $slug,
1859
				'title' => $title,
1860
				'underscoredSlug' => str_replace( '-', '_', $slug ),
1861
			) ),
1862
			"$path/index.js" => $this->render_block_file( 'block-index-js', array(
1863
				'slug' => $slug,
1864
				'title' => $title,
1865
				'description' => isset( $assoc_args['description'] )
1866
					? $assoc_args['description']
1867
					: $title,
1868
				'keywords' => $hasKeywords
1869
					? array_map( function( $keyword ) {
1870
						// Construction necessary for Mustache lists
1871
						return array( 'keyword' => trim( $keyword ) );
1872
					}, explode( ',', $assoc_args['keywords'], 3 ) )
1873
					: '',
1874
				'hasKeywords' => $hasKeywords
1875
			) ),
1876
			"$path/editor.js" => $this->render_block_file( 'block-editor-js' ),
1877
			"$path/editor.scss" => $this->render_block_file( 'block-editor-scss', array(
1878
				'slug' => $slug,
1879
				'title' => $title,
1880
			) ),
1881
			"$path/edit.js" => $this->render_block_file( 'block-edit-js', array(
1882
				'title' => $title,
1883
				'className' => str_replace( ' ', '', ucwords( str_replace( '-', ' ', $slug ) ) ),
1884
			) )
1885
		);
1886
1887
		$files_written = array();
1888
1889
		foreach ( $files as $filename => $contents ) {
1890
			if ( $wp_filesystem->put_contents( $filename, $contents ) ) {
1891
				$files_written[] = $filename;
1892
			} else {
1893
				/* translators: %s is a file name */
1894
				WP_CLI::error( sprintf( esc_html__( 'Error creating %s', 'jetpack' ), $filename ) );
1895
			}
1896
		}
1897
1898
		if ( empty( $files_written ) ) {
1899
			WP_CLI::log( esc_html__( 'No files were created', 'jetpack' ) );
1900
		} else {
1901
			// Load index.json and insert the slug of the new block in the production array
1902
			$block_list_path = JETPACK__PLUGIN_DIR . 'extensions/index.json';
1903
			$block_list = $wp_filesystem->get_contents( $block_list_path );
1904
			if ( empty( $block_list ) ) {
1905
				/* translators: %s is the path to the file with the block list */
1906
				WP_CLI::error( sprintf( esc_html__( 'Error fetching contents of %s', 'jetpack' ), $block_list_path ) );
1907
			} else if ( false === stripos( $block_list, $slug ) ) {
1908
				$new_block_list = json_decode( $block_list );
1909
				$new_block_list->beta[] = $slug;
1910
				if ( ! $wp_filesystem->put_contents( $block_list_path, wp_json_encode( $new_block_list ) ) ) {
1911
					/* translators: %s is the path to the file with the block list */
1912
					WP_CLI::error( sprintf( esc_html__( 'Error writing new %s', 'jetpack' ), $block_list_path ) );
1913
				}
1914
			}
1915
1916
			WP_CLI::success( sprintf(
1917
				/* translators: the placeholders are a human readable title, and a series of words separated by dashes */
1918
				esc_html__( 'Successfully created block %s with slug %s', 'jetpack' ) . ' 🎉' . "\n" .
1919
				"--------------------------------------------------------------------------------------------------------------------\n" .
1920
				/* translators: the placeholder is a directory path */
1921
				esc_html__( 'The files were created at %s', 'jetpack' ) . "\n" .
1922
				esc_html__( 'To start using the block, build the blocks with yarn run build-extensions', 'jetpack' ) . "\n" .
1923
				/* translators: the placeholder is a file path */
1924
				esc_html__( 'The block slug has been added to the beta list at %s', 'jetpack' ) . "\n" .
1925
				esc_html__( 'To load the block, add the constant JETPACK_BETA_BLOCKS as true to your wp-config.php file', 'jetpack' ) . "\n" .
1926
				/* translators: the placeholder is a URL */
1927
				"\n" . esc_html__( 'Read more at %s', 'jetpack' ) . "\n",
1928
				$title,
1929
				$slug,
1930
				$path,
1931
				$block_list_path,
1932
				'https://github.com/Automattic/jetpack/blob/master/extensions/README.md#develop-new-blocks'
1933
			) . '--------------------------------------------------------------------------------------------------------------------' );
1934
		}
1935
	}
1936
1937
	/**
1938
	 * Built the file replacing the placeholders in the template with the data supplied.
1939
	 *
1940
	 * @param string $template
1941
	 * @param array $data
1942
	 *
1943
	 * @return string mixed
1944
	 */
1945
	private static function render_block_file( $template, $data = array() ) {
1946
		return \WP_CLI\Utils\mustache_render( JETPACK__PLUGIN_DIR . "wp-cli-templates/$template.mustache", $data );
1947
	}
1948
}
1949
1950
/*
1951
 * Standard "ask for permission to continue" function.
1952
 * If action cancelled, ask if they need help.
1953
 *
1954
 * Written outside of the class so it's not listed as an executable command w/ 'wp jetpack'
1955
 *
1956
 * @param $flagged   bool   false = normal option | true = flagged by get_jetpack_options_for_reset()
1957
 * @param $error_msg string (optional)
1958
 */
1959
function jetpack_cli_are_you_sure( $flagged = false, $error_msg = false ) {
1960
	$cli = new Jetpack_CLI();
1961
1962
	// Default cancellation message
1963
	if ( ! $error_msg ) {
1964
		$error_msg =
1965
			__( 'Action cancelled. Have a question?', 'jetpack' )
1966
			. ' '
1967
			. $cli->green_open
1968
			. 'jetpack.com/support'
1969
			.  $cli->color_close;
1970
	}
1971
1972
	if ( ! $flagged ) {
1973
		$prompt_message = _x( 'Are you sure? This cannot be undone. Type "yes" to continue:', '"yes" is a command - do not translate.', 'jetpack' );
1974
	} else {
1975
		$prompt_message = _x( 'Are you sure? Modifying this option may disrupt your Jetpack connection.  Type "yes" to continue.', '"yes" is a command - do not translate.', 'jetpack' );
1976
	}
1977
1978
	WP_CLI::line( $prompt_message );
1979
	$handle = fopen( "php://stdin", "r" );
1980
	$line = fgets( $handle );
1981
	if ( 'yes' != trim( $line ) ){
1982
		WP_CLI::error( $error_msg );
1983
	}
1984
}
1985