Completed
Push — update/composer-lock ( 4bc3dc...ab53f1 )
by Jeremy
08:14
created

Jetpack_CLI::disconnect()   C

Complexity

Conditions 13
Paths 161

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
nc 161
nop 2
dl 0
loc 59
rs 6.1083
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
WP_CLI::add_command( 'jetpack', 'Jetpack_CLI' );
4
5
use Automattic\Jetpack\Connection\Client;
6
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
7
use Automattic\Jetpack\Sync\Actions;
8
use Automattic\Jetpack\Sync\Listener;
9
use Automattic\Jetpack\Sync\Queue;
10
use Automattic\Jetpack\Sync\Settings;
11
12
/**
13
 * Control your local Jetpack installation.
14
 */
15
class Jetpack_CLI extends WP_CLI_Command {
16
	// Aesthetics.
17
	public $green_open  = "\033[32m";
18
	public $red_open    = "\033[31m";
19
	public $yellow_open = "\033[33m";
20
	public $color_close = "\033[0m";
21
22
	/**
23
	 * Get Jetpack Details
24
	 *
25
	 * ## OPTIONS
26
	 *
27
	 * empty: Leave it empty for basic stats
28
	 *
29
	 * full: View full stats.  It's the data from the heartbeat
30
	 *
31
	 * ## EXAMPLES
32
	 *
33
	 * wp jetpack status
34
	 * wp jetpack status full
35
	 */
36
	public function status( $args, $assoc_args ) {
37
		jetpack_require_lib( 'debugger' );
38
39
		/* translators: %s is the site URL */
40
		WP_CLI::line( sprintf( __( 'Checking status for %s', 'jetpack' ), esc_url( get_home_url() ) ) );
41
42 View Code Duplication
		if ( isset( $args[0] ) && 'full' !== $args[0] ) {
43
			/* translators: %s is a command like "prompt" */
44
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $args[0] ) );
45
		}
46
47
		$master_user_email = Jetpack::get_master_user_email();
48
49
		$cxntests = new Jetpack_Cxn_Tests();
50
51
		if ( $cxntests->pass() ) {
52
			$cxntests->output_results_for_cli();
53
54
			WP_CLI::success( __( 'Jetpack is currently connected to WordPress.com', 'jetpack' ) );
55
		} else {
56
			$error = array();
57
			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...
58
				$error[] = $fail['name'] . ': ' . $fail['message'];
59
			}
60
			WP_CLI::error_multi_line( $error );
61
62
			$cxntests->output_results_for_cli();
63
64
			WP_CLI::error( __( 'Jetpack connection is broken.', 'jetpack' ) ); // Exit CLI.
65
		}
66
67
		/* translators: %s is current version of Jetpack, for example 7.3 */
68
		WP_CLI::line( sprintf( __( 'The Jetpack Version is %s', 'jetpack' ), JETPACK__VERSION ) );
69
		/* translators: %d is WP.com ID of this blog */
70
		WP_CLI::line( sprintf( __( 'The WordPress.com blog_id is %d', 'jetpack' ), Jetpack_Options::get_option( 'id' ) ) );
71
		/* translators: %s is the email address of the connection owner */
72
		WP_CLI::line( sprintf( __( 'The WordPress.com account for the primary connection is %s', 'jetpack' ), $master_user_email ) );
73
74
		/*
75
		 * Are they asking for all data?
76
		 *
77
		 * Loop through heartbeat data and organize by priority.
78
		 */
79
		$all_data = ( isset( $args[0] ) && 'full' == $args[0] ) ? 'full' : false;
80
		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...
81
			// Heartbeat data
82
			WP_CLI::line( "\n" . __( 'Additional data: ', 'jetpack' ) );
83
84
			// Get the filtered heartbeat data.
85
			// Filtered so we can color/list by severity
86
			$stats = Jetpack::jetpack_check_heartbeat_data();
87
88
			// Display red flags first
89
			foreach ( $stats['bad'] as $stat => $value ) {
90
				printf( "$this->red_open%-'.16s %s $this->color_close\n", $stat, $value );
91
			}
92
93
			// Display caution warnings next
94
			foreach ( $stats['caution'] as $stat => $value ) {
95
				printf( "$this->yellow_open%-'.16s %s $this->color_close\n", $stat, $value );
96
			}
97
98
			// The rest of the results are good!
99
			foreach ( $stats['good'] as $stat => $value ) {
100
101
				// Modules should get special spacing for aestetics
102
				if ( strpos( $stat, 'odule-' ) ) {
103
					printf( "%-'.30s %s\n", $stat, $value );
104
					usleep( 4000 ); // For dramatic effect lolz
105
					continue;
106
				}
107
				printf( "%-'.16s %s\n", $stat, $value );
108
				usleep( 4000 ); // For dramatic effect lolz
109
			}
110
		} else {
111
			// Just the basics
112
			WP_CLI::line( "\n" . _x( "View full status with 'wp jetpack status full'", '"wp jetpack status full" is a command - do not translate', 'jetpack' ) );
113
		}
114
	}
115
116
	/**
117
	 * Tests the active connection
118
	 *
119
	 * 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.
120
	 *
121
	 * ## EXAMPLES
122
	 *
123
	 * wp jetpack test-connection
124
	 *
125
	 * @subcommand test-connection
126
	 */
127
	public function test_connection( $args, $assoc_args ) {
128
129
		/* translators: %s is the site URL */
130
		WP_CLI::line( sprintf( __( 'Testing connection for %s', 'jetpack' ), esc_url( get_site_url() ) ) );
131
132
		if ( ! Jetpack::is_active() ) {
133
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
134
		}
135
136
		$response = Client::wpcom_json_api_request_as_blog(
137
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
138
			Client::WPCOM_JSON_API_VERSION
139
		);
140
141 View Code Duplication
		if ( is_wp_error( $response ) ) {
142
			/* translators: %1$s is the error code, %2$s is the error message */
143
			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...
144
		}
145
146
		$body = wp_remote_retrieve_body( $response );
147
		if ( ! $body ) {
148
			WP_CLI::error( __( 'Failed to test connection (empty response body)', 'jetpack' ) );
149
		}
150
151
		$result       = json_decode( $body );
152
		$is_connected = (bool) $result->connected;
153
		$message      = $result->message;
154
155
		if ( $is_connected ) {
156
			WP_CLI::success( $message );
157
		} else {
158
			WP_CLI::error( $message );
159
		}
160
	}
161
162
	/**
163
	 * Disconnect Jetpack Blogs or Users
164
	 *
165
	 * ## OPTIONS
166
	 *
167
	 * blog: Disconnect the entire blog.
168
	 *
169
	 * user <user_identifier>: Disconnect a specific user from WordPress.com.
170
	 *
171
	 * Please note, the primary account that the blog is connected
172
	 * to WordPress.com with cannot be disconnected without
173
	 * disconnecting the entire blog.
174
	 *
175
	 * ## EXAMPLES
176
	 *
177
	 * wp jetpack disconnect blog
178
	 * wp jetpack disconnect user 13
179
	 * wp jetpack disconnect user username
180
	 * wp jetpack disconnect user [email protected]
181
	 *
182
	 * @synopsis <blog|user> [<user_identifier>]
183
	 */
184
	public function disconnect( $args, $assoc_args ) {
185
		if ( ! Jetpack::is_active() ) {
186
			WP_CLI::success( __( 'The site is not currently connected, so nothing to do!', 'jetpack' ) );
187
			return;
188
		}
189
190
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
191
		if ( ! in_array( $action, array( 'blog', 'user', 'prompt' ) ) ) {
192
			/* translators: %s is a command like "prompt" */
193
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
194
		}
195
196
		if ( in_array( $action, array( 'user' ) ) ) {
197
			if ( isset( $args[1] ) ) {
198
				$user_id = $args[1];
199
				if ( ctype_digit( $user_id ) ) {
200
					$field   = 'id';
201
					$user_id = (int) $user_id;
202
				} elseif ( is_email( $user_id ) ) {
203
					$field   = 'email';
204
					$user_id = sanitize_user( $user_id, true );
205
				} else {
206
					$field   = 'login';
207
					$user_id = sanitize_user( $user_id, true );
208
				}
209
				if ( ! $user = get_user_by( $field, $user_id ) ) {
210
					WP_CLI::error( __( 'Please specify a valid user.', 'jetpack' ) );
211
				}
212
			} else {
213
				WP_CLI::error( __( 'Please specify a user by either ID, username, or email.', 'jetpack' ) );
214
			}
215
		}
216
217
		switch ( $action ) {
218
			case 'blog':
219
				Jetpack::log( 'disconnect' );
220
				Jetpack::disconnect();
221
				WP_CLI::success(
222
					sprintf(
223
						/* translators: %s is the site URL */
224
						__( 'Jetpack has been successfully disconnected for %s.', 'jetpack' ),
225
						esc_url( get_site_url() )
226
					)
227
				);
228
				break;
229
			case 'user':
230
				if ( Connection_Manager::disconnect_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
	public function reset( $args, $assoc_args ) {
267
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
268 View Code Duplication
		if ( ! in_array( $action, array( 'options', 'modules', 'sync-checksum' ), true ) ) {
269
			/* translators: %s is a command like "prompt" */
270
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
271
		}
272
273
		$is_dry_run = ! empty( $assoc_args['dry-run'] );
274
275 View Code Duplication
		if ( $is_dry_run ) {
276
			WP_CLI::warning(
277
				__( "\nThis is a dry run.\n", 'jetpack' ) .
278
				__( "No actions will be taken.\n", 'jetpack' ) .
279
				__( "The following messages will give you preview of what will happen when you run this command.\n\n", 'jetpack' )
280
			);
281
		} else {
282
			// We only need to confirm "Are you sure?" when we are not doing a dry run.
283
			jetpack_cli_are_you_sure();
284
		}
285
286
		switch ( $action ) {
287
			case 'options':
288
				$options_to_reset = Jetpack_Options::get_options_for_reset();
289
				// Reset the Jetpack options
290
				WP_CLI::line(
291
					sprintf(
292
						/* translators: %s is the site URL */
293
						__( "Resetting Jetpack Options for %s...\n", 'jetpack' ),
294
						esc_url( get_site_url() )
295
					)
296
				);
297
				sleep( 1 ); // Take a breath
298 View Code Duplication
				foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
299
					if ( ! $is_dry_run ) {
300
						Jetpack_Options::delete_option( $option_to_reset );
301
						usleep( 100000 );
302
					}
303
304
					/* translators: This is the result of an action. The option named %s was reset */
305
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
306
				}
307
308
				// Reset the WP options
309
				WP_CLI::line( __( "Resetting the jetpack options stored in wp_options...\n", 'jetpack' ) );
310
				usleep( 500000 ); // Take a breath
311 View Code Duplication
				foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
312
					if ( ! $is_dry_run ) {
313
						delete_option( $option_to_reset );
314
						usleep( 100000 );
315
					}
316
					/* translators: This is the result of an action. The option named %s was reset */
317
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
318
				}
319
320
				// Reset to default modules
321
				WP_CLI::line( __( "Resetting default modules...\n", 'jetpack' ) );
322
				usleep( 500000 ); // Take a breath
323
				$default_modules = Jetpack::get_default_modules();
324
				if ( ! $is_dry_run ) {
325
					Jetpack::update_active_modules( $default_modules );
326
				}
327
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
328
				break;
329 View Code Duplication
			case 'modules':
330
				if ( ! $is_dry_run ) {
331
					$default_modules = Jetpack::get_default_modules();
332
					Jetpack::update_active_modules( $default_modules );
333
				}
334
335
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
336
				break;
337
			case 'prompt':
338
				WP_CLI::error( __( 'Please specify if you would like to reset your options, modules or sync-checksum', 'jetpack' ) );
339
				break;
340
			case 'sync-checksum':
341
				$option = 'jetpack_callables_sync_checksum';
342
343
				if ( is_multisite() ) {
344
					$offset = isset( $assoc_args['offset'] ) ? (int) $assoc_args['offset'] : 0;
345
346
					/*
347
					 * 1000 is a good limit since we don't expect the number of sites to be more than 1000
348
					 * Offset can be used to paginate and try to clean up more sites.
349
					 */
350
					$sites       = get_sites(
351
						array(
352
							'number' => 1000,
353
							'offset' => $offset,
354
						)
355
					);
356
					$count_fixes = 0;
357
					foreach ( $sites as $site ) {
358
						switch_to_blog( $site->blog_id );
359
						$count = self::count_option( $option );
360
						if ( $count > 1 ) {
361
							if ( ! $is_dry_run ) {
362
								delete_option( $option );
363
							}
364
							WP_CLI::line(
365
								sprintf(
366
									/* translators: %1$d is a number, %2$s is the name of an option, %2$s is the site URL. */
367
									__( 'Deleted %1$d %2$s options from %3$s', 'jetpack' ),
368
									$count,
369
									$option,
370
									"{$site->domain}{$site->path}"
371
								)
372
							);
373
							$count_fixes++;
374
							if ( ! $is_dry_run ) {
375
								/*
376
								 * We could be deleting a lot of options rows at the same time.
377
								 * Allow some time for replication to catch up.
378
								 */
379
								sleep( 3 );
380
							}
381
						}
382
383
						restore_current_blog();
384
					}
385
					if ( $count_fixes ) {
386
						WP_CLI::success(
387
							sprintf(
388
								/* translators: %1$s is the name of an option, %2$d is a number of sites. */
389
								__( 'Successfully reset %1$s on %2$d sites.', 'jetpack' ),
390
								$option,
391
								$count_fixes
392
							)
393
						);
394
					} else {
395
						WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
396
					}
397
					return;
398
				}
399
400
				$count = self::count_option( $option );
401
				if ( $count > 1 ) {
402
					if ( ! $is_dry_run ) {
403
						delete_option( $option );
404
					}
405
					WP_CLI::success(
406
						sprintf(
407
							/* translators: %1$d is a number, %2$s is the name of an option. */
408
							__( 'Deleted %1$d %2$s options', 'jetpack' ),
409
							$count,
410
							$option
411
						)
412
					);
413
					return;
414
				}
415
416
				WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
417
				break;
418
419
		}
420
	}
421
422
	/**
423
	 * Return the number of times an option appears
424
	 * Normally an option would only appear 1 since the option key is supposed to be unique
425
	 * but if a site hasn't updated the DB schema then that would not be the case.
426
	 *
427
	 * @param string $option Option name.
428
	 *
429
	 * @return int
430
	 */
431
	private static function count_option( $option ) {
432
		global $wpdb;
433
		return (int) $wpdb->get_var(
434
			$wpdb->prepare(
435
				"SELECT COUNT(*) FROM $wpdb->options WHERE option_name = %s",
436
				$option
437
			)
438
		);
439
440
	}
441
442
	/**
443
	 * Manage Jetpack Modules
444
	 *
445
	 * ## OPTIONS
446
	 *
447
	 * <list|activate|deactivate|toggle>
448
	 * : The action to take.
449
	 * ---
450
	 * default: list
451
	 * options:
452
	 *  - list
453
	 *  - activate
454
	 *  - deactivate
455
	 *  - toggle
456
	 * ---
457
	 *
458
	 * [<module_slug>]
459
	 * : The slug of the module to perform an action on.
460
	 *
461
	 * [--format=<format>]
462
	 * : Allows overriding the output of the command when listing modules.
463
	 * ---
464
	 * default: table
465
	 * options:
466
	 *  - table
467
	 *  - json
468
	 *  - csv
469
	 *  - yaml
470
	 *  - ids
471
	 *  - count
472
	 * ---
473
	 *
474
	 * ## EXAMPLES
475
	 *
476
	 * wp jetpack module list
477
	 * wp jetpack module list --format=json
478
	 * wp jetpack module activate stats
479
	 * wp jetpack module deactivate stats
480
	 * wp jetpack module toggle stats
481
	 * wp jetpack module activate all
482
	 * wp jetpack module deactivate all
483
	 */
484
	public function module( $args, $assoc_args ) {
485
		$action = isset( $args[0] ) ? $args[0] : 'list';
486
487
		if ( isset( $args[1] ) ) {
488
			$module_slug = $args[1];
489
			if ( 'all' !== $module_slug && ! Jetpack::is_module( $module_slug ) ) {
490
				/* translators: %s is a module slug like "stats" */
491
				WP_CLI::error( sprintf( __( '%s is not a valid module.', 'jetpack' ), $module_slug ) );
492
			}
493
			if ( 'toggle' === $action ) {
494
				$action = Jetpack::is_module_active( $module_slug )
495
					? 'deactivate'
496
					: 'activate';
497
			}
498
			if ( 'all' === $args[1] ) {
499
				$action = ( 'deactivate' === $action )
500
					? 'deactivate_all'
501
					: 'activate_all';
502
			}
503
		} elseif ( 'list' !== $action ) {
504
			WP_CLI::line( __( 'Please specify a valid module.', 'jetpack' ) );
505
			$action = 'list';
506
		}
507
508
		switch ( $action ) {
509
			case 'list':
510
				$modules_list = array();
511
				$modules      = Jetpack::get_available_modules();
512
				sort( $modules );
513
				foreach ( (array) $modules as $module_slug ) {
514
					if ( 'vaultpress' === $module_slug ) {
515
						continue;
516
					}
517
					$modules_list[] = array(
518
						'slug'   => $module_slug,
519
						'status' => Jetpack::is_module_active( $module_slug )
520
							? __( 'Active', 'jetpack' )
521
							: __( 'Inactive', 'jetpack' ),
522
					);
523
				}
524
				WP_CLI\Utils\format_items( $assoc_args['format'], $modules_list, array( 'slug', 'status' ) );
525
				break;
526
			case 'activate':
527
				$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...
528
				Jetpack::log( 'activate', $module_slug );
529
				if ( Jetpack::activate_module( $module_slug, false, false ) ) {
530
					/* translators: %s is the name of a Jetpack module */
531
					WP_CLI::success( sprintf( __( '%s has been activated.', 'jetpack' ), $module['name'] ) );
532
				} else {
533
					/* translators: %s is the name of a Jetpack module */
534
					WP_CLI::error( sprintf( __( '%s could not be activated.', 'jetpack' ), $module['name'] ) );
535
				}
536
				break;
537 View Code Duplication
			case 'activate_all':
538
				$modules = Jetpack::get_available_modules();
539
				Jetpack::update_active_modules( $modules );
540
				WP_CLI::success( __( 'All modules activated!', 'jetpack' ) );
541
				break;
542
			case 'deactivate':
543
				$module = Jetpack::get_module( $module_slug );
544
				Jetpack::log( 'deactivate', $module_slug );
545
				Jetpack::deactivate_module( $module_slug );
546
				/* translators: %s is the name of a Jetpack module */
547
				WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) );
548
				break;
549
			case 'deactivate_all':
550
				Jetpack::delete_active_modules();
551
				WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) );
552
				break;
553
			case 'toggle':
554
				// Will never happen, should have been handled above and changed to activate or deactivate.
555
				break;
556
		}
557
	}
558
559
	/**
560
	 * Manage Protect Settings
561
	 *
562
	 * ## OPTIONS
563
	 *
564
	 * whitelist: Whitelist an IP address.  You can also read or clear the whitelist.
565
	 *
566
	 *
567
	 * ## EXAMPLES
568
	 *
569
	 * wp jetpack protect whitelist <ip address>
570
	 * wp jetpack protect whitelist list
571
	 * wp jetpack protect whitelist clear
572
	 *
573
	 * @synopsis <whitelist> [<ip|ip_low-ip_high|list|clear>]
574
	 */
575
	public function protect( $args, $assoc_args ) {
576
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
577
		if ( ! in_array( $action, array( 'whitelist' ) ) ) {
578
			/* translators: %s is a command like "prompt" */
579
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
580
		}
581
		// Check if module is active
582
		if ( ! Jetpack::is_module_active( __FUNCTION__ ) ) {
583
			/* translators: %s is a module name */
584
			WP_CLI::error( sprintf( _x( '%1$s is not active. You can activate it with "wp jetpack module activate %2$s"', '"wp jetpack module activate" is a command - do not translate', 'jetpack' ), __FUNCTION__, __FUNCTION__ ) );
585
		}
586
		if ( in_array( $action, array( 'whitelist' ) ) ) {
587
			if ( isset( $args[1] ) ) {
588
				$action = 'whitelist';
589
			} else {
590
				$action = 'prompt';
591
			}
592
		}
593
		switch ( $action ) {
594
			case 'whitelist':
595
				$whitelist         = array();
596
				$new_ip            = $args[1];
597
				$current_whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
598
599
				// Build array of IPs that are already whitelisted.
600
				// Re-build manually instead of using jetpack_protect_format_whitelist() so we can easily get
601
				// low & high range params for jetpack_protect_ip_address_is_in_range();
602
				foreach ( $current_whitelist as $whitelisted ) {
603
604
					// IP ranges
605
					if ( $whitelisted->range ) {
606
607
						// Is it already whitelisted?
608
						if ( jetpack_protect_ip_address_is_in_range( $new_ip, $whitelisted->range_low, $whitelisted->range_high ) ) {
609
							/* translators: %s is an IP address */
610
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
611
							break;
612
						}
613
						$whitelist[] = $whitelisted->range_low . ' - ' . $whitelisted->range_high;
614
615
					} else { // Individual IPs
616
617
						// Check if the IP is already whitelisted (single IP only)
618
						if ( $new_ip == $whitelisted->ip_address ) {
619
							/* translators: %s is an IP address */
620
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
621
							break;
622
						}
623
						$whitelist[] = $whitelisted->ip_address;
624
625
					}
626
				}
627
628
				/*
629
				 * List the whitelist
630
				 * Done here because it's easier to read the $whitelist array after it's been rebuilt
631
				 */
632
				if ( isset( $args[1] ) && 'list' == $args[1] ) {
633
					if ( ! empty( $whitelist ) ) {
634
						WP_CLI::success( __( 'Here are your whitelisted IPs:', 'jetpack' ) );
635
						foreach ( $whitelist as $ip ) {
636
							WP_CLI::line( "\t" . str_pad( $ip, 24 ) );
637
						}
638
					} else {
639
						WP_CLI::line( __( 'Whitelist is empty.', 'jetpack' ) );
640
					}
641
					break;
642
				}
643
644
				/*
645
				 * Clear the whitelist
646
				 */
647
				if ( isset( $args[1] ) && 'clear' == $args[1] ) {
648
					if ( ! empty( $whitelist ) ) {
649
						$whitelist = array();
650
						jetpack_protect_save_whitelist( $whitelist );
651
						WP_CLI::success( __( 'Cleared all whitelisted IPs', 'jetpack' ) );
652
					} else {
653
						WP_CLI::line( __( 'Whitelist is empty.', 'jetpack' ) );
654
					}
655
					break;
656
				}
657
658
				// Append new IP to whitelist array
659
				array_push( $whitelist, $new_ip );
660
661
				// Save whitelist if there are no errors
662
				$result = jetpack_protect_save_whitelist( $whitelist );
663
				if ( is_wp_error( $result ) ) {
664
					WP_CLI::error( $result );
665
				}
666
667
				/* translators: %s is an IP address */
668
				WP_CLI::success( sprintf( __( '%s has been whitelisted.', 'jetpack' ), $new_ip ) );
669
				break;
670
			case 'prompt':
671
				WP_CLI::error(
672
					__( 'No command found.', 'jetpack' ) . "\n" .
673
					__( 'Please enter the IP address you want to whitelist.', 'jetpack' ) . "\n" .
674
					_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" .
675
					_x( "You can also 'list' or 'clear' the whitelist.", "'list' and 'clear' are commands and should not be translated", 'jetpack' ) . "\n"
676
				);
677
				break;
678
		}
679
	}
680
681
	/**
682
	 * Manage Jetpack Options
683
	 *
684
	 * ## OPTIONS
685
	 *
686
	 * list   : List all jetpack options and their values
687
	 * delete : Delete an option
688
	 *          - can only delete options that are white listed.
689
	 * update : update an option
690
	 *          - can only update option strings
691
	 * get    : get the value of an option
692
	 *
693
	 * ## EXAMPLES
694
	 *
695
	 * wp jetpack options list
696
	 * wp jetpack options get    <option_name>
697
	 * wp jetpack options delete <option_name>
698
	 * wp jetpack options update <option_name> [<option_value>]
699
	 *
700
	 * @synopsis <list|get|delete|update> [<option_name>] [<option_value>]
701
	 */
702
	public function options( $args, $assoc_args ) {
703
		$action         = isset( $args[0] ) ? $args[0] : 'list';
704
		$safe_to_modify = Jetpack_Options::get_options_for_reset();
705
706
		// Is the option flagged as unsafe?
707
		$flagged = ! in_array( $args[1], $safe_to_modify );
708
709 View Code Duplication
		if ( ! in_array( $action, array( 'list', 'get', 'delete', 'update' ) ) ) {
710
			/* translators: %s is a command like "prompt" */
711
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
712
		}
713
714
		if ( isset( $args[0] ) ) {
715
			if ( 'get' == $args[0] && isset( $args[1] ) ) {
716
				$action = 'get';
717
			} elseif ( 'delete' == $args[0] && isset( $args[1] ) ) {
718
				$action = 'delete';
719
			} elseif ( 'update' == $args[0] && isset( $args[1] ) ) {
720
				$action = 'update';
721
			} else {
722
				$action = 'list';
723
			}
724
		}
725
726
		// Bail if the option isn't found
727
		$option = isset( $args[1] ) ? Jetpack_Options::get_option( $args[1] ) : false;
728 View Code Duplication
		if ( isset( $args[1] ) && ! $option && 'update' !== $args[0] ) {
729
			WP_CLI::error( __( 'Option not found or is empty.  Use "list" to list option names', 'jetpack' ) );
730
		}
731
732
		// Let's print_r the option if it's an array
733
		// Used in the 'get' and 'list' actions
734
		$option = is_array( $option ) ? print_r( $option ) : $option;
735
736
		switch ( $action ) {
737
			case 'get':
738
				WP_CLI::success( "\t" . $option );
739
				break;
740
			case 'delete':
741
				jetpack_cli_are_you_sure( $flagged );
742
743
				Jetpack_Options::delete_option( $args[1] );
744
				/* translators: %s is the option name */
745
				WP_CLI::success( sprintf( __( 'Deleted option: %s', 'jetpack' ), $args[1] ) );
746
				break;
747
			case 'update':
748
				jetpack_cli_are_you_sure( $flagged );
749
750
				// Updating arrays would get pretty tricky...
751
				$value = Jetpack_Options::get_option( $args[1] );
752
				if ( $value && is_array( $value ) ) {
753
					WP_CLI::error( __( 'Sorry, no updating arrays at this time', 'jetpack' ) );
754
				}
755
756
				Jetpack_Options::update_option( $args[1], $args[2] );
757
				/* translators: %1$s is the previous value, %2$s is the new value */
758
				WP_CLI::success( sprintf( _x( 'Updated option: %1$s to "%2$s"', 'Updating an option from "this" to "that".', 'jetpack' ), $args[1], $args[2] ) );
759
				break;
760
			case 'list':
761
				$options_compact     = Jetpack_Options::get_option_names();
762
				$options_non_compact = Jetpack_Options::get_option_names( 'non_compact' );
763
				$options_private     = Jetpack_Options::get_option_names( 'private' );
764
				$options             = array_merge( $options_compact, $options_non_compact, $options_private );
765
766
				// Table headers
767
				WP_CLI::line( "\t" . str_pad( __( 'Option', 'jetpack' ), 30 ) . __( 'Value', 'jetpack' ) );
768
769
				// List out the options and their values
770
				// Tell them if the value is empty or not
771
				// Tell them if it's an array
772
				foreach ( $options as $option ) {
773
					$value = Jetpack_Options::get_option( $option );
774
					if ( ! $value ) {
775
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Empty' );
776
						continue;
777
					}
778
779
					if ( ! is_array( $value ) ) {
780
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . $value );
781
					} elseif ( is_array( $value ) ) {
782
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Array - Use "get <option>" to read option array.' );
783
					}
784
				}
785
				$option_text = '{' . _x( 'option', 'a variable command that a user can write, provided in the printed instructions', 'jetpack' ) . '}';
786
				$value_text  = '{' . _x( 'value', 'the value that they want to update the option to', 'jetpack' ) . '}';
787
788
				WP_CLI::success(
789
					_x( "Above are your options. You may 'get', 'delete', and 'update' them.", "'get', 'delete', and 'update' are commands - do not translate.", 'jetpack' ) . "\n" .
790
					str_pad( 'wp jetpack options get', 26 ) . $option_text . "\n" .
791
					str_pad( 'wp jetpack options delete', 26 ) . $option_text . "\n" .
792
					str_pad( 'wp jetpack options update', 26 ) . "$option_text $value_text" . "\n" .
793
					_x( "Type 'wp jetpack options' for more info.", "'wp jetpack options' is a command - do not translate.", 'jetpack' ) . "\n"
794
				);
795
				break;
796
		}
797
	}
798
799
	/**
800
	 * Get the status of or start a new Jetpack sync.
801
	 *
802
	 * ## OPTIONS
803
	 *
804
	 * status   : Print the current sync status
805
	 * settings : Prints the current sync settings
806
	 * start    : Start a full sync from this site to WordPress.com
807
	 * enable   : Enables sync on the site
808
	 * disable  : Disable sync on a site
809
	 * reset    : Disables sync and Resets the sync queues on a site
810
	 *
811
	 * ## EXAMPLES
812
	 *
813
	 * wp jetpack sync status
814
	 * wp jetpack sync settings
815
	 * wp jetpack sync start --modules=functions --sync_wait_time=5
816
	 * wp jetpack sync enable
817
	 * wp jetpack sync disable
818
	 * wp jetpack sync reset
819
	 * wp jetpack sync reset --queue=full or regular
820
	 *
821
	 * @synopsis <status|start> [--<field>=<value>]
822
	 */
823
	public function sync( $args, $assoc_args ) {
824
825
		$action = isset( $args[0] ) ? $args[0] : 'status';
826
827
		switch ( $action ) {
828
			case 'status':
829
				$status     = Actions::get_sync_status();
830
				$collection = array();
831
				foreach ( $status as $key => $item ) {
832
					$collection[] = array(
833
						'option' => $key,
834
						'value'  => is_scalar( $item ) ? $item : json_encode( $item ),
835
					);
836
				}
837
				WP_CLI::log( __( 'Sync Status:', 'jetpack' ) );
838
				WP_CLI\Utils\format_items( 'table', $collection, array( 'option', 'value' ) );
839
				break;
840
			case 'settings':
841
				WP_CLI::log( __( 'Sync Settings:', 'jetpack' ) );
842
				foreach ( Settings::get_settings() as $setting => $item ) {
843
					$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...
844
						'setting' => $setting,
845
						'value'   => is_scalar( $item ) ? $item : json_encode( $item ),
846
					);
847
				}
848
				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...
849
850
			case 'disable':
851
				// Don't set it via the Settings since that also resets the queues.
852
				update_option( 'jetpack_sync_settings_disable', 1 );
853
				/* translators: %s is the site URL */
854
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s', 'jetpack' ), get_site_url() ) );
855
				break;
856
			case 'enable':
857
				Settings::update_settings( array( 'disable' => 0 ) );
858
				/* translators: %s is the site URL */
859
				WP_CLI::log( sprintf( __( 'Sync Enabled on %s', 'jetpack' ), get_site_url() ) );
860
				break;
861
			case 'reset':
862
				// Don't set it via the Settings since that also resets the queues.
863
				update_option( 'jetpack_sync_settings_disable', 1 );
864
865
				/* translators: %s is the site URL */
866
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s. Use `wp jetpack sync enable` to enable syncing again.', 'jetpack' ), get_site_url() ) );
867
				$listener = Listener::get_instance();
868
				if ( empty( $assoc_args['queue'] ) ) {
869
					$listener->get_sync_queue()->reset();
870
					$listener->get_full_sync_queue()->reset();
871
					/* translators: %s is the site URL */
872
					WP_CLI::log( sprintf( __( 'Reset Full Sync and Regular Queues Queue on %s', 'jetpack' ), get_site_url() ) );
873
					break;
874
				}
875
876
				if ( ! empty( $assoc_args['queue'] ) ) {
877
					switch ( $assoc_args['queue'] ) {
878 View Code Duplication
						case 'regular':
879
							$listener->get_sync_queue()->reset();
880
							/* translators: %s is the site URL */
881
							WP_CLI::log( sprintf( __( 'Reset Regular Sync Queue on %s', 'jetpack' ), get_site_url() ) );
882
							break;
883 View Code Duplication
						case 'full':
884
							$listener->get_full_sync_queue()->reset();
885
							/* translators: %s is the site URL */
886
							WP_CLI::log( sprintf( __( 'Reset Full Sync Queue on %s', 'jetpack' ), get_site_url() ) );
887
							break;
888
						default:
889
							WP_CLI::error( __( 'Please specify what type of queue do you want to reset: `full` or `regular`.', 'jetpack' ) );
890
							break;
891
					}
892
				}
893
894
				break;
895
			case 'start':
896
				if ( ! Actions::sync_allowed() ) {
897
					if ( ! Settings::get_setting( 'disable' ) ) {
898
						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' ) );
899
						return;
900
					}
901
					if ( doing_action( 'jetpack_user_authorized' ) || Jetpack::is_active() ) {
902
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. Jetpack is not connected.', 'jetpack' ) );
903
						return;
904
					}
905
					if ( Jetpack::is_development_mode() ) {
906
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in development mode.', 'jetpack' ) );
907
						return;
908
					}
909
					if ( Jetpack::is_staging_site() ) {
910
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in staging mode.', 'jetpack' ) );
911
						return;
912
					}
913
				}
914
				// Get the original settings so that we can restore them later
915
				$original_settings = Settings::get_settings();
916
917
				// Initialize sync settigns so we can sync as quickly as possible
918
				$sync_settings = wp_parse_args(
919
					array_intersect_key( $assoc_args, Settings::$valid_settings ),
920
					array(
921
						'sync_wait_time'           => 0,
922
						'enqueue_wait_time'        => 0,
923
						'queue_max_writes_sec'     => 10000,
924
						'max_queue_size_full_sync' => 100000,
925
					)
926
				);
927
				Settings::update_settings( $sync_settings );
928
929
				// Convert comma-delimited string of modules to an array
930 View Code Duplication
				if ( ! empty( $assoc_args['modules'] ) ) {
931
					$modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
932
933
					// Convert the array so that the keys are the module name and the value is true to indicate
934
					// that we want to sync the module
935
					$modules = array_map( '__return_true', array_flip( $modules ) );
936
				}
937
938 View Code Duplication
				foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
939
					if (
940
						'users' === $module_name &&
941
						isset( $assoc_args[ $module_name ] ) &&
942
						'initial' === $assoc_args[ $module_name ]
943
					) {
944
						$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...
945
					} elseif ( isset( $assoc_args[ $module_name ] ) ) {
946
						$ids = explode( ',', $assoc_args[ $module_name ] );
947
						if ( count( $ids ) > 0 ) {
948
							$modules[ $module_name ] = $ids;
949
						}
950
					}
951
				}
952
953
				if ( empty( $modules ) ) {
954
					$modules = null;
955
				}
956
957
				// Kick off a full sync
958
				if ( Actions::do_full_sync( $modules ) ) {
959
					if ( $modules ) {
960
						/* translators: %s is a comma separated list of Jetpack modules */
961
						WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
962
					} else {
963
						WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
964
					}
965 View Code Duplication
				} else {
966
967
					// Reset sync settings to original.
968
					Settings::update_settings( $original_settings );
969
970
					if ( $modules ) {
971
						/* translators: %s is a comma separated list of Jetpack modules */
972
						WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
973
					} else {
974
						WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
975
					}
976
				}
977
978
				// Keep sending to WPCOM until there's nothing to send
979
				$i = 1;
980
				do {
981
					$result = Actions::$sender->do_full_sync();
982
					if ( is_wp_error( $result ) ) {
983
						$queue_empty_error = ( 'empty_queue_full_sync' == $result->get_error_code() );
984
						if ( ! $queue_empty_error || ( $queue_empty_error && ( 1 == $i ) ) ) {
985
							/* translators: %s is an error code  */
986
							WP_CLI::error( sprintf( __( 'Sync errored with code: %s', 'jetpack' ), $result->get_error_code() ) );
987
						}
988
					} else {
989
						if ( 1 == $i ) {
990
							WP_CLI::log( __( 'Sent data to WordPress.com', 'jetpack' ) );
991
						} else {
992
							WP_CLI::log( __( 'Sent more data to WordPress.com', 'jetpack' ) );
993
						}
994
					}
995
					$i++;
996
				} while ( $result && ! is_wp_error( $result ) );
997
998
				// Reset sync settings to original.
999
				Settings::update_settings( $original_settings );
1000
1001
				WP_CLI::success( __( 'Finished syncing to WordPress.com', 'jetpack' ) );
1002
				break;
1003
		}
1004
	}
1005
1006
	/**
1007
	 * List the contents of a specific Jetpack sync queue.
1008
	 *
1009
	 * ## OPTIONS
1010
	 *
1011
	 * peek : List the 100 front-most items on the queue.
1012
	 *
1013
	 * ## EXAMPLES
1014
	 *
1015
	 * wp jetpack sync_queue full_sync peek
1016
	 *
1017
	 * @synopsis <incremental|full_sync> <peek>
1018
	 */
1019
	public function sync_queue( $args, $assoc_args ) {
1020
		if ( ! Actions::sync_allowed() ) {
1021
			WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
1022
		}
1023
1024
		$queue_name = isset( $args[0] ) ? $args[0] : 'sync';
1025
		$action     = isset( $args[1] ) ? $args[1] : 'peek';
1026
1027
		// We map the queue name that way we can support more friendly queue names in the commands, but still use
1028
		// the queue name that the code expects.
1029
		$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...
1030
			'incremental' => 'sync',
1031
			'full'        => 'full_sync',
1032
		);
1033
		$mapped_queue_name = isset( $queue_name_map[ $queue_name ] ) ? $queue_name_map[ $queue_name ] : $queue_name;
1034
1035
		switch ( $action ) {
1036
			case 'peek':
1037
				$queue = new Queue( $mapped_queue_name );
1038
				$items = $queue->peek( 100 );
1039
1040
				if ( empty( $items ) ) {
1041
					/* translators: %s is the name of the queue, either 'incremental' or 'full' */
1042
					WP_CLI::log( sprintf( __( 'Nothing is in the queue: %s', 'jetpack' ), $queue_name ) );
1043
				} else {
1044
					$collection = array();
1045
					foreach ( $items as $item ) {
1046
						$collection[] = array(
1047
							'action'          => $item[0],
1048
							'args'            => json_encode( $item[1] ),
1049
							'current_user_id' => $item[2],
1050
							'microtime'       => $item[3],
1051
							'importing'       => (string) $item[4],
1052
						);
1053
					}
1054
					WP_CLI\Utils\format_items(
1055
						'table',
1056
						$collection,
1057
						array(
1058
							'action',
1059
							'args',
1060
							'current_user_id',
1061
							'microtime',
1062
							'importing',
1063
						)
1064
					);
1065
				}
1066
				break;
1067
		}
1068
	}
1069
1070
	/**
1071
	 * Cancel's the current Jetpack plan granted by this partner, if applicable
1072
	 *
1073
	 * Returns success or error JSON
1074
	 *
1075
	 * <token_json>
1076
	 * : JSON blob of WPCOM API token
1077
	 *  [--partner_tracking_id=<partner_tracking_id>]
1078
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1079
	 *
1080
	 *  * @synopsis <token_json> [--partner_tracking_id=<partner_tracking_id>]
1081
	 */
1082
	public function partner_cancel( $args, $named_args ) {
1083
		list( $token_json ) = $args;
1084
1085 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1086
			/* translators: %s is the invalid JSON string */
1087
			$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...
1088
		}
1089
1090
		if ( isset( $token->error ) ) {
1091
			$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...
1092
		}
1093
1094
		if ( ! isset( $token->access_token ) ) {
1095
			$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...
1096
		}
1097
1098
		if ( Jetpack::validate_sync_error_idc_option() ) {
1099
			$this->partner_provision_error(
1100
				new WP_Error(
1101
					'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...
1102
					esc_html__( 'Can not cancel a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
1103
				)
1104
			);
1105
		}
1106
1107
		$site_identifier = Jetpack_Options::get_option( 'id' );
1108
1109
		if ( ! $site_identifier ) {
1110
			$site_identifier = Jetpack::build_raw_urls( get_home_url() );
1111
		}
1112
1113
		$request = array(
1114
			'headers' => array(
1115
				'Authorization' => 'Bearer ' . $token->access_token,
1116
				'Host'          => 'public-api.wordpress.com',
1117
			),
1118
			'timeout' => 60,
1119
			'method'  => 'POST',
1120
		);
1121
1122
		$url = sprintf( 'https://%s/rest/v1.3/jpphp/%s/partner-cancel', $this->get_api_host(), $site_identifier );
1123 View Code Duplication
		if ( ! empty( $named_args ) && ! empty( $named_args['partner_tracking_id'] ) ) {
1124
			$url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
1125
		}
1126
1127
		$result = Client::_wp_remote_request( $url, $request );
1128
1129
		Jetpack_Options::delete_option( 'onboarding' );
1130
1131
		if ( is_wp_error( $result ) ) {
1132
			$this->partner_provision_error( $result );
1133
		}
1134
1135
		WP_CLI::log( wp_remote_retrieve_body( $result ) );
1136
	}
1137
1138
	/**
1139
	 * Provision a site using a Jetpack Partner license
1140
	 *
1141
	 * Returns JSON blob
1142
	 *
1143
	 * ## OPTIONS
1144
	 *
1145
	 * <token_json>
1146
	 * : JSON blob of WPCOM API token
1147
	 * [--plan=<plan_name>]
1148
	 * : Slug of the requested plan, e.g. premium
1149
	 * [--wpcom_user_id=<user_id>]
1150
	 * : WordPress.com ID of user to connect as (must be whitelisted against partner key)
1151
	 * [--wpcom_user_email=<wpcom_user_email>]
1152
	 * : Override the email we send to WordPress.com for registration
1153
	 * [--onboarding=<onboarding>]
1154
	 * : Guide the user through an onboarding wizard
1155
	 * [--force_register=<register>]
1156
	 * : Whether to force a site to register
1157
	 * [--force_connect=<force_connect>]
1158
	 * : Force JPS to not reuse existing credentials
1159
	 * [--home_url=<home_url>]
1160
	 * : Overrides the home option via the home_url filter, or the WP_HOME constant
1161
	 * [--site_url=<site_url>]
1162
	 * : Overrides the siteurl option via the site_url filter, or the WP_SITEURL constant
1163
	 * [--partner_tracking_id=<partner_tracking_id>]
1164
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1165
	 *
1166
	 * ## EXAMPLES
1167
	 *
1168
	 *     $ wp jetpack partner_provision '{ some: "json" }' premium 1
1169
	 *     { success: true }
1170
	 *
1171
	 * @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>]
1172
	 */
1173
	public function partner_provision( $args, $named_args ) {
1174
		list( $token_json ) = $args;
1175
1176 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1177
			/* translators: %s is the invalid JSON string */
1178
			$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...
1179
		}
1180
1181
		if ( isset( $token->error ) ) {
1182
			$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...
1183
				? $token->message
1184
				: '';
1185
			$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...
1186
		}
1187
1188
		if ( ! isset( $token->access_token ) ) {
1189
			$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...
1190
		}
1191
1192
		require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
1193
1194
		$body_json = Jetpack_Provision::partner_provision( $token->access_token, $named_args );
1195
1196
		if ( is_wp_error( $body_json ) ) {
1197
			error_log(
1198
				json_encode(
1199
					array(
1200
						'success'       => false,
1201
						'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...
1202
						'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...
1203
					)
1204
				)
1205
			);
1206
			exit( 1 );
1207
		}
1208
1209
		WP_CLI::log( json_encode( $body_json ) );
1210
	}
1211
1212
	/**
1213
	 * Manages your Jetpack sitemap
1214
	 *
1215
	 * ## OPTIONS
1216
	 *
1217
	 * rebuild : Rebuild all sitemaps
1218
	 * --purge : if set, will remove all existing sitemap data before rebuilding
1219
	 *
1220
	 * ## EXAMPLES
1221
	 *
1222
	 * wp jetpack sitemap rebuild
1223
	 *
1224
	 * @subcommand sitemap
1225
	 * @synopsis <rebuild> [--purge]
1226
	 */
1227
	public function sitemap( $args, $assoc_args ) {
1228
		if ( ! Jetpack::is_active() ) {
1229
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1230
		}
1231
		if ( ! Jetpack::is_module_active( 'sitemaps' ) ) {
1232
			WP_CLI::error( __( 'Jetpack Sitemaps module is not currently active. Activate it first if you want to work with sitemaps.', 'jetpack' ) );
1233
		}
1234
		if ( ! class_exists( 'Jetpack_Sitemap_Builder' ) ) {
1235
			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' ) );
1236
		}
1237
1238
		if ( isset( $assoc_args['purge'] ) && $assoc_args['purge'] ) {
1239
			$librarian = new Jetpack_Sitemap_Librarian();
1240
			$librarian->delete_all_stored_sitemap_data();
1241
		}
1242
1243
		$sitemap_builder = new Jetpack_Sitemap_Builder();
1244
		$sitemap_builder->update_sitemap();
1245
	}
1246
1247
	/**
1248
	 * Allows authorizing a user via the command line and will activate
1249
	 *
1250
	 * ## EXAMPLES
1251
	 *
1252
	 * wp jetpack authorize_user --token=123456789abcdef
1253
	 *
1254
	 * @synopsis --token=<value>
1255
	 */
1256
	public function authorize_user( $args, $named_args ) {
1257
		if ( ! is_user_logged_in() ) {
1258
			WP_CLI::error( __( 'Please select a user to authorize via the --user global argument.', 'jetpack' ) );
1259
		}
1260
1261
		if ( empty( $named_args['token'] ) ) {
1262
			WP_CLI::error( __( 'A non-empty token argument must be passed.', 'jetpack' ) );
1263
		}
1264
1265
		$is_master_user  = ! Jetpack::is_active();
1266
		$current_user_id = get_current_user_id();
1267
1268
		Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_master_user );
1269
1270
		WP_CLI::log( wp_json_encode( $named_args ) );
1271
1272
		if ( $is_master_user ) {
1273
			/**
1274
			 * Auto-enable SSO module for new Jetpack Start connections
1275
			*
1276
			* @since 5.0.0
1277
			*
1278
			* @param bool $enable_sso Whether to enable the SSO module. Default to true.
1279
			*/
1280
			$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1281
			Jetpack::handle_post_authorization_actions( $enable_sso, false );
1282
1283
			/* translators: %d is a user ID */
1284
			WP_CLI::success( sprintf( __( 'Authorized %d and activated default modules.', 'jetpack' ), $current_user_id ) );
1285
		} else {
1286
			/* translators: %d is a user ID */
1287
			WP_CLI::success( sprintf( __( 'Authorized %d.', 'jetpack' ), $current_user_id ) );
1288
		}
1289
	}
1290
1291
	/**
1292
	 * Allows calling a WordPress.com API endpoint using the current blog's token.
1293
	 *
1294
	 * ## OPTIONS
1295
	 * --resource=<resource>
1296
	 * : The resource to call with the current blog's token, where `%d` represents the current blog's ID.
1297
	 *
1298
	 * [--api_version=<api_version>]
1299
	 * : The API version to query against.
1300
	 *
1301
	 * [--base_api_path=<base_api_path>]
1302
	 * : The base API path to query.
1303
	 * ---
1304
	 * default: rest
1305
	 * ---
1306
	 *
1307
	 * [--body=<body>]
1308
	 * : A JSON encoded string representing arguments to send in the body.
1309
	 *
1310
	 * [--field=<value>]
1311
	 * : Any number of arguments that should be passed to the resource.
1312
	 *
1313
	 * [--pretty]
1314
	 * : Will pretty print the results of a successful API call.
1315
	 *
1316
	 * [--strip-success]
1317
	 * : Will remove the green success label from successful API calls.
1318
	 *
1319
	 * ## EXAMPLES
1320
	 *
1321
	 * wp jetpack call_api --resource='/sites/%d'
1322
	 */
1323
	public function call_api( $args, $named_args ) {
1324
		if ( ! Jetpack::is_active() ) {
1325
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1326
		}
1327
1328
		$consumed_args = array(
1329
			'resource',
1330
			'api_version',
1331
			'base_api_path',
1332
			'body',
1333
			'pretty',
1334
		);
1335
1336
		// Get args that should be passed to resource.
1337
		$other_args = array_diff_key( $named_args, array_flip( $consumed_args ) );
1338
1339
		$decoded_body = ! empty( $named_args['body'] )
1340
			? json_decode( $named_args['body'], true )
1341
			: false;
1342
1343
		$resource_url = ( false === strpos( $named_args['resource'], '%d' ) )
1344
			? $named_args['resource']
1345
			: sprintf( $named_args['resource'], Jetpack_Options::get_option( 'id' ) );
1346
1347
		$response = Client::wpcom_json_api_request_as_blog(
1348
			$resource_url,
1349
			empty( $named_args['api_version'] ) ? Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
1350
			$other_args,
1351
			empty( $decoded_body ) ? null : $decoded_body,
1352
			empty( $named_args['base_api_path'] ) ? 'rest' : $named_args['base_api_path']
1353
		);
1354
1355 View Code Duplication
		if ( is_wp_error( $response ) ) {
1356
			WP_CLI::error(
1357
				sprintf(
1358
					/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
1359
					__( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
1360
					$resource_url,
1361
					$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...
1362
					$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...
1363
				)
1364
			);
1365
		}
1366
1367
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1368
			WP_CLI::error(
1369
				sprintf(
1370
					/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
1371
					__( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
1372
					$resource_url,
1373
					wp_remote_retrieve_response_code( $response )
1374
				)
1375
			);
1376
		}
1377
1378
		$output = wp_remote_retrieve_body( $response );
1379
		if ( isset( $named_args['pretty'] ) ) {
1380
			$decoded_output = json_decode( $output );
1381
			if ( $decoded_output ) {
1382
				$output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
1383
			}
1384
		}
1385
1386
		if ( isset( $named_args['strip-success'] ) ) {
1387
			WP_CLI::log( $output );
1388
			WP_CLI::halt( 0 );
1389
		}
1390
1391
		WP_CLI::success( $output );
1392
	}
1393
1394
	/**
1395
	 * Allows uploading SSH Credentials to the current site for backups, restores, and security scanning.
1396
	 *
1397
	 * ## OPTIONS
1398
	 *
1399
	 * [--host=<host>]
1400
	 * : The SSH server's address.
1401
	 *
1402
	 * [--ssh-user=<user>]
1403
	 * : The username to use to log in to the SSH server.
1404
	 *
1405
	 * [--pass=<pass>]
1406
	 * : The password used to log in, if using a password. (optional)
1407
	 *
1408
	 * [--kpri=<kpri>]
1409
	 * : The private key used to log in, if using a private key. (optional)
1410
	 *
1411
	 * [--pretty]
1412
	 * : Will pretty print the results of a successful API call. (optional)
1413
	 *
1414
	 * [--strip-success]
1415
	 * : Will remove the green success label from successful API calls. (optional)
1416
	 *
1417
	 * ## EXAMPLES
1418
	 *
1419
	 * wp jetpack upload_ssh_creds --host=example.com --ssh-user=example --pass=password
1420
	 * wp jetpack updload_ssh_creds --host=example.com --ssh-user=example --kpri=key
1421
	 */
1422
	public function upload_ssh_creds( $args, $named_args ) {
1423
		if ( ! Jetpack::is_active() ) {
1424
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1425
		}
1426
1427
		$required_args = array(
1428
			'host',
1429
			'ssh-user',
1430
		);
1431
1432
		foreach ( $required_args as $arg ) {
1433
			if ( empty( $named_args[ $arg ] ) ) {
1434
				WP_CLI::error(
1435
					sprintf(
1436
						/* translators: %s is a slug, such as 'host'. */
1437
						__( '`%s` cannot be empty.', 'jetpack' ),
1438
						$arg
1439
					)
1440
				);
1441
			}
1442
		}
1443
1444
		if ( empty( $named_args['pass'] ) && empty( $named_args['kpri'] ) ) {
1445
			WP_CLI::error( __( 'Both `pass` and `kpri` fields cannot be blank.', 'jetpack' ) );
1446
		}
1447
1448
		$values = array(
1449
			'credentials' => array(
1450
				'site_url' => get_site_url(),
1451
				'abspath'  => ABSPATH,
1452
				'protocol' => 'ssh',
1453
				'port'     => 22,
1454
				'role'     => 'main',
1455
				'host'     => $named_args['host'],
1456
				'user'     => $named_args['ssh-user'],
1457
				'pass'     => empty( $named_args['pass'] ) ? '' : $named_args['pass'],
1458
				'kpri'     => empty( $named_args['kpri'] ) ? '' : $named_args['kpri'],
1459
			),
1460
		);
1461
1462
		$named_args = wp_parse_args(
1463
			array(
1464
				'resource'    => '/activity-log/%d/update-credentials',
1465
				'method'      => 'POST',
1466
				'api_version' => '1.1',
1467
				'body'        => wp_json_encode( $values ),
1468
				'timeout'     => 30,
1469
			),
1470
			$named_args
1471
		);
1472
1473
		self::call_api( $args, $named_args );
1474
	}
1475
1476
	/**
1477
	 * API wrapper for getting stats from the WordPress.com API for the current site.
1478
	 *
1479
	 * ## OPTIONS
1480
	 *
1481
	 * [--quantity=<quantity>]
1482
	 * : The number of units to include.
1483
	 * ---
1484
	 * default: 30
1485
	 * ---
1486
	 *
1487
	 * [--period=<period>]
1488
	 * : The unit of time to query stats for.
1489
	 * ---
1490
	 * default: day
1491
	 * options:
1492
	 *  - day
1493
	 *  - week
1494
	 *  - month
1495
	 *  - year
1496
	 * ---
1497
	 *
1498
	 * [--date=<date>]
1499
	 * : The latest date to return stats for. Ex. - 2018-01-01.
1500
	 *
1501
	 * [--pretty]
1502
	 * : Will pretty print the results of a successful API call.
1503
	 *
1504
	 * [--strip-success]
1505
	 * : Will remove the green success label from successful API calls.
1506
	 *
1507
	 * ## EXAMPLES
1508
	 *
1509
	 * wp jetpack get_stats
1510
	 */
1511
	public function get_stats( $args, $named_args ) {
1512
		$selected_args = array_intersect_key(
1513
			$named_args,
1514
			array_flip(
1515
				array(
1516
					'quantity',
1517
					'date',
1518
				)
1519
			)
1520
		);
1521
1522
		// The API expects unit, but period seems to be more correct.
1523
		$selected_args['unit'] = $named_args['period'];
1524
1525
		$command = sprintf(
1526
			'jetpack call_api --resource=/sites/%d/stats/%s',
1527
			Jetpack_Options::get_option( 'id' ),
1528
			add_query_arg( $selected_args, 'visits' )
1529
		);
1530
1531
		if ( isset( $named_args['pretty'] ) ) {
1532
			$command .= ' --pretty';
1533
		}
1534
1535
		if ( isset( $named_args['strip-success'] ) ) {
1536
			$command .= ' --strip-success';
1537
		}
1538
1539
		WP_CLI::runcommand(
1540
			$command,
1541
			array(
1542
				'launch' => false, // Use the current process.
1543
			)
1544
		);
1545
	}
1546
1547
	/**
1548
	 * Allows management of publicize connections.
1549
	 *
1550
	 * ## OPTIONS
1551
	 *
1552
	 * <list|disconnect>
1553
	 * : The action to perform.
1554
	 * ---
1555
	 * options:
1556
	 *   - list
1557
	 *   - disconnect
1558
	 * ---
1559
	 *
1560
	 * [<identifier>]
1561
	 * : The connection ID or service to perform an action on.
1562
	 *
1563
	 * [--format=<format>]
1564
	 * : Allows overriding the output of the command when listing connections.
1565
	 * ---
1566
	 * default: table
1567
	 * options:
1568
	 *   - table
1569
	 *   - json
1570
	 *   - csv
1571
	 *   - yaml
1572
	 *   - ids
1573
	 *   - count
1574
	 * ---
1575
	 *
1576
	 * ## EXAMPLES
1577
	 *
1578
	 *     # List all publicize connections.
1579
	 *     $ wp jetpack publicize list
1580
	 *
1581
	 *     # List publicize connections for a given service.
1582
	 *     $ wp jetpack publicize list twitter
1583
	 *
1584
	 *     # List all publicize connections for a given user.
1585
	 *     $ wp --user=1 jetpack publicize list
1586
	 *
1587
	 *     # List all publicize connections for a given user and service.
1588
	 *     $ wp --user=1 jetpack publicize list twitter
1589
	 *
1590
	 *     # Display details for a given connection.
1591
	 *     $ wp jetpack publicize list 123456
1592
	 *
1593
	 *     # Diconnection a given connection.
1594
	 *     $ wp jetpack publicize disconnect 123456
1595
	 *
1596
	 *     # Disconnect all connections.
1597
	 *     $ wp jetpack publicize disconnect all
1598
	 *
1599
	 *     # Disconnect all connections for a given service.
1600
	 *     $ wp jetpack publicize disconnect twitter
1601
	 */
1602
	public function publicize( $args, $named_args ) {
1603
		if ( ! Jetpack::is_active() ) {
1604
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1605
		}
1606
1607
		if ( ! Jetpack::is_module_active( 'publicize' ) ) {
1608
			WP_CLI::error( __( 'The publicize module is not active.', 'jetpack' ) );
1609
		}
1610
1611
		if ( Jetpack::is_development_mode() ) {
1612
			if (
1613
				! defined( 'JETPACK_DEV_DEBUG' ) &&
1614
				! has_filter( 'jetpack_development_mode' ) &&
1615
				false === strpos( site_url(), '.' )
1616
			) {
1617
				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' ) );
1618
			}
1619
1620
			WP_CLI::error( __( 'Jetpack is currently in development mode, so the publicize module will not load.', 'jetpack' ) );
1621
		}
1622
1623
		if ( ! class_exists( 'Publicize' ) ) {
1624
			WP_CLI::error( __( 'The publicize module is not loaded.', 'jetpack' ) );
1625
		}
1626
1627
		$action        = $args[0];
1628
		$publicize     = new Publicize();
1629
		$identifier    = ! empty( $args[1] ) ? $args[1] : false;
1630
		$services      = array_keys( $publicize->get_services() );
1631
		$id_is_service = in_array( $identifier, $services, true );
1632
1633
		switch ( $action ) {
1634
			case 'list':
1635
				$connections_to_return = array();
1636
1637
				// For the CLI command, let's return all connections when a user isn't specified. This
1638
				// differs from the logic in the Publicize class.
1639
				$option_connections = is_user_logged_in()
1640
					? (array) $publicize->get_all_connections_for_user()
1641
					: (array) $publicize->get_all_connections();
1642
1643
				foreach ( $option_connections as $service_name => $connections ) {
1644
					foreach ( (array) $connections as $id => $connection ) {
1645
						$connection['id']        = $id;
1646
						$connection['service']   = $service_name;
1647
						$connections_to_return[] = $connection;
1648
					}
1649
				}
1650
1651
				if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
1652
					$temp_connections      = $connections_to_return;
1653
					$connections_to_return = array();
1654
1655
					foreach ( $temp_connections as $connection ) {
1656
						if ( $identifier === $connection['service'] ) {
1657
							$connections_to_return[] = $connection;
1658
						}
1659
					}
1660
				}
1661
1662
				if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
1663
					$connections_to_return = wp_list_filter( $connections_to_return, array( 'id' => $identifier ) );
1664
				}
1665
1666
				$expected_keys = array(
1667
					'id',
1668
					'service',
1669
					'user_id',
1670
					'provider',
1671
					'issued',
1672
					'expires',
1673
					'external_id',
1674
					'external_name',
1675
					'external_display',
1676
					'type',
1677
					'connection_data',
1678
				);
1679
1680
				// Somehow, a test site ended up in a state where $connections_to_return looked like:
1681
				// array( array( array( 'id' => 0, 'service' => 0 ) ) ) // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1682
				// This caused the CLI command to error when running WP_CLI\Utils\format_items() below. So
1683
				// to minimize future issues, this nested loop will remove any connections that don't contain
1684
				// any keys that we expect.
1685
				foreach ( (array) $connections_to_return as $connection_key => $connection ) {
1686
					foreach ( $expected_keys as $expected_key ) {
1687
						if ( ! isset( $connection[ $expected_key ] ) ) {
1688
							unset( $connections_to_return[ $connection_key ] );
1689
							continue;
1690
						}
1691
					}
1692
				}
1693
1694
				if ( empty( $connections_to_return ) ) {
1695
					return false;
1696
				}
1697
1698
				WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
1699
				break; // list.
1700
			case 'disconnect':
1701
				if ( ! $identifier ) {
1702
					WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
1703
				}
1704
1705
				// If the connection ID is 'all' then delete all connections. If the connection ID
1706
				// matches a service, delete all connections for that service.
1707
				if ( 'all' === $identifier || $id_is_service ) {
1708
					if ( 'all' === $identifier ) {
1709
						WP_CLI::log( __( "You're about to delete all publicize connections.", 'jetpack' ) );
1710
					} else {
1711
						/* translators: %s is a lowercase string for a social network. */
1712
						WP_CLI::log( sprintf( __( "You're about to delete all publicize connections to %s.", 'jetpack' ), $identifier ) );
1713
					}
1714
1715
					jetpack_cli_are_you_sure();
1716
1717
					$connections = array();
1718
					$service     = $identifier;
1719
1720
					$option_connections = is_user_logged_in()
1721
						? (array) $publicize->get_all_connections_for_user()
1722
						: (array) $publicize->get_all_connections();
1723
1724
					if ( 'all' === $service ) {
1725
						foreach ( (array) $option_connections as $service_name => $service_connections ) {
1726
							foreach ( $service_connections as $id => $connection ) {
1727
								$connections[ $id ] = $connection;
1728
							}
1729
						}
1730
					} elseif ( ! empty( $option_connections[ $service ] ) ) {
1731
						$connections = $option_connections[ $service ];
1732
					}
1733
1734
					if ( ! empty( $connections ) ) {
1735
						$count    = count( $connections );
1736
						$progress = \WP_CLI\Utils\make_progress_bar(
1737
							/* translators: %s is a lowercase string for a social network. */
1738
							sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
1739
							$count
1740
						);
1741
1742
						foreach ( $connections as $id => $connection ) {
1743
							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...
1744
								WP_CLI::error(
1745
									sprintf(
1746
										/* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
1747
										__( 'Publicize connection %d could not be disconnected', 'jetpack' ),
1748
										$id
1749
									)
1750
								);
1751
							}
1752
1753
							$progress->tick();
1754
						}
1755
1756
						$progress->finish();
1757
1758
						if ( 'all' === $service ) {
1759
							WP_CLI::success( __( 'All publicize connections were successfully disconnected.', 'jetpack' ) );
1760
						} else {
1761
							/* translators: %s is a lowercase string for a social network. */
1762
							WP_CLI::success( __( 'All publicize connections to %s were successfully disconnected.', 'jetpack' ), $service );
1763
						}
1764
					}
1765
				} else {
1766
					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...
1767
						/* translators: %d is a numeric ID. Example: 1234. */
1768
						WP_CLI::success( sprintf( __( 'Publicize connection %d has been disconnected.', 'jetpack' ), $identifier ) );
1769
					} else {
1770
						/* translators: %d is a numeric ID. Example: 1234. */
1771
						WP_CLI::error( sprintf( __( 'Publicize connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
1772
					}
1773
				}
1774
				break; // disconnect.
1775
		}
1776
	}
1777
1778
	private function get_api_host() {
1779
		$env_api_host = getenv( 'JETPACK_START_API_HOST', true );
1780
		return $env_api_host ? $env_api_host : JETPACK__WPCOM_JSON_API_HOST;
1781
	}
1782
1783
	private function partner_provision_error( $error ) {
1784
		WP_CLI::log(
1785
			json_encode(
1786
				array(
1787
					'success'       => false,
1788
					'error_code'    => $error->get_error_code(),
1789
					'error_message' => $error->get_error_message(),
1790
				)
1791
			)
1792
		);
1793
		exit( 1 );
1794
	}
1795
1796
	/**
1797
	 * Creates the essential files in Jetpack to start building a Gutenberg block or plugin.
1798
	 *
1799
	 * ## TYPES
1800
	 *
1801
	 * 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.
1802
	 *
1803
	 * ## BLOCK TYPE OPTIONS
1804
	 *
1805
	 * The first parameter is the block title and it's not associative. Add it wrapped in quotes.
1806
	 * 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.
1807
	 * --slug: Specific slug to identify the block that overrides the one generated based on the title.
1808
	 * --description: Allows to provide a text description of the block.
1809
	 * --keywords: Provide up to three keywords separated by comma so users can find this block when they search in Gutenberg's inserter.
1810
	 *
1811
	 * ## BLOCK TYPE EXAMPLES
1812
	 *
1813
	 * wp jetpack scaffold block "Cool Block"
1814
	 * wp jetpack scaffold block "Amazing Rock" --slug="good-music" --description="Rock the best music on your site"
1815
	 * wp jetpack scaffold block "Jukebox" --keywords="music, audio, media"
1816
	 *
1817
	 * @subcommand scaffold block
1818
	 * @synopsis <type> <title> [--slug] [--description] [--keywords]
1819
	 *
1820
	 * @param array $args       Positional parameters, when strings are passed, wrap them in quotes.
1821
	 * @param array $assoc_args Associative parameters like --slug="nice-block".
1822
	 */
1823
	public function scaffold( $args, $assoc_args ) {
1824
		// It's ok not to check if it's set, because otherwise WPCLI exits earlier.
1825
		switch ( $args[0] ) {
1826
			case 'block':
1827
				$this->block( $args, $assoc_args );
1828
				break;
1829
			default:
1830
				/* translators: %s is the subcommand */
1831
				WP_CLI::error( sprintf( esc_html__( 'Invalid subcommand %s.', 'jetpack' ), $args[0] ) . ' 👻' );
1832
				exit( 1 );
1833
		}
1834
	}
1835
1836
	/**
1837
	 * Creates the essential files in Jetpack to build a Gutenberg block.
1838
	 *
1839
	 * @param array $args       Positional parameters. Only one is used, that corresponds to the block title.
1840
	 * @param array $assoc_args Associative parameters defined in the scaffold() method.
1841
	 */
1842
	public function block( $args, $assoc_args ) {
1843 View Code Duplication
		if ( isset( $args[1] ) ) {
1844
			$title = ucwords( $args[1] );
1845
		} else {
1846
			WP_CLI::error( esc_html__( 'The title parameter is required.', 'jetpack' ) . ' 👻' );
1847
			exit( 1 );
1848
		}
1849
1850
		$slug = isset( $assoc_args['slug'] )
1851
			? $assoc_args['slug']
1852
			: sanitize_title( $title );
1853
1854
		if ( preg_match( '#^jetpack/#', $slug ) ) {
1855
			$slug = preg_replace( '#^jetpack/#', '', $slug );
1856
		}
1857
1858
		if ( ! preg_match( '/^[a-z][a-z0-9\-]*$/', $slug ) ) {
1859
			WP_CLI::error( esc_html__( 'Invalid block slug. They can contain only lowercase alphanumeric characters or dashes, and start with a letter', 'jetpack' ) . ' 👻' );
1860
		}
1861
1862
		global $wp_filesystem;
1863
		if ( ! WP_Filesystem() ) {
1864
			WP_CLI::error( esc_html__( "Can't write files", 'jetpack' ) . ' 😱' );
1865
		}
1866
1867
		$path = JETPACK__PLUGIN_DIR . "extensions/blocks/$slug";
1868
1869
		if ( $wp_filesystem->exists( $path ) && $wp_filesystem->is_dir( $path ) ) {
1870
			/* translators: %s is path to the conflicting block */
1871
			WP_CLI::error( sprintf( esc_html__( 'Name conflicts with the existing block %s', 'jetpack' ), $path ) . ' ⛔️' );
1872
			exit( 1 );
1873
		}
1874
1875
		$wp_filesystem->mkdir( $path );
1876
1877
		$hasKeywords = isset( $assoc_args['keywords'] );
1878
1879
		$files = array(
1880
			"$path/$slug.php"   => $this->render_block_file(
1881
				'block-register-php',
1882
				array(
1883
					'slug'            => $slug,
1884
					'title'           => $title,
1885
					'underscoredSlug' => str_replace( '-', '_', $slug ),
1886
				)
1887
			),
1888
			"$path/index.js"    => $this->render_block_file(
1889
				'block-index-js',
1890
				array(
1891
					'slug'        => $slug,
1892
					'title'       => $title,
1893
					'description' => isset( $assoc_args['description'] )
1894
						? $assoc_args['description']
1895
						: $title,
1896
					'keywords'    => $hasKeywords
1897
					? array_map(
1898
						function( $keyword ) {
1899
								// Construction necessary for Mustache lists
1900
								return array( 'keyword' => trim( $keyword ) );
1901
						},
1902
						explode( ',', $assoc_args['keywords'], 3 )
1903
					)
1904
					: '',
1905
					'hasKeywords' => $hasKeywords,
1906
				)
1907
			),
1908
			"$path/editor.js"   => $this->render_block_file( 'block-editor-js' ),
1909
			"$path/editor.scss" => $this->render_block_file(
1910
				'block-editor-scss',
1911
				array(
1912
					'slug'  => $slug,
1913
					'title' => $title,
1914
				)
1915
			),
1916
			"$path/edit.js"     => $this->render_block_file(
1917
				'block-edit-js',
1918
				array(
1919
					'title'     => $title,
1920
					'className' => str_replace( ' ', '', ucwords( str_replace( '-', ' ', $slug ) ) ),
1921
				)
1922
			),
1923
		);
1924
1925
		$files_written = array();
1926
1927
		foreach ( $files as $filename => $contents ) {
1928
			if ( $wp_filesystem->put_contents( $filename, $contents ) ) {
1929
				$files_written[] = $filename;
1930
			} else {
1931
				/* translators: %s is a file name */
1932
				WP_CLI::error( sprintf( esc_html__( 'Error creating %s', 'jetpack' ), $filename ) );
1933
			}
1934
		}
1935
1936
		if ( empty( $files_written ) ) {
1937
			WP_CLI::log( esc_html__( 'No files were created', 'jetpack' ) );
1938
		} else {
1939
			// Load index.json and insert the slug of the new block in the production array
1940
			$block_list_path = JETPACK__PLUGIN_DIR . 'extensions/index.json';
1941
			$block_list      = $wp_filesystem->get_contents( $block_list_path );
1942
			if ( empty( $block_list ) ) {
1943
				/* translators: %s is the path to the file with the block list */
1944
				WP_CLI::error( sprintf( esc_html__( 'Error fetching contents of %s', 'jetpack' ), $block_list_path ) );
1945
			} elseif ( false === stripos( $block_list, $slug ) ) {
1946
				$new_block_list         = json_decode( $block_list );
1947
				$new_block_list->beta[] = $slug;
1948
				if ( ! $wp_filesystem->put_contents( $block_list_path, wp_json_encode( $new_block_list ) ) ) {
1949
					/* translators: %s is the path to the file with the block list */
1950
					WP_CLI::error( sprintf( esc_html__( 'Error writing new %s', 'jetpack' ), $block_list_path ) );
1951
				}
1952
			}
1953
1954
			WP_CLI::success(
1955
				sprintf(
1956
					/* translators: the placeholders are a human readable title, and a series of words separated by dashes */
1957
					esc_html__( 'Successfully created block %1$s with slug %2$s', 'jetpack' ) . ' 🎉' . "\n" .
1958
					"--------------------------------------------------------------------------------------------------------------------\n" .
1959
					/* translators: the placeholder is a directory path */
1960
					esc_html__( 'The files were created at %s', 'jetpack' ) . "\n" .
1961
					esc_html__( 'To start using the block, build the blocks with yarn run build-extensions', 'jetpack' ) . "\n" .
1962
					/* translators: the placeholder is a file path */
1963
					esc_html__( 'The block slug has been added to the beta list at %s', 'jetpack' ) . "\n" .
1964
					esc_html__( 'To load the block, add the constant JETPACK_BETA_BLOCKS as true to your wp-config.php file', 'jetpack' ) . "\n" .
1965
					/* translators: the placeholder is a URL */
1966
					"\n" . esc_html__( 'Read more at %s', 'jetpack' ) . "\n",
1967
					$title,
1968
					$slug,
1969
					$path,
1970
					$block_list_path,
1971
					'https://github.com/Automattic/jetpack/blob/master/extensions/README.md#develop-new-blocks'
1972
				) . '--------------------------------------------------------------------------------------------------------------------'
1973
			);
1974
		}
1975
	}
1976
1977
	/**
1978
	 * Built the file replacing the placeholders in the template with the data supplied.
1979
	 *
1980
	 * @param string $template
1981
	 * @param array  $data
1982
	 *
1983
	 * @return string mixed
1984
	 */
1985
	private static function render_block_file( $template, $data = array() ) {
1986
		return \WP_CLI\Utils\mustache_render( JETPACK__PLUGIN_DIR . "wp-cli-templates/$template.mustache", $data );
1987
	}
1988
}
1989
1990
/*
1991
 * Standard "ask for permission to continue" function.
1992
 * If action cancelled, ask if they need help.
1993
 *
1994
 * Written outside of the class so it's not listed as an executable command w/ 'wp jetpack'
1995
 *
1996
 * @param $flagged   bool   false = normal option | true = flagged by get_jetpack_options_for_reset()
1997
 * @param $error_msg string (optional)
1998
 */
1999
function jetpack_cli_are_you_sure( $flagged = false, $error_msg = false ) {
2000
	$cli = new Jetpack_CLI();
2001
2002
	// Default cancellation message
2003
	if ( ! $error_msg ) {
2004
		$error_msg =
2005
			__( 'Action cancelled. Have a question?', 'jetpack' )
2006
			. ' '
2007
			. $cli->green_open
2008
			. 'jetpack.com/support'
2009
			. $cli->color_close;
2010
	}
2011
2012
	if ( ! $flagged ) {
2013
		$prompt_message = _x( 'Are you sure? This cannot be undone. Type "yes" to continue:', '"yes" is a command - do not translate.', 'jetpack' );
2014
	} else {
2015
		$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' );
2016
	}
2017
2018
	WP_CLI::line( $prompt_message );
2019
	$handle = fopen( 'php://stdin', 'r' );
2020
	$line   = fgets( $handle );
2021
	if ( 'yes' != trim( $line ) ) {
2022
		WP_CLI::error( $error_msg );
2023
	}
2024
}
2025