Completed
Push — update/dialogue-focus-on-conte... ( 9f1745...fa862f )
by
unknown
80:03 queued 71:18
created

Jetpack_CLI::block()   F

Complexity

Conditions 21
Paths 1793

Size

Total Lines 168

Duplication

Lines 6
Ratio 3.57 %

Importance

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

There are different options of fixing this problem.

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

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

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

Loading history...
61
				$error[] = $fail['name'] . ': ' . $fail['message'];
62
			}
63
			WP_CLI::error_multi_line( $error );
64
65
			$cxntests->output_results_for_cli();
66
67
			WP_CLI::error( __( 'One or more tests did not pass. Please investigate!', 'jetpack' ) ); // Exit CLI.
68
		}
69
70
		/* translators: %s is current version of Jetpack, for example 7.3 */
71
		WP_CLI::line( sprintf( __( 'The Jetpack Version is %s', 'jetpack' ), JETPACK__VERSION ) );
72
		/* translators: %d is WP.com ID of this blog */
73
		WP_CLI::line( sprintf( __( 'The WordPress.com blog_id is %d', 'jetpack' ), Jetpack_Options::get_option( 'id' ) ) );
74
		/* translators: %s is the email address of the connection owner */
75
		WP_CLI::line( sprintf( __( 'The WordPress.com account for the primary connection is %s', 'jetpack' ), $master_user_email ) );
76
77
		/*
78
		 * Are they asking for all data?
79
		 *
80
		 * Loop through heartbeat data and organize by priority.
81
		 */
82
		$all_data = ( isset( $args[0] ) && 'full' == $args[0] ) ? 'full' : false;
83
		if ( $all_data ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $all_data of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
84
			// Heartbeat data
85
			WP_CLI::line( "\n" . __( 'Additional data: ', 'jetpack' ) );
86
87
			// Get the filtered heartbeat data.
88
			// Filtered so we can color/list by severity
89
			$stats = Jetpack::jetpack_check_heartbeat_data();
90
91
			// Display red flags first
92
			foreach ( $stats['bad'] as $stat => $value ) {
93
				printf( "$this->red_open%-'.16s %s $this->color_close\n", $stat, $value );
94
			}
95
96
			// Display caution warnings next
97
			foreach ( $stats['caution'] as $stat => $value ) {
98
				printf( "$this->yellow_open%-'.16s %s $this->color_close\n", $stat, $value );
99
			}
100
101
			// The rest of the results are good!
102
			foreach ( $stats['good'] as $stat => $value ) {
103
104
				// Modules should get special spacing for aestetics
105
				if ( strpos( $stat, 'odule-' ) ) {
106
					printf( "%-'.30s %s\n", $stat, $value );
107
					usleep( 4000 ); // For dramatic effect lolz
108
					continue;
109
				}
110
				printf( "%-'.16s %s\n", $stat, $value );
111
				usleep( 4000 ); // For dramatic effect lolz
112
			}
113
		} else {
114
			// Just the basics
115
			WP_CLI::line( "\n" . _x( "View full status with 'wp jetpack status full'", '"wp jetpack status full" is a command - do not translate', 'jetpack' ) );
116
		}
117
	}
118
119
	/**
120
	 * Tests the active connection
121
	 *
122
	 * Does a two-way test to verify that the local site can communicate with remote Jetpack/WP.com servers and that Jetpack/WP.com servers can talk to the local site.
123
	 *
124
	 * ## EXAMPLES
125
	 *
126
	 * wp jetpack test-connection
127
	 *
128
	 * @subcommand test-connection
129
	 */
130
	public function test_connection( $args, $assoc_args ) {
131
132
		/* translators: %s is the site URL */
133
		WP_CLI::line( sprintf( __( 'Testing connection for %s', 'jetpack' ), esc_url( get_site_url() ) ) );
134
135
		if ( ! Jetpack::is_active() ) {
136
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
137
		}
138
139
		$response = Client::wpcom_json_api_request_as_blog(
140
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
141
			Client::WPCOM_JSON_API_VERSION
142
		);
143
144
		if ( is_wp_error( $response ) ) {
145
			/* translators: %1$s is the error code, %2$s is the error message */
146
			WP_CLI::error( sprintf( __( 'Failed to test connection (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ) );
147
		}
148
149
		$body = wp_remote_retrieve_body( $response );
150
		if ( ! $body ) {
151
			WP_CLI::error( __( 'Failed to test connection (empty response body)', 'jetpack' ) );
152
		}
153
154
		$result       = json_decode( $body );
155
		$is_connected = (bool) $result->connected;
156
		$message      = $result->message;
157
158
		if ( $is_connected ) {
159
			WP_CLI::success( $message );
160
		} else {
161
			WP_CLI::error( $message );
162
		}
163
	}
164
165
	/**
166
	 * Disconnect Jetpack Blogs or Users
167
	 *
168
	 * ## OPTIONS
169
	 *
170
	 * blog: Disconnect the entire blog.
171
	 *
172
	 * user <user_identifier>: Disconnect a specific user from WordPress.com.
173
	 *
174
	 * Please note, the primary account that the blog is connected
175
	 * to WordPress.com with cannot be disconnected without
176
	 * disconnecting the entire blog.
177
	 *
178
	 * ## EXAMPLES
179
	 *
180
	 * wp jetpack disconnect blog
181
	 * wp jetpack disconnect user 13
182
	 * wp jetpack disconnect user username
183
	 * wp jetpack disconnect user [email protected]
184
	 *
185
	 * @synopsis <blog|user> [<user_identifier>]
186
	 */
187
	public function disconnect( $args, $assoc_args ) {
188
		if ( ! Jetpack::is_active() ) {
189
			WP_CLI::success( __( 'The site is not currently connected, so nothing to do!', 'jetpack' ) );
190
			return;
191
		}
192
193
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
194 View Code Duplication
		if ( ! in_array( $action, array( 'blog', 'user', 'prompt' ) ) ) {
195
			/* translators: %s is a command like "prompt" */
196
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
197
		}
198
199
		if ( in_array( $action, array( 'user' ) ) ) {
200
			if ( isset( $args[1] ) ) {
201
				$user_id = $args[1];
202
				if ( ctype_digit( $user_id ) ) {
203
					$field   = 'id';
204
					$user_id = (int) $user_id;
205
				} elseif ( is_email( $user_id ) ) {
206
					$field   = 'email';
207
					$user_id = sanitize_user( $user_id, true );
208
				} else {
209
					$field   = 'login';
210
					$user_id = sanitize_user( $user_id, true );
211
				}
212
				if ( ! $user = get_user_by( $field, $user_id ) ) {
213
					WP_CLI::error( __( 'Please specify a valid user.', 'jetpack' ) );
214
				}
215
			} else {
216
				WP_CLI::error( __( 'Please specify a user by either ID, username, or email.', 'jetpack' ) );
217
			}
218
		}
219
220
		switch ( $action ) {
221
			case 'blog':
222
				Jetpack::log( 'disconnect' );
223
				Jetpack::disconnect();
224
				WP_CLI::success(
225
					sprintf(
226
						/* translators: %s is the site URL */
227
						__( 'Jetpack has been successfully disconnected for %s.', 'jetpack' ),
228
						esc_url( get_site_url() )
229
					)
230
				);
231
				break;
232
			case 'user':
233
				if ( Connection_Manager::disconnect_user( $user->ID ) ) {
234
					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...
235
					WP_CLI::success( __( 'User has been successfully disconnected.', 'jetpack' ) );
236
				} else {
237
					/* translators: %s is a username */
238
					WP_CLI::error( sprintf( __( "User %s could not be disconnected. Are you sure they're connected currently?", 'jetpack' ), "{$user->login} <{$user->email}>" ) );
239
				}
240
				break;
241
			case 'prompt':
242
				WP_CLI::error( __( 'Please specify if you would like to disconnect a blog or user.', 'jetpack' ) );
243
				break;
244
		}
245
	}
246
247
	/**
248
	 * Reset Jetpack options and settings to default
249
	 *
250
	 * ## OPTIONS
251
	 *
252
	 * modules: Resets modules to default state ( get_default_modules() )
253
	 *
254
	 * options: Resets all Jetpack options except:
255
	 *  - All private options (Blog token, user token, etc...)
256
	 *  - id (The Client ID/WP.com Blog ID of this site)
257
	 *  - master_user
258
	 *  - version
259
	 *  - activated
260
	 *
261
	 * ## EXAMPLES
262
	 *
263
	 * wp jetpack reset options
264
	 * wp jetpack reset modules
265
	 * wp jetpack reset sync-checksum --dry-run --offset=0
266
	 *
267
	 * @synopsis <modules|options|sync-checksum> [--dry-run] [--offset=<offset>]
268
	 */
269
	public function reset( $args, $assoc_args ) {
270
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
271 View Code Duplication
		if ( ! in_array( $action, array( 'options', 'modules', 'sync-checksum' ), true ) ) {
272
			/* translators: %s is a command like "prompt" */
273
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
274
		}
275
276
		$is_dry_run = ! empty( $assoc_args['dry-run'] );
277
278 View Code Duplication
		if ( $is_dry_run ) {
279
			WP_CLI::warning(
280
				__( "\nThis is a dry run.\n", 'jetpack' ) .
281
				__( "No actions will be taken.\n", 'jetpack' ) .
282
				__( "The following messages will give you preview of what will happen when you run this command.\n\n", 'jetpack' )
283
			);
284
		} else {
285
			// We only need to confirm "Are you sure?" when we are not doing a dry run.
286
			jetpack_cli_are_you_sure();
287
		}
288
289
		switch ( $action ) {
290
			case 'options':
291
				$options_to_reset = Jetpack_Options::get_options_for_reset();
292
				// Reset the Jetpack options
293
				WP_CLI::line(
294
					sprintf(
295
						/* translators: %s is the site URL */
296
						__( "Resetting Jetpack Options for %s...\n", 'jetpack' ),
297
						esc_url( get_site_url() )
298
					)
299
				);
300
				sleep( 1 ); // Take a breath
301 View Code Duplication
				foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
302
					if ( ! $is_dry_run ) {
303
						Jetpack_Options::delete_option( $option_to_reset );
304
						usleep( 100000 );
305
					}
306
307
					/* translators: This is the result of an action. The option named %s was reset */
308
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
309
				}
310
311
				// Reset the WP options
312
				WP_CLI::line( __( "Resetting the jetpack options stored in wp_options...\n", 'jetpack' ) );
313
				usleep( 500000 ); // Take a breath
314 View Code Duplication
				foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
315
					if ( ! $is_dry_run ) {
316
						delete_option( $option_to_reset );
317
						usleep( 100000 );
318
					}
319
					/* translators: This is the result of an action. The option named %s was reset */
320
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
321
				}
322
323
				// Reset to default modules
324
				WP_CLI::line( __( "Resetting default modules...\n", 'jetpack' ) );
325
				usleep( 500000 ); // Take a breath
326
				$default_modules = Jetpack::get_default_modules();
327
				if ( ! $is_dry_run ) {
328
					Jetpack::update_active_modules( $default_modules );
329
				}
330
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
331
				break;
332 View Code Duplication
			case 'modules':
333
				if ( ! $is_dry_run ) {
334
					$default_modules = Jetpack::get_default_modules();
335
					Jetpack::update_active_modules( $default_modules );
336
				}
337
338
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
339
				break;
340
			case 'prompt':
341
				WP_CLI::error( __( 'Please specify if you would like to reset your options, modules or sync-checksum', 'jetpack' ) );
342
				break;
343
			case 'sync-checksum':
344
				$option = 'jetpack_callables_sync_checksum';
345
346
				if ( is_multisite() ) {
347
					$offset = isset( $assoc_args['offset'] ) ? (int) $assoc_args['offset'] : 0;
348
349
					/*
350
					 * 1000 is a good limit since we don't expect the number of sites to be more than 1000
351
					 * Offset can be used to paginate and try to clean up more sites.
352
					 */
353
					$sites       = get_sites(
354
						array(
355
							'number' => 1000,
356
							'offset' => $offset,
357
						)
358
					);
359
					$count_fixes = 0;
360
					foreach ( $sites as $site ) {
361
						switch_to_blog( $site->blog_id );
362
						$count = self::count_option( $option );
363
						if ( $count > 1 ) {
364
							if ( ! $is_dry_run ) {
365
								delete_option( $option );
366
							}
367
							WP_CLI::line(
368
								sprintf(
369
									/* translators: %1$d is a number, %2$s is the name of an option, %2$s is the site URL. */
370
									__( 'Deleted %1$d %2$s options from %3$s', 'jetpack' ),
371
									$count,
372
									$option,
373
									"{$site->domain}{$site->path}"
374
								)
375
							);
376
							$count_fixes++;
377
							if ( ! $is_dry_run ) {
378
								/*
379
								 * We could be deleting a lot of options rows at the same time.
380
								 * Allow some time for replication to catch up.
381
								 */
382
								sleep( 3 );
383
							}
384
						}
385
386
						restore_current_blog();
387
					}
388
					if ( $count_fixes ) {
389
						WP_CLI::success(
390
							sprintf(
391
								/* translators: %1$s is the name of an option, %2$d is a number of sites. */
392
								__( 'Successfully reset %1$s on %2$d sites.', 'jetpack' ),
393
								$option,
394
								$count_fixes
395
							)
396
						);
397
					} else {
398
						WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
399
					}
400
					return;
401
				}
402
403
				$count = self::count_option( $option );
404
				if ( $count > 1 ) {
405
					if ( ! $is_dry_run ) {
406
						delete_option( $option );
407
					}
408
					WP_CLI::success(
409
						sprintf(
410
							/* translators: %1$d is a number, %2$s is the name of an option. */
411
							__( 'Deleted %1$d %2$s options', 'jetpack' ),
412
							$count,
413
							$option
414
						)
415
					);
416
					return;
417
				}
418
419
				WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
420
				break;
421
422
		}
423
	}
424
425
	/**
426
	 * Return the number of times an option appears
427
	 * Normally an option would only appear 1 since the option key is supposed to be unique
428
	 * but if a site hasn't updated the DB schema then that would not be the case.
429
	 *
430
	 * @param string $option Option name.
431
	 *
432
	 * @return int
433
	 */
434
	private static function count_option( $option ) {
435
		global $wpdb;
436
		return (int) $wpdb->get_var(
437
			$wpdb->prepare(
438
				"SELECT COUNT(*) FROM $wpdb->options WHERE option_name = %s",
439
				$option
440
			)
441
		);
442
443
	}
444
445
	/**
446
	 * Manage Jetpack Modules
447
	 *
448
	 * ## OPTIONS
449
	 *
450
	 * <list|activate|deactivate|toggle>
451
	 * : The action to take.
452
	 * ---
453
	 * default: list
454
	 * options:
455
	 *  - list
456
	 *  - activate
457
	 *  - deactivate
458
	 *  - toggle
459
	 * ---
460
	 *
461
	 * [<module_slug>]
462
	 * : The slug of the module to perform an action on.
463
	 *
464
	 * [--format=<format>]
465
	 * : Allows overriding the output of the command when listing modules.
466
	 * ---
467
	 * default: table
468
	 * options:
469
	 *  - table
470
	 *  - json
471
	 *  - csv
472
	 *  - yaml
473
	 *  - ids
474
	 *  - count
475
	 * ---
476
	 *
477
	 * ## EXAMPLES
478
	 *
479
	 * wp jetpack module list
480
	 * wp jetpack module list --format=json
481
	 * wp jetpack module activate stats
482
	 * wp jetpack module deactivate stats
483
	 * wp jetpack module toggle stats
484
	 * wp jetpack module activate all
485
	 * wp jetpack module deactivate all
486
	 */
487
	public function module( $args, $assoc_args ) {
488
		$action = isset( $args[0] ) ? $args[0] : 'list';
489
490
		if ( isset( $args[1] ) ) {
491
			$module_slug = $args[1];
492
			if ( 'all' !== $module_slug && ! Jetpack::is_module( $module_slug ) ) {
493
				/* translators: %s is a module slug like "stats" */
494
				WP_CLI::error( sprintf( __( '%s is not a valid module.', 'jetpack' ), $module_slug ) );
495
			}
496
			if ( 'toggle' === $action ) {
497
				$action = Jetpack::is_module_active( $module_slug )
498
					? 'deactivate'
499
					: 'activate';
500
			}
501
			if ( 'all' === $args[1] ) {
502
				$action = ( 'deactivate' === $action )
503
					? 'deactivate_all'
504
					: 'activate_all';
505
			}
506
		} elseif ( 'list' !== $action ) {
507
			WP_CLI::line( __( 'Please specify a valid module.', 'jetpack' ) );
508
			$action = 'list';
509
		}
510
511
		switch ( $action ) {
512
			case 'list':
513
				$modules_list = array();
514
				$modules      = Jetpack::get_available_modules();
515
				sort( $modules );
516
				foreach ( (array) $modules as $module_slug ) {
517
					if ( 'vaultpress' === $module_slug ) {
518
						continue;
519
					}
520
					$modules_list[] = array(
521
						'slug'   => $module_slug,
522
						'status' => Jetpack::is_module_active( $module_slug )
523
							? __( 'Active', 'jetpack' )
524
							: __( 'Inactive', 'jetpack' ),
525
					);
526
				}
527
				WP_CLI\Utils\format_items( $assoc_args['format'], $modules_list, array( 'slug', 'status' ) );
528
				break;
529
			case 'activate':
530
				$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...
531
				Jetpack::log( 'activate', $module_slug );
532
				if ( Jetpack::activate_module( $module_slug, false, false ) ) {
533
					/* translators: %s is the name of a Jetpack module */
534
					WP_CLI::success( sprintf( __( '%s has been activated.', 'jetpack' ), $module['name'] ) );
535
				} else {
536
					/* translators: %s is the name of a Jetpack module */
537
					WP_CLI::error( sprintf( __( '%s could not be activated.', 'jetpack' ), $module['name'] ) );
538
				}
539
				break;
540 View Code Duplication
			case 'activate_all':
541
				$modules = Jetpack::get_available_modules();
542
				Jetpack::update_active_modules( $modules );
543
				WP_CLI::success( __( 'All modules activated!', 'jetpack' ) );
544
				break;
545
			case 'deactivate':
546
				$module = Jetpack::get_module( $module_slug );
547
				Jetpack::log( 'deactivate', $module_slug );
548
				Jetpack::deactivate_module( $module_slug );
549
				/* translators: %s is the name of a Jetpack module */
550
				WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) );
551
				break;
552
			case 'deactivate_all':
553
				Jetpack::delete_active_modules();
554
				WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) );
555
				break;
556
			case 'toggle':
557
				// Will never happen, should have been handled above and changed to activate or deactivate.
558
				break;
559
		}
560
	}
561
562
	/**
563
	 * Manage Protect Settings
564
	 *
565
	 * ## OPTIONS
566
	 *
567
	 * allow: Add an IP address to an always allow list.  You can also read or clear the allow list.
568
	 *
569
	 *
570
	 * ## EXAMPLES
571
	 *
572
	 * wp jetpack protect allow <ip address>
573
	 * wp jetpack protect allow list
574
	 * wp jetpack protect allow clear
575
	 *
576
	 * @synopsis <allow> [<ip|ip_low-ip_high|list|clear>]
577
	 */
578
	public function protect( $args, $assoc_args ) {
579
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
580 View Code Duplication
		if ( ! in_array( $action, array( 'whitelist', 'allow' ), true ) ) { // Still allow "whitelist" for legacy support.
581
			/* translators: %s is a command like "prompt" */
582
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
583
		}
584
		// Check if module is active
585
		if ( ! Jetpack::is_module_active( __FUNCTION__ ) ) {
586
			/* translators: %s is a module name */
587
			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__ ) );
588
		}
589
		if ( in_array( $action, array( 'allow', 'whitelist' ), true ) ) {
590
			if ( isset( $args[1] ) ) {
591
				$action = 'allow';
592
			} else {
593
				$action = 'prompt';
594
			}
595
		}
596
		switch ( $action ) {
597
			case 'allow':
598
				$allow         = array();
599
				$new_ip        = $args[1];
600
				$current_allow = get_site_option( 'jetpack_protect_whitelist', array() ); // @todo Update the option name.
601
602
				// Build array of IPs that are already on the allowed list.
603
				// Re-build manually instead of using jetpack_protect_format_whitelist() so we can easily get
604
				// low & high range params for jetpack_protect_ip_address_is_in_range();
605
				foreach ( $current_allow as $allowed ) {
606
607
					// IP ranges
608
					if ( $allowed->range ) {
609
610
						// Is it already on the allowed list?
611
						if ( jetpack_protect_ip_address_is_in_range( $new_ip, $allowed->range_low, $allowed->range_high ) ) {
612
							/* translators: %s is an IP address */
613
							WP_CLI::error( sprintf( __( '%s is already on the always allow list.', 'jetpack' ), $new_ip ) );
614
							break;
615
						}
616
						$allow[] = $allowed->range_low . ' - ' . $allowed->range_high;
617
618
					} else { // Individual IPs
619
620
						// Check if the IP is already on the allow list (single IP only).
621
						if ( $new_ip === $allowed->ip_address ) {
622
							/* translators: %s is an IP address */
623
							WP_CLI::error( sprintf( __( '%s is already on the always allow list.', 'jetpack' ), $new_ip ) );
624
							break;
625
						}
626
						$allow[] = $allowed->ip_address;
627
628
					}
629
				}
630
631
				/*
632
				 * List the allowed IPs.
633
				 * Done here because it's easier to read the $allow array after it's been rebuilt.
634
				 */
635
				if ( isset( $args[1] ) && 'list' == $args[1] ) {
636
					if ( ! empty( $allow ) ) {
637
						WP_CLI::success( __( 'Here are your always allowed IPs:', 'jetpack' ) );
638
						foreach ( $allow as $ip ) {
639
							WP_CLI::line( "\t" . str_pad( $ip, 24 ) );
640
						}
641
					} else {
642
						WP_CLI::line( __( 'Always allow list is empty.', 'jetpack' ) );
643
					}
644
					break;
645
				}
646
647
				/*
648
				 * Clear the always allow list.
649
				 */
650
				if ( isset( $args[1] ) && 'clear' == $args[1] ) {
651
					if ( ! empty( $allow ) ) {
652
						$allow = array();
653
						jetpack_protect_save_whitelist( $allow ); // @todo Need to update function name in the Protect module.
654
						WP_CLI::success( __( 'Cleared all IPs from the always allow list.', 'jetpack' ) );
655
					} else {
656
						WP_CLI::line( __( 'Always allow list is empty.', 'jetpack' ) );
657
					}
658
					break;
659
				}
660
661
				// Append new IP to allow array.
662
				array_push( $allow, $new_ip );
663
664
				// Save allow list if there are no errors.
665
				$result = jetpack_protect_save_whitelist( $allow ); // @todo Need to update function name in the Protect module.
666
				if ( is_wp_error( $result ) ) {
667
					WP_CLI::error( $result );
668
				}
669
670
				/* translators: %s is an IP address */
671
				WP_CLI::success( sprintf( __( '%s has been added to the always allowed list.', 'jetpack' ), $new_ip ) );
672
				break;
673
			case 'prompt':
674
				WP_CLI::error(
675
					__( 'No command found.', 'jetpack' ) . "\n" .
676
					__( 'Please enter the IP address you want to always allow.', 'jetpack' ) . "\n" .
677
					_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 add IP ranges - low_range/high_range should be translated.', 'jetpack' ) . "\n" .
678
					_x( "You can also 'list' or 'clear' the always allowed list.", "'list' and 'clear' are commands and should not be translated", 'jetpack' ) . "\n"
679
				);
680
				break;
681
		}
682
	}
683
684
	/**
685
	 * Manage Jetpack Options
686
	 *
687
	 * ## OPTIONS
688
	 *
689
	 * list   : List all jetpack options and their values
690
	 * delete : Delete an option
691
	 *          - can only delete options that are white listed.
692
	 * update : update an option
693
	 *          - can only update option strings
694
	 * get    : get the value of an option
695
	 *
696
	 * ## EXAMPLES
697
	 *
698
	 * wp jetpack options list
699
	 * wp jetpack options get    <option_name>
700
	 * wp jetpack options delete <option_name>
701
	 * wp jetpack options update <option_name> [<option_value>]
702
	 *
703
	 * @synopsis <list|get|delete|update> [<option_name>] [<option_value>]
704
	 */
705
	public function options( $args, $assoc_args ) {
706
		$action         = isset( $args[0] ) ? $args[0] : 'list';
707
		$safe_to_modify = Jetpack_Options::get_options_for_reset();
708
709
		// Is the option flagged as unsafe?
710
		$flagged = ! in_array( $args[1], $safe_to_modify );
711
712 View Code Duplication
		if ( ! in_array( $action, array( 'list', 'get', 'delete', 'update' ) ) ) {
713
			/* translators: %s is a command like "prompt" */
714
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
715
		}
716
717
		if ( isset( $args[0] ) ) {
718
			if ( 'get' == $args[0] && isset( $args[1] ) ) {
719
				$action = 'get';
720
			} elseif ( 'delete' == $args[0] && isset( $args[1] ) ) {
721
				$action = 'delete';
722
			} elseif ( 'update' == $args[0] && isset( $args[1] ) ) {
723
				$action = 'update';
724
			} else {
725
				$action = 'list';
726
			}
727
		}
728
729
		// Bail if the option isn't found
730
		$option = isset( $args[1] ) ? Jetpack_Options::get_option( $args[1] ) : false;
731 View Code Duplication
		if ( isset( $args[1] ) && ! $option && 'update' !== $args[0] ) {
732
			WP_CLI::error( __( 'Option not found or is empty.  Use "list" to list option names', 'jetpack' ) );
733
		}
734
735
		// Let's print_r the option if it's an array
736
		// Used in the 'get' and 'list' actions
737
		$option = is_array( $option ) ? print_r( $option ) : $option;
738
739
		switch ( $action ) {
740
			case 'get':
741
				WP_CLI::success( "\t" . $option );
742
				break;
743
			case 'delete':
744
				jetpack_cli_are_you_sure( $flagged );
745
746
				Jetpack_Options::delete_option( $args[1] );
747
				/* translators: %s is the option name */
748
				WP_CLI::success( sprintf( __( 'Deleted option: %s', 'jetpack' ), $args[1] ) );
749
				break;
750
			case 'update':
751
				jetpack_cli_are_you_sure( $flagged );
752
753
				// Updating arrays would get pretty tricky...
754
				$value = Jetpack_Options::get_option( $args[1] );
755
				if ( $value && is_array( $value ) ) {
756
					WP_CLI::error( __( 'Sorry, no updating arrays at this time', 'jetpack' ) );
757
				}
758
759
				Jetpack_Options::update_option( $args[1], $args[2] );
760
				/* translators: %1$s is the previous value, %2$s is the new value */
761
				WP_CLI::success( sprintf( _x( 'Updated option: %1$s to "%2$s"', 'Updating an option from "this" to "that".', 'jetpack' ), $args[1], $args[2] ) );
762
				break;
763
			case 'list':
764
				$options_compact     = Jetpack_Options::get_option_names();
765
				$options_non_compact = Jetpack_Options::get_option_names( 'non_compact' );
766
				$options_private     = Jetpack_Options::get_option_names( 'private' );
767
				$options             = array_merge( $options_compact, $options_non_compact, $options_private );
768
769
				// Table headers
770
				WP_CLI::line( "\t" . str_pad( __( 'Option', 'jetpack' ), 30 ) . __( 'Value', 'jetpack' ) );
771
772
				// List out the options and their values
773
				// Tell them if the value is empty or not
774
				// Tell them if it's an array
775
				foreach ( $options as $option ) {
776
					$value = Jetpack_Options::get_option( $option );
777
					if ( ! $value ) {
778
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Empty' );
779
						continue;
780
					}
781
782
					if ( ! is_array( $value ) ) {
783
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . $value );
784
					} elseif ( is_array( $value ) ) {
785
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Array - Use "get <option>" to read option array.' );
786
					}
787
				}
788
				$option_text = '{' . _x( 'option', 'a variable command that a user can write, provided in the printed instructions', 'jetpack' ) . '}';
789
				$value_text  = '{' . _x( 'value', 'the value that they want to update the option to', 'jetpack' ) . '}';
790
791
				WP_CLI::success(
792
					_x( "Above are your options. You may 'get', 'delete', and 'update' them.", "'get', 'delete', and 'update' are commands - do not translate.", 'jetpack' ) . "\n" .
793
					str_pad( 'wp jetpack options get', 26 ) . $option_text . "\n" .
794
					str_pad( 'wp jetpack options delete', 26 ) . $option_text . "\n" .
795
					str_pad( 'wp jetpack options update', 26 ) . "$option_text $value_text" . "\n" .
796
					_x( "Type 'wp jetpack options' for more info.", "'wp jetpack options' is a command - do not translate.", 'jetpack' ) . "\n"
797
				);
798
				break;
799
		}
800
	}
801
802
	/**
803
	 * Get the status of or start a new Jetpack sync.
804
	 *
805
	 * ## OPTIONS
806
	 *
807
	 * status   : Print the current sync status
808
	 * settings : Prints the current sync settings
809
	 * start    : Start a full sync from this site to WordPress.com
810
	 * enable   : Enables sync on the site
811
	 * disable  : Disable sync on a site
812
	 * reset    : Disables sync and Resets the sync queues on a site
813
	 *
814
	 * ## EXAMPLES
815
	 *
816
	 * wp jetpack sync status
817
	 * wp jetpack sync settings
818
	 * wp jetpack sync start --modules=functions --sync_wait_time=5
819
	 * wp jetpack sync enable
820
	 * wp jetpack sync disable
821
	 * wp jetpack sync reset
822
	 * wp jetpack sync reset --queue=full or regular
823
	 *
824
	 * @synopsis <status|start> [--<field>=<value>]
825
	 */
826
	public function sync( $args, $assoc_args ) {
827
828
		$action = isset( $args[0] ) ? $args[0] : 'status';
829
830
		switch ( $action ) {
831
			case 'status':
832
				$status     = Actions::get_sync_status();
833
				$collection = array();
834
				foreach ( $status as $key => $item ) {
835
					$collection[] = array(
836
						'option' => $key,
837
						'value'  => is_scalar( $item ) ? $item : json_encode( $item ),
838
					);
839
				}
840
				WP_CLI::log( __( 'Sync Status:', 'jetpack' ) );
841
				WP_CLI\Utils\format_items( 'table', $collection, array( 'option', 'value' ) );
842
				break;
843
			case 'settings':
844
				WP_CLI::log( __( 'Sync Settings:', 'jetpack' ) );
845
				foreach ( Settings::get_settings() as $setting => $item ) {
846
					$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...
847
						'setting' => $setting,
848
						'value'   => is_scalar( $item ) ? $item : json_encode( $item ),
849
					);
850
				}
851
				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...
852
853
			case 'disable':
854
				// Don't set it via the Settings since that also resets the queues.
855
				update_option( 'jetpack_sync_settings_disable', 1 );
856
				/* translators: %s is the site URL */
857
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s', 'jetpack' ), get_site_url() ) );
858
				break;
859
			case 'enable':
860
				Settings::update_settings( array( 'disable' => 0 ) );
861
				/* translators: %s is the site URL */
862
				WP_CLI::log( sprintf( __( 'Sync Enabled on %s', 'jetpack' ), get_site_url() ) );
863
				break;
864
			case 'reset':
865
				// Don't set it via the Settings since that also resets the queues.
866
				update_option( 'jetpack_sync_settings_disable', 1 );
867
868
				/* translators: %s is the site URL */
869
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s. Use `wp jetpack sync enable` to enable syncing again.', 'jetpack' ), get_site_url() ) );
870
				$listener = Listener::get_instance();
871
				if ( empty( $assoc_args['queue'] ) ) {
872
					$listener->get_sync_queue()->reset();
873
					$listener->get_full_sync_queue()->reset();
874
					/* translators: %s is the site URL */
875
					WP_CLI::log( sprintf( __( 'Reset Full Sync and Regular Queues Queue on %s', 'jetpack' ), get_site_url() ) );
876
					break;
877
				}
878
879
				if ( ! empty( $assoc_args['queue'] ) ) {
880
					switch ( $assoc_args['queue'] ) {
881 View Code Duplication
						case 'regular':
882
							$listener->get_sync_queue()->reset();
883
							/* translators: %s is the site URL */
884
							WP_CLI::log( sprintf( __( 'Reset Regular Sync Queue on %s', 'jetpack' ), get_site_url() ) );
885
							break;
886 View Code Duplication
						case 'full':
887
							$listener->get_full_sync_queue()->reset();
888
							/* translators: %s is the site URL */
889
							WP_CLI::log( sprintf( __( 'Reset Full Sync Queue on %s', 'jetpack' ), get_site_url() ) );
890
							break;
891
						default:
892
							WP_CLI::error( __( 'Please specify what type of queue do you want to reset: `full` or `regular`.', 'jetpack' ) );
893
							break;
894
					}
895
				}
896
897
				break;
898
			case 'start':
899
				if ( ! Actions::sync_allowed() ) {
900
					if ( ! Settings::get_setting( 'disable' ) ) {
901
						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' ) );
902
						return;
903
					}
904
					if ( doing_action( 'jetpack_user_authorized' ) || Jetpack::is_active() ) {
905
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. Jetpack is not connected.', 'jetpack' ) );
906
						return;
907
					}
908
909
					$status = new Status();
910
911
					if ( $status->is_offline_mode() ) {
912
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in offline mode.', 'jetpack' ) );
913
						return;
914
					}
915
					if ( $status->is_staging_site() ) {
916
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in staging mode.', 'jetpack' ) );
917
						return;
918
					}
919
				}
920
				// Get the original settings so that we can restore them later
921
				$original_settings = Settings::get_settings();
922
923
				// Initialize sync settigns so we can sync as quickly as possible
924
				$sync_settings = wp_parse_args(
925
					array_intersect_key( $assoc_args, Settings::$valid_settings ),
926
					array(
0 ignored issues
show
Documentation introduced by
array('sync_wait_time' =...on' => HOUR_IN_SECONDS) is of type array<string,integer,{"s...d_duration":"integer"}>, 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...
927
						'sync_wait_time'           => 0,
928
						'enqueue_wait_time'        => 0,
929
						'queue_max_writes_sec'     => 10000,
930
						'max_queue_size_full_sync' => 100000,
931
						'full_sync_send_duration'  => HOUR_IN_SECONDS,
932
					)
933
				);
934
				Settings::update_settings( $sync_settings );
0 ignored issues
show
Bug introduced by
It seems like $sync_settings defined by wp_parse_args(array_inte...n' => HOUR_IN_SECONDS)) on line 924 can also be of type null; however, Automattic\Jetpack\Sync\...ings::update_settings() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
935
936
				// Convert comma-delimited string of modules to an array
937 View Code Duplication
				if ( ! empty( $assoc_args['modules'] ) ) {
938
					$modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
939
940
					// Convert the array so that the keys are the module name and the value is true to indicate
941
					// that we want to sync the module
942
					$modules = array_map( '__return_true', array_flip( $modules ) );
943
				}
944
945 View Code Duplication
				foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
946
					if (
947
						'users' === $module_name &&
948
						isset( $assoc_args[ $module_name ] ) &&
949
						'initial' === $assoc_args[ $module_name ]
950
					) {
951
						$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...
952
					} elseif ( isset( $assoc_args[ $module_name ] ) ) {
953
						$ids = explode( ',', $assoc_args[ $module_name ] );
954
						if ( count( $ids ) > 0 ) {
955
							$modules[ $module_name ] = $ids;
956
						}
957
					}
958
				}
959
960
				if ( empty( $modules ) ) {
961
					$modules = null;
962
				}
963
964
				// Kick off a full sync
965
				if ( Actions::do_full_sync( $modules ) ) {
966 View Code Duplication
					if ( $modules ) {
967
						/* translators: %s is a comma separated list of Jetpack modules */
968
						WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
969
					} else {
970
						WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
971
					}
972 View Code Duplication
				} else {
973
974
					// Reset sync settings to original.
975
					Settings::update_settings( $original_settings );
976
977
					if ( $modules ) {
978
						/* translators: %s is a comma separated list of Jetpack modules */
979
						WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
980
					} else {
981
						WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
982
					}
983
				}
984
985
				// Keep sending to WPCOM until there's nothing to send
986
				$i = 1;
987
				do {
988
					$result = Actions::$sender->do_full_sync();
989
					if ( is_wp_error( $result ) ) {
990
						$queue_empty_error = ( 'empty_queue_full_sync' == $result->get_error_code() );
991
						if ( ! $queue_empty_error || ( $queue_empty_error && ( 1 == $i ) ) ) {
992
							/* translators: %s is an error code  */
993
							WP_CLI::error( sprintf( __( 'Sync errored with code: %s', 'jetpack' ), $result->get_error_code() ) );
994
						}
995
					} else {
996
						if ( 1 == $i ) {
997
							WP_CLI::log( __( 'Sent data to WordPress.com', 'jetpack' ) );
998
						} else {
999
							WP_CLI::log( __( 'Sent more data to WordPress.com', 'jetpack' ) );
1000
						}
1001
1002
						// Immediate Full Sync does not wait for WP.com to process data so we need to enforce a wait.
1003
						if ( false !== strpos( get_class( Modules::get_module( 'full-sync' ) ), 'Full_Sync_Immediately' ) ) {
1004
							sleep( 15 );
1005
						}
1006
					}
1007
					$i++;
1008
				} while ( $result && ! is_wp_error( $result ) );
1009
1010
				// Reset sync settings to original.
1011
				Settings::update_settings( $original_settings );
1012
1013
				WP_CLI::success( __( 'Finished syncing to WordPress.com', 'jetpack' ) );
1014
				break;
1015
		}
1016
	}
1017
1018
	/**
1019
	 * List the contents of a specific Jetpack sync queue.
1020
	 *
1021
	 * ## OPTIONS
1022
	 *
1023
	 * peek : List the 100 front-most items on the queue.
1024
	 *
1025
	 * ## EXAMPLES
1026
	 *
1027
	 * wp jetpack sync_queue full_sync peek
1028
	 *
1029
	 * @synopsis <incremental|full_sync> <peek>
1030
	 */
1031
	public function sync_queue( $args, $assoc_args ) {
1032
		if ( ! Actions::sync_allowed() ) {
1033
			WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
1034
		}
1035
1036
		$queue_name = isset( $args[0] ) ? $args[0] : 'sync';
1037
		$action     = isset( $args[1] ) ? $args[1] : 'peek';
1038
1039
		// We map the queue name that way we can support more friendly queue names in the commands, but still use
1040
		// the queue name that the code expects.
1041
		$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...
1042
			'incremental' => 'sync',
1043
			'full'        => 'full_sync',
1044
		);
1045
		$mapped_queue_name = isset( $queue_name_map[ $queue_name ] ) ? $queue_name_map[ $queue_name ] : $queue_name;
1046
1047
		switch ( $action ) {
1048
			case 'peek':
1049
				$queue = new Queue( $mapped_queue_name );
1050
				$items = $queue->peek( 100 );
1051
1052
				if ( empty( $items ) ) {
1053
					/* translators: %s is the name of the queue, either 'incremental' or 'full' */
1054
					WP_CLI::log( sprintf( __( 'Nothing is in the queue: %s', 'jetpack' ), $queue_name ) );
1055
				} else {
1056
					$collection = array();
1057
					foreach ( $items as $item ) {
1058
						$collection[] = array(
1059
							'action'          => $item[0],
1060
							'args'            => json_encode( $item[1] ),
1061
							'current_user_id' => $item[2],
1062
							'microtime'       => $item[3],
1063
							'importing'       => (string) $item[4],
1064
						);
1065
					}
1066
					WP_CLI\Utils\format_items(
1067
						'table',
1068
						$collection,
1069
						array(
1070
							'action',
1071
							'args',
1072
							'current_user_id',
1073
							'microtime',
1074
							'importing',
1075
						)
1076
					);
1077
				}
1078
				break;
1079
		}
1080
	}
1081
1082
	/**
1083
	 * Cancel's the current Jetpack plan granted by this partner, if applicable
1084
	 *
1085
	 * Returns success or error JSON
1086
	 *
1087
	 * <token_json>
1088
	 * : JSON blob of WPCOM API token
1089
	 *  [--partner_tracking_id=<partner_tracking_id>]
1090
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1091
	 *
1092
	 *  * @synopsis <token_json> [--partner_tracking_id=<partner_tracking_id>]
1093
	 */
1094
	public function partner_cancel( $args, $named_args ) {
1095
		list( $token_json ) = $args;
1096
1097 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1098
			/* translators: %s is the invalid JSON string */
1099
			$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...
1100
		}
1101
1102
		if ( isset( $token->error ) ) {
1103
			$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...
1104
		}
1105
1106
		if ( ! isset( $token->access_token ) ) {
1107
			$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...
1108
		}
1109
1110
		if ( Jetpack::validate_sync_error_idc_option() ) {
1111
			$this->partner_provision_error(
1112
				new WP_Error(
1113
					'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...
1114
					esc_html__( 'Can not cancel a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
1115
				)
1116
			);
1117
		}
1118
1119
		$site_identifier = Jetpack_Options::get_option( 'id' );
1120
1121
		if ( ! $site_identifier ) {
1122
			$status          = new Status();
1123
			$site_identifier = $status->get_site_suffix();
1124
		}
1125
1126
		$request = array(
1127
			'headers' => array(
1128
				'Authorization' => 'Bearer ' . $token->access_token,
1129
				'Host'          => 'public-api.wordpress.com',
1130
			),
1131
			'timeout' => 60,
1132
			'method'  => 'POST',
1133
		);
1134
1135
		$url = sprintf( '%s/rest/v1.3/jpphp/%s/partner-cancel', $this->get_api_host(), $site_identifier );
1136 View Code Duplication
		if ( ! empty( $named_args ) && ! empty( $named_args['partner_tracking_id'] ) ) {
1137
			$url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
1138
		}
1139
1140
		$result = Client::_wp_remote_request( $url, $request );
1141
1142
		Jetpack_Options::delete_option( 'onboarding' );
1143
1144
		if ( is_wp_error( $result ) ) {
1145
			$this->partner_provision_error( $result );
1146
		}
1147
1148
		WP_CLI::log( wp_remote_retrieve_body( $result ) );
1149
	}
1150
1151
	/**
1152
	 * Provision a site using a Jetpack Partner license
1153
	 *
1154
	 * Returns JSON blob
1155
	 *
1156
	 * ## OPTIONS
1157
	 *
1158
	 * <token_json>
1159
	 * : JSON blob of WPCOM API token
1160
	 * [--plan=<plan_name>]
1161
	 * : Slug of the requested plan, e.g. premium
1162
	 * [--wpcom_user_id=<user_id>]
1163
	 * : WordPress.com ID of user to connect as (must be whitelisted against partner key)
1164
	 * [--wpcom_user_email=<wpcom_user_email>]
1165
	 * : Override the email we send to WordPress.com for registration
1166
	 * [--onboarding=<onboarding>]
1167
	 * : Guide the user through an onboarding wizard
1168
	 * [--force_register=<register>]
1169
	 * : Whether to force a site to register
1170
	 * [--force_connect=<force_connect>]
1171
	 * : Force JPS to not reuse existing credentials
1172
	 * [--home_url=<home_url>]
1173
	 * : Overrides the home option via the home_url filter, or the WP_HOME constant
1174
	 * [--site_url=<site_url>]
1175
	 * : Overrides the siteurl option via the site_url filter, or the WP_SITEURL constant
1176
	 * [--partner_tracking_id=<partner_tracking_id>]
1177
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1178
	 *
1179
	 * ## EXAMPLES
1180
	 *
1181
	 *     $ wp jetpack partner_provision '{ some: "json" }' premium 1
1182
	 *     { success: true }
1183
	 *
1184
	 * @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>]
1185
	 */
1186
	public function partner_provision( $args, $named_args ) {
1187
		list( $token_json ) = $args;
1188
1189 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1190
			/* translators: %s is the invalid JSON string */
1191
			$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...
1192
		}
1193
1194
		if ( isset( $token->error ) ) {
1195
			$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...
1196
				? $token->message
1197
				: '';
1198
			$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...
1199
		}
1200
1201
		if ( ! isset( $token->access_token ) ) {
1202
			$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...
1203
		}
1204
1205
		require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
1206
1207
		$body_json = Jetpack_Provision::partner_provision( $token->access_token, $named_args );
1208
1209
		if ( is_wp_error( $body_json ) ) {
1210
			error_log(
1211
				json_encode(
1212
					array(
1213
						'success'       => false,
1214
						'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...
1215
						'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...
1216
					)
1217
				)
1218
			);
1219
			exit( 1 );
1220
		}
1221
1222
		WP_CLI::log( json_encode( $body_json ) );
1223
	}
1224
1225
	/**
1226
	 * Manages your Jetpack sitemap
1227
	 *
1228
	 * ## OPTIONS
1229
	 *
1230
	 * rebuild : Rebuild all sitemaps
1231
	 * --purge : if set, will remove all existing sitemap data before rebuilding
1232
	 *
1233
	 * ## EXAMPLES
1234
	 *
1235
	 * wp jetpack sitemap rebuild
1236
	 *
1237
	 * @subcommand sitemap
1238
	 * @synopsis <rebuild> [--purge]
1239
	 */
1240
	public function sitemap( $args, $assoc_args ) {
1241
		if ( ! Jetpack::is_active() ) {
1242
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1243
		}
1244
		if ( ! Jetpack::is_module_active( 'sitemaps' ) ) {
1245
			WP_CLI::error( __( 'Jetpack Sitemaps module is not currently active. Activate it first if you want to work with sitemaps.', 'jetpack' ) );
1246
		}
1247
		if ( ! class_exists( 'Jetpack_Sitemap_Builder' ) ) {
1248
			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' ) );
1249
		}
1250
1251
		if ( isset( $assoc_args['purge'] ) && $assoc_args['purge'] ) {
1252
			$librarian = new Jetpack_Sitemap_Librarian();
1253
			$librarian->delete_all_stored_sitemap_data();
1254
		}
1255
1256
		$sitemap_builder = new Jetpack_Sitemap_Builder();
1257
		$sitemap_builder->update_sitemap();
1258
	}
1259
1260
	/**
1261
	 * Allows authorizing a user via the command line and will activate
1262
	 *
1263
	 * ## EXAMPLES
1264
	 *
1265
	 * wp jetpack authorize_user --token=123456789abcdef
1266
	 *
1267
	 * @synopsis --token=<value>
1268
	 */
1269
	public function authorize_user( $args, $named_args ) {
1270
		if ( ! is_user_logged_in() ) {
1271
			WP_CLI::error( __( 'Please select a user to authorize via the --user global argument.', 'jetpack' ) );
1272
		}
1273
1274
		if ( empty( $named_args['token'] ) ) {
1275
			WP_CLI::error( __( 'A non-empty token argument must be passed.', 'jetpack' ) );
1276
		}
1277
1278
		$is_master_user  = ! Jetpack::is_active();
1279
		$current_user_id = get_current_user_id();
1280
1281
		Connection_Utils::update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_master_user );
1282
1283
		WP_CLI::log( wp_json_encode( $named_args ) );
1284
1285
		if ( $is_master_user ) {
1286
			/**
1287
			 * Auto-enable SSO module for new Jetpack Start connections
1288
			*
1289
			* @since 5.0.0
1290
			*
1291
			* @param bool $enable_sso Whether to enable the SSO module. Default to true.
1292
			*/
1293
			$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1294
			Jetpack::handle_post_authorization_actions( $enable_sso, false );
1295
1296
			/* translators: %d is a user ID */
1297
			WP_CLI::success( sprintf( __( 'Authorized %d and activated default modules.', 'jetpack' ), $current_user_id ) );
1298
		} else {
1299
			/* translators: %d is a user ID */
1300
			WP_CLI::success( sprintf( __( 'Authorized %d.', 'jetpack' ), $current_user_id ) );
1301
		}
1302
	}
1303
1304
	/**
1305
	 * Allows calling a WordPress.com API endpoint using the current blog's token.
1306
	 *
1307
	 * ## OPTIONS
1308
	 * --resource=<resource>
1309
	 * : The resource to call with the current blog's token, where `%d` represents the current blog's ID.
1310
	 *
1311
	 * [--api_version=<api_version>]
1312
	 * : The API version to query against.
1313
	 *
1314
	 * [--base_api_path=<base_api_path>]
1315
	 * : The base API path to query.
1316
	 * ---
1317
	 * default: rest
1318
	 * ---
1319
	 *
1320
	 * [--body=<body>]
1321
	 * : A JSON encoded string representing arguments to send in the body.
1322
	 *
1323
	 * [--field=<value>]
1324
	 * : Any number of arguments that should be passed to the resource.
1325
	 *
1326
	 * [--pretty]
1327
	 * : Will pretty print the results of a successful API call.
1328
	 *
1329
	 * [--strip-success]
1330
	 * : Will remove the green success label from successful API calls.
1331
	 *
1332
	 * ## EXAMPLES
1333
	 *
1334
	 * wp jetpack call_api --resource='/sites/%d'
1335
	 */
1336
	public function call_api( $args, $named_args ) {
1337
		if ( ! Jetpack::is_active() ) {
1338
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1339
		}
1340
1341
		$consumed_args = array(
1342
			'resource',
1343
			'api_version',
1344
			'base_api_path',
1345
			'body',
1346
			'pretty',
1347
		);
1348
1349
		// Get args that should be passed to resource.
1350
		$other_args = array_diff_key( $named_args, array_flip( $consumed_args ) );
1351
1352
		$decoded_body = ! empty( $named_args['body'] )
1353
			? json_decode( $named_args['body'], true )
1354
			: false;
1355
1356
		$resource_url = ( false === strpos( $named_args['resource'], '%d' ) )
1357
			? $named_args['resource']
1358
			: sprintf( $named_args['resource'], Jetpack_Options::get_option( 'id' ) );
1359
1360
		$response = Client::wpcom_json_api_request_as_blog(
1361
			$resource_url,
1362
			empty( $named_args['api_version'] ) ? Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
1363
			$other_args,
1364
			empty( $decoded_body ) ? null : $decoded_body,
1365
			empty( $named_args['base_api_path'] ) ? 'rest' : $named_args['base_api_path']
1366
		);
1367
1368
		if ( is_wp_error( $response ) ) {
1369
			WP_CLI::error(
1370
				sprintf(
1371
					/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
1372
					__( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
1373
					$resource_url,
1374
					$response->get_error_code(),
1375
					$response->get_error_message()
1376
				)
1377
			);
1378
		}
1379
1380
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1381
			WP_CLI::error(
1382
				sprintf(
1383
					/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
1384
					__( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
1385
					$resource_url,
1386
					wp_remote_retrieve_response_code( $response )
1387
				)
1388
			);
1389
		}
1390
1391
		$output = wp_remote_retrieve_body( $response );
1392
		if ( isset( $named_args['pretty'] ) ) {
1393
			$decoded_output = json_decode( $output );
1394
			if ( $decoded_output ) {
1395
				$output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
1396
			}
1397
		}
1398
1399
		if ( isset( $named_args['strip-success'] ) ) {
1400
			WP_CLI::log( $output );
1401
			WP_CLI::halt( 0 );
1402
		}
1403
1404
		WP_CLI::success( $output );
1405
	}
1406
1407
	/**
1408
	 * Allows uploading SSH Credentials to the current site for backups, restores, and security scanning.
1409
	 *
1410
	 * ## OPTIONS
1411
	 *
1412
	 * [--host=<host>]
1413
	 * : The SSH server's address.
1414
	 *
1415
	 * [--ssh-user=<user>]
1416
	 * : The username to use to log in to the SSH server.
1417
	 *
1418
	 * [--pass=<pass>]
1419
	 * : The password used to log in, if using a password. (optional)
1420
	 *
1421
	 * [--kpri=<kpri>]
1422
	 * : The private key used to log in, if using a private key. (optional)
1423
	 *
1424
	 * [--pretty]
1425
	 * : Will pretty print the results of a successful API call. (optional)
1426
	 *
1427
	 * [--strip-success]
1428
	 * : Will remove the green success label from successful API calls. (optional)
1429
	 *
1430
	 * ## EXAMPLES
1431
	 *
1432
	 * wp jetpack upload_ssh_creds --host=example.com --ssh-user=example --pass=password
1433
	 * wp jetpack updload_ssh_creds --host=example.com --ssh-user=example --kpri=key
1434
	 */
1435
	public function upload_ssh_creds( $args, $named_args ) {
1436
		if ( ! Jetpack::is_active() ) {
1437
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1438
		}
1439
1440
		$required_args = array(
1441
			'host',
1442
			'ssh-user',
1443
		);
1444
1445
		foreach ( $required_args as $arg ) {
1446
			if ( empty( $named_args[ $arg ] ) ) {
1447
				WP_CLI::error(
1448
					sprintf(
1449
						/* translators: %s is a slug, such as 'host'. */
1450
						__( '`%s` cannot be empty.', 'jetpack' ),
1451
						$arg
1452
					)
1453
				);
1454
			}
1455
		}
1456
1457
		if ( empty( $named_args['pass'] ) && empty( $named_args['kpri'] ) ) {
1458
			WP_CLI::error( __( 'Both `pass` and `kpri` fields cannot be blank.', 'jetpack' ) );
1459
		}
1460
1461
		$values = array(
1462
			'credentials' => array(
1463
				'site_url' => get_site_url(),
1464
				'abspath'  => ABSPATH,
1465
				'protocol' => 'ssh',
1466
				'port'     => 22,
1467
				'role'     => 'main',
1468
				'host'     => $named_args['host'],
1469
				'user'     => $named_args['ssh-user'],
1470
				'pass'     => empty( $named_args['pass'] ) ? '' : $named_args['pass'],
1471
				'kpri'     => empty( $named_args['kpri'] ) ? '' : $named_args['kpri'],
1472
			),
1473
		);
1474
1475
		$named_args = wp_parse_args(
1476
			array(
1477
				'resource'    => '/activity-log/%d/update-credentials',
1478
				'method'      => 'POST',
1479
				'api_version' => '1.1',
1480
				'body'        => wp_json_encode( $values ),
1481
				'timeout'     => 30,
1482
			),
1483
			$named_args
1484
		);
1485
1486
		self::call_api( $args, $named_args );
1487
	}
1488
1489
	/**
1490
	 * API wrapper for getting stats from the WordPress.com API for the current site.
1491
	 *
1492
	 * ## OPTIONS
1493
	 *
1494
	 * [--quantity=<quantity>]
1495
	 * : The number of units to include.
1496
	 * ---
1497
	 * default: 30
1498
	 * ---
1499
	 *
1500
	 * [--period=<period>]
1501
	 * : The unit of time to query stats for.
1502
	 * ---
1503
	 * default: day
1504
	 * options:
1505
	 *  - day
1506
	 *  - week
1507
	 *  - month
1508
	 *  - year
1509
	 * ---
1510
	 *
1511
	 * [--date=<date>]
1512
	 * : The latest date to return stats for. Ex. - 2018-01-01.
1513
	 *
1514
	 * [--pretty]
1515
	 * : Will pretty print the results of a successful API call.
1516
	 *
1517
	 * [--strip-success]
1518
	 * : Will remove the green success label from successful API calls.
1519
	 *
1520
	 * ## EXAMPLES
1521
	 *
1522
	 * wp jetpack get_stats
1523
	 */
1524
	public function get_stats( $args, $named_args ) {
1525
		$selected_args = array_intersect_key(
1526
			$named_args,
1527
			array_flip(
1528
				array(
1529
					'quantity',
1530
					'date',
1531
				)
1532
			)
1533
		);
1534
1535
		// The API expects unit, but period seems to be more correct.
1536
		$selected_args['unit'] = $named_args['period'];
1537
1538
		$command = sprintf(
1539
			'jetpack call_api --resource=/sites/%d/stats/%s',
1540
			Jetpack_Options::get_option( 'id' ),
1541
			add_query_arg( $selected_args, 'visits' )
1542
		);
1543
1544
		if ( isset( $named_args['pretty'] ) ) {
1545
			$command .= ' --pretty';
1546
		}
1547
1548
		if ( isset( $named_args['strip-success'] ) ) {
1549
			$command .= ' --strip-success';
1550
		}
1551
1552
		WP_CLI::runcommand(
1553
			$command,
1554
			array(
1555
				'launch' => false, // Use the current process.
1556
			)
1557
		);
1558
	}
1559
1560
	/**
1561
	 * Allows management of publicize connections.
1562
	 *
1563
	 * ## OPTIONS
1564
	 *
1565
	 * <list|disconnect>
1566
	 * : The action to perform.
1567
	 * ---
1568
	 * options:
1569
	 *   - list
1570
	 *   - disconnect
1571
	 * ---
1572
	 *
1573
	 * [<identifier>]
1574
	 * : The connection ID or service to perform an action on.
1575
	 *
1576
	 * [--format=<format>]
1577
	 * : Allows overriding the output of the command when listing connections.
1578
	 * ---
1579
	 * default: table
1580
	 * options:
1581
	 *   - table
1582
	 *   - json
1583
	 *   - csv
1584
	 *   - yaml
1585
	 *   - ids
1586
	 *   - count
1587
	 * ---
1588
	 *
1589
	 * ## EXAMPLES
1590
	 *
1591
	 *     # List all publicize connections.
1592
	 *     $ wp jetpack publicize list
1593
	 *
1594
	 *     # List publicize connections for a given service.
1595
	 *     $ wp jetpack publicize list twitter
1596
	 *
1597
	 *     # List all publicize connections for a given user.
1598
	 *     $ wp --user=1 jetpack publicize list
1599
	 *
1600
	 *     # List all publicize connections for a given user and service.
1601
	 *     $ wp --user=1 jetpack publicize list twitter
1602
	 *
1603
	 *     # Display details for a given connection.
1604
	 *     $ wp jetpack publicize list 123456
1605
	 *
1606
	 *     # Diconnection a given connection.
1607
	 *     $ wp jetpack publicize disconnect 123456
1608
	 *
1609
	 *     # Disconnect all connections.
1610
	 *     $ wp jetpack publicize disconnect all
1611
	 *
1612
	 *     # Disconnect all connections for a given service.
1613
	 *     $ wp jetpack publicize disconnect twitter
1614
	 */
1615
	public function publicize( $args, $named_args ) {
1616
		if ( ! Jetpack::is_active() ) {
1617
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1618
		}
1619
1620
		if ( ! Jetpack::is_module_active( 'publicize' ) ) {
1621
			WP_CLI::error( __( 'The publicize module is not active.', 'jetpack' ) );
1622
		}
1623
1624
		if ( ( new Status() )->is_offline_mode() ) {
1625
			if (
1626
				! defined( 'JETPACK_DEV_DEBUG' ) &&
1627
				! has_filter( 'jetpack_development_mode' ) &&
1628
				! has_filter( 'jetpack_offline_mode' ) &&
1629
				false === strpos( site_url(), '.' )
1630
			) {
1631
				WP_CLI::error( __( "Jetpack is current in offline mode because the site url does not contain a '.', which often occurs when dynamically setting the WP_SITEURL constant. While in offline mode, the publicize module will not load.", 'jetpack' ) );
1632
			}
1633
1634
			WP_CLI::error( __( 'Jetpack is currently in offline mode, so the publicize module will not load.', 'jetpack' ) );
1635
		}
1636
1637
		if ( ! class_exists( 'Publicize' ) ) {
1638
			WP_CLI::error( __( 'The publicize module is not loaded.', 'jetpack' ) );
1639
		}
1640
1641
		$action        = $args[0];
1642
		$publicize     = new Publicize();
1643
		$identifier    = ! empty( $args[1] ) ? $args[1] : false;
1644
		$services      = array_keys( $publicize->get_services() );
1645
		$id_is_service = in_array( $identifier, $services, true );
1646
1647
		switch ( $action ) {
1648
			case 'list':
1649
				$connections_to_return = array();
1650
1651
				// For the CLI command, let's return all connections when a user isn't specified. This
1652
				// differs from the logic in the Publicize class.
1653
				$option_connections = is_user_logged_in()
1654
					? (array) $publicize->get_all_connections_for_user()
1655
					: (array) $publicize->get_all_connections();
1656
1657
				foreach ( $option_connections as $service_name => $connections ) {
1658
					foreach ( (array) $connections as $id => $connection ) {
1659
						$connection['id']        = $id;
1660
						$connection['service']   = $service_name;
1661
						$connections_to_return[] = $connection;
1662
					}
1663
				}
1664
1665
				if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
1666
					$temp_connections      = $connections_to_return;
1667
					$connections_to_return = array();
1668
1669
					foreach ( $temp_connections as $connection ) {
1670
						if ( $identifier === $connection['service'] ) {
1671
							$connections_to_return[] = $connection;
1672
						}
1673
					}
1674
				}
1675
1676
				if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
1677
					$connections_to_return = wp_list_filter( $connections_to_return, array( 'id' => $identifier ) );
1678
				}
1679
1680
				$expected_keys = array(
1681
					'id',
1682
					'service',
1683
					'user_id',
1684
					'provider',
1685
					'issued',
1686
					'expires',
1687
					'external_id',
1688
					'external_name',
1689
					'external_display',
1690
					'type',
1691
					'connection_data',
1692
				);
1693
1694
				// Somehow, a test site ended up in a state where $connections_to_return looked like:
1695
				// array( array( array( 'id' => 0, 'service' => 0 ) ) ) // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1696
				// This caused the CLI command to error when running WP_CLI\Utils\format_items() below. So
1697
				// to minimize future issues, this nested loop will remove any connections that don't contain
1698
				// any keys that we expect.
1699
				foreach ( (array) $connections_to_return as $connection_key => $connection ) {
1700
					foreach ( $expected_keys as $expected_key ) {
1701
						if ( ! isset( $connection[ $expected_key ] ) ) {
1702
							unset( $connections_to_return[ $connection_key ] );
1703
							continue;
1704
						}
1705
					}
1706
				}
1707
1708
				if ( empty( $connections_to_return ) ) {
1709
					return false;
1710
				}
1711
1712
				WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
1713
				break; // list.
1714
			case 'disconnect':
1715
				if ( ! $identifier ) {
1716
					WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
1717
				}
1718
1719
				// If the connection ID is 'all' then delete all connections. If the connection ID
1720
				// matches a service, delete all connections for that service.
1721
				if ( 'all' === $identifier || $id_is_service ) {
1722
					if ( 'all' === $identifier ) {
1723
						WP_CLI::log( __( "You're about to delete all publicize connections.", 'jetpack' ) );
1724
					} else {
1725
						/* translators: %s is a lowercase string for a social network. */
1726
						WP_CLI::log( sprintf( __( "You're about to delete all publicize connections to %s.", 'jetpack' ), $identifier ) );
1727
					}
1728
1729
					jetpack_cli_are_you_sure();
1730
1731
					$connections = array();
1732
					$service     = $identifier;
1733
1734
					$option_connections = is_user_logged_in()
1735
						? (array) $publicize->get_all_connections_for_user()
1736
						: (array) $publicize->get_all_connections();
1737
1738
					if ( 'all' === $service ) {
1739
						foreach ( (array) $option_connections as $service_name => $service_connections ) {
1740
							foreach ( $service_connections as $id => $connection ) {
1741
								$connections[ $id ] = $connection;
1742
							}
1743
						}
1744
					} elseif ( ! empty( $option_connections[ $service ] ) ) {
1745
						$connections = $option_connections[ $service ];
1746
					}
1747
1748
					if ( ! empty( $connections ) ) {
1749
						$count    = count( $connections );
1750
						$progress = \WP_CLI\Utils\make_progress_bar(
1751
							/* translators: %s is a lowercase string for a social network. */
1752
							sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
1753
							$count
1754
						);
1755
1756
						foreach ( $connections as $id => $connection ) {
1757
							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...
1758
								WP_CLI::error(
1759
									sprintf(
1760
										/* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
1761
										__( 'Publicize connection %d could not be disconnected', 'jetpack' ),
1762
										$id
1763
									)
1764
								);
1765
							}
1766
1767
							$progress->tick();
1768
						}
1769
1770
						$progress->finish();
1771
1772
						if ( 'all' === $service ) {
1773
							WP_CLI::success( __( 'All publicize connections were successfully disconnected.', 'jetpack' ) );
1774
						} else {
1775
							/* translators: %s is a lowercase string for a social network. */
1776
							WP_CLI::success( __( 'All publicize connections to %s were successfully disconnected.', 'jetpack' ), $service );
1777
						}
1778
					}
1779
				} else {
1780
					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...
1781
						/* translators: %d is a numeric ID. Example: 1234. */
1782
						WP_CLI::success( sprintf( __( 'Publicize connection %d has been disconnected.', 'jetpack' ), $identifier ) );
1783
					} else {
1784
						/* translators: %d is a numeric ID. Example: 1234. */
1785
						WP_CLI::error( sprintf( __( 'Publicize connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
1786
					}
1787
				}
1788
				break; // disconnect.
1789
		}
1790
	}
1791
1792
	private function get_api_host() {
1793
		$env_api_host = getenv( 'JETPACK_START_API_HOST', true );
1794
		return $env_api_host ? 'https://' . $env_api_host : JETPACK__WPCOM_JSON_API_BASE;
1795
	}
1796
1797
	private function partner_provision_error( $error ) {
1798
		WP_CLI::log(
1799
			json_encode(
1800
				array(
1801
					'success'       => false,
1802
					'error_code'    => $error->get_error_code(),
1803
					'error_message' => $error->get_error_message(),
1804
				)
1805
			)
1806
		);
1807
		exit( 1 );
1808
	}
1809
1810
	/**
1811
	 * Creates the essential files in Jetpack to start building a Gutenberg block or plugin.
1812
	 *
1813
	 * ## TYPES
1814
	 *
1815
	 * 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.
1816
	 *
1817
	 * ## BLOCK TYPE OPTIONS
1818
	 *
1819
	 * The first parameter is the block title and it's not associative. Add it wrapped in quotes.
1820
	 * 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.
1821
	 * --slug: Specific slug to identify the block that overrides the one generated based on the title.
1822
	 * --description: Allows to provide a text description of the block.
1823
	 * --keywords: Provide up to three keywords separated by comma so users can find this block when they search in Gutenberg's inserter.
1824
	 * --variation: Allows to decide whether the block should be a production block, experimental, or beta. Defaults to Beta when arg not provided.
1825
	 *
1826
	 * ## BLOCK TYPE EXAMPLES
1827
	 *
1828
	 * wp jetpack scaffold block "Cool Block"
1829
	 * wp jetpack scaffold block "Amazing Rock" --slug="good-music" --description="Rock the best music on your site"
1830
	 * wp jetpack scaffold block "Jukebox" --keywords="music, audio, media"
1831
	 * wp jetpack scaffold block "Jukebox" --variation="experimental"
1832
	 *
1833
	 * @subcommand scaffold block
1834
	 * @synopsis <type> <title> [--slug] [--description] [--keywords] [--variation]
1835
	 *
1836
	 * @param array $args       Positional parameters, when strings are passed, wrap them in quotes.
1837
	 * @param array $assoc_args Associative parameters like --slug="nice-block".
1838
	 */
1839
	public function scaffold( $args, $assoc_args ) {
1840
		// It's ok not to check if it's set, because otherwise WPCLI exits earlier.
1841
		switch ( $args[0] ) {
1842
			case 'block':
1843
				$this->block( $args, $assoc_args );
1844
				break;
1845
			default:
1846
				/* translators: %s is the subcommand */
1847
				WP_CLI::error( sprintf( esc_html__( 'Invalid subcommand %s.', 'jetpack' ), $args[0] ) . ' 👻' );
1848
				exit( 1 );
1849
		}
1850
	}
1851
1852
	/**
1853
	 * Creates the essential files in Jetpack to build a Gutenberg block.
1854
	 *
1855
	 * @param array $args       Positional parameters. Only one is used, that corresponds to the block title.
1856
	 * @param array $assoc_args Associative parameters defined in the scaffold() method.
1857
	 */
1858
	public function block( $args, $assoc_args ) {
1859 View Code Duplication
		if ( isset( $args[1] ) ) {
1860
			$title = ucwords( $args[1] );
1861
		} else {
1862
			WP_CLI::error( esc_html__( 'The title parameter is required.', 'jetpack' ) . ' 👻' );
1863
			exit( 1 );
1864
		}
1865
1866
		$slug = isset( $assoc_args['slug'] )
1867
			? $assoc_args['slug']
1868
			: sanitize_title( $title );
1869
1870
		$variation_options = array( 'production', 'experimental', 'beta' );
1871
		$variation         = ( isset( $assoc_args['variation'] ) && in_array( $assoc_args['variation'], $variation_options, true ) )
1872
			? $assoc_args['variation']
1873
			: 'beta';
1874
1875
		if ( preg_match( '#^jetpack/#', $slug ) ) {
1876
			$slug = preg_replace( '#^jetpack/#', '', $slug );
1877
		}
1878
1879
		if ( ! preg_match( '/^[a-z][a-z0-9\-]*$/', $slug ) ) {
1880
			WP_CLI::error( esc_html__( 'Invalid block slug. They can contain only lowercase alphanumeric characters or dashes, and start with a letter', 'jetpack' ) . ' 👻' );
1881
		}
1882
1883
		global $wp_filesystem;
1884
		if ( ! WP_Filesystem() ) {
1885
			WP_CLI::error( esc_html__( "Can't write files", 'jetpack' ) . ' 😱' );
1886
		}
1887
1888
		$path = JETPACK__PLUGIN_DIR . "extensions/blocks/$slug";
1889
1890
		if ( $wp_filesystem->exists( $path ) && $wp_filesystem->is_dir( $path ) ) {
1891
			/* translators: %s is path to the conflicting block */
1892
			WP_CLI::error( sprintf( esc_html__( 'Name conflicts with the existing block %s', 'jetpack' ), $path ) . ' ⛔️' );
1893
			exit( 1 );
1894
		}
1895
1896
		$wp_filesystem->mkdir( $path );
1897
1898
		$hasKeywords = isset( $assoc_args['keywords'] );
1899
1900
		$files = array(
1901
			"$path/$slug.php"     => $this->render_block_file(
1902
				'block-register-php',
1903
				array(
1904
					'slug'             => $slug,
1905
					'title'            => $title,
1906
					'underscoredSlug'  => str_replace( '-', '_', $slug ),
1907
					'underscoredTitle' => str_replace( ' ', '_', $title ),
1908
					'jetpackVersion'   => substr( JETPACK__VERSION, 0, strpos( JETPACK__VERSION, '.' ) ) . '.x',
1909
				)
1910
			),
1911
			"$path/index.js"      => $this->render_block_file(
1912
				'block-index-js',
1913
				array(
1914
					'slug'        => $slug,
1915
					'title'       => $title,
1916
					'description' => isset( $assoc_args['description'] )
1917
						? $assoc_args['description']
1918
						: $title,
1919
					'keywords'    => $hasKeywords
1920
					? array_map(
1921
						function( $keyword ) {
1922
								// Construction necessary for Mustache lists
1923
								return array( 'keyword' => trim( $keyword ) );
1924
						},
1925
						explode( ',', $assoc_args['keywords'], 3 )
1926
					)
1927
					: '',
1928
					'hasKeywords' => $hasKeywords,
1929
				)
1930
			),
1931
			"$path/editor.js"     => $this->render_block_file( 'block-editor-js' ),
1932
			"$path/editor.scss"   => $this->render_block_file(
1933
				'block-editor-scss',
1934
				array(
1935
					'slug'  => $slug,
1936
					'title' => $title,
1937
				)
1938
			),
1939
			"$path/edit.js"       => $this->render_block_file(
1940
				'block-edit-js',
1941
				array(
1942
					'title'     => $title,
1943
					'className' => str_replace( ' ', '', ucwords( str_replace( '-', ' ', $slug ) ) ),
1944
				)
1945
			),
1946
			"$path/icon.js"       => $this->render_block_file( 'block-icon-js' ),
1947
			"$path/attributes.js" => $this->render_block_file( 'block-attributes-js' ),
1948
		);
1949
1950
		$files_written = array();
1951
1952
		foreach ( $files as $filename => $contents ) {
1953
			if ( $wp_filesystem->put_contents( $filename, $contents ) ) {
1954
				$files_written[] = $filename;
1955
			} else {
1956
				/* translators: %s is a file name */
1957
				WP_CLI::error( sprintf( esc_html__( 'Error creating %s', 'jetpack' ), $filename ) );
1958
			}
1959
		}
1960
1961
		if ( empty( $files_written ) ) {
1962
			WP_CLI::log( esc_html__( 'No files were created', 'jetpack' ) );
1963
		} else {
1964
			// Load index.json and insert the slug of the new block in its block variation array.
1965
			$block_list_path = JETPACK__PLUGIN_DIR . 'extensions/index.json';
1966
			$block_list      = $wp_filesystem->get_contents( $block_list_path );
1967
			if ( empty( $block_list ) ) {
1968
				/* translators: %s is the path to the file with the block list */
1969
				WP_CLI::error( sprintf( esc_html__( 'Error fetching contents of %s', 'jetpack' ), $block_list_path ) );
1970
			} elseif ( false === stripos( $block_list, $slug ) ) {
1971
				$new_block_list                   = json_decode( $block_list );
1972
				$new_block_list->{ $variation }[] = $slug;
1973
1974
				// Format the JSON to match our coding standards.
1975
				$new_block_list_formatted = wp_json_encode( $new_block_list, JSON_PRETTY_PRINT ) . "\n";
1976
				$new_block_list_formatted = preg_replace_callback(
1977
					// Find all occurrences of multiples of 4 spaces a the start of the line.
1978
					'/^((?:    )+)/m',
1979
					function ( $matches ) {
1980
						// Replace each occurrence of 4 spaces with a tab character.
1981
						return str_repeat( "\t", substr_count( $matches[0], '    ' ) );
1982
					},
1983
					$new_block_list_formatted
1984
				);
1985
1986
				if ( ! $wp_filesystem->put_contents( $block_list_path, $new_block_list_formatted ) ) {
1987
					/* translators: %s is the path to the file with the block list */
1988
					WP_CLI::error( sprintf( esc_html__( 'Error writing new %s', 'jetpack' ), $block_list_path ) );
1989
				}
1990
			}
1991
1992
			if ( 'beta' === $variation || 'experimental' === $variation ) {
1993
				$block_constant = sprintf(
1994
					/* translators: the placeholder is a constant name */
1995
					esc_html__( 'To load the block, add the constant %1$s as true to your wp-config.php file', 'jetpack' ),
1996
					( 'beta' === $variation ? 'JETPACK_BETA_BLOCKS' : 'JETPACK_EXPERIMENTAL_BLOCKS' )
1997
				);
1998
			} else {
1999
				$block_constant = '';
2000
			}
2001
2002
			WP_CLI::success(
2003
				sprintf(
2004
					/* translators: the placeholders are a human readable title, and a series of words separated by dashes */
2005
					esc_html__( 'Successfully created block %1$s with slug %2$s', 'jetpack' ) . ' 🎉' . "\n" .
2006
					"--------------------------------------------------------------------------------------------------------------------\n" .
2007
					/* translators: the placeholder is a directory path */
2008
					esc_html__( 'The files were created at %3$s', 'jetpack' ) . "\n" .
2009
					esc_html__( 'To start using the block, build the blocks with yarn run build-extensions', 'jetpack' ) . "\n" .
2010
					/* translators: the placeholder is a file path */
2011
					esc_html__( 'The block slug has been added to the %4$s list at %5$s', 'jetpack' ) . "\n" .
2012
					'%6$s' . "\n" .
2013
					/* translators: the placeholder is a URL */
2014
					"\n" . esc_html__( 'Read more at %7$s', 'jetpack' ) . "\n",
2015
					$title,
2016
					$slug,
2017
					$path,
2018
					$variation,
2019
					$block_list_path,
2020
					$block_constant,
2021
					'https://github.com/Automattic/jetpack/blob/master/extensions/README.md#develop-new-blocks'
2022
				) . '--------------------------------------------------------------------------------------------------------------------'
2023
			);
2024
		}
2025
	}
2026
2027
	/**
2028
	 * Built the file replacing the placeholders in the template with the data supplied.
2029
	 *
2030
	 * @param string $template
2031
	 * @param array  $data
2032
	 *
2033
	 * @return string mixed
2034
	 */
2035
	private static function render_block_file( $template, $data = array() ) {
2036
		return \WP_CLI\Utils\mustache_render( JETPACK__PLUGIN_DIR . "wp-cli-templates/$template.mustache", $data );
2037
	}
2038
}
2039
2040
/*
2041
 * Standard "ask for permission to continue" function.
2042
 * If action cancelled, ask if they need help.
2043
 *
2044
 * Written outside of the class so it's not listed as an executable command w/ 'wp jetpack'
2045
 *
2046
 * @param $flagged   bool   false = normal option | true = flagged by get_jetpack_options_for_reset()
2047
 * @param $error_msg string (optional)
2048
 */
2049
function jetpack_cli_are_you_sure( $flagged = false, $error_msg = false ) {
2050
	$cli = new Jetpack_CLI();
2051
2052
	// Default cancellation message
2053
	if ( ! $error_msg ) {
2054
		$error_msg =
2055
			__( 'Action cancelled. Have a question?', 'jetpack' )
2056
			. ' '
2057
			. $cli->green_open
2058
			. 'jetpack.com/support'
2059
			. $cli->color_close;
2060
	}
2061
2062
	if ( ! $flagged ) {
2063
		$prompt_message = _x( 'Are you sure? This cannot be undone. Type "yes" to continue:', '"yes" is a command - do not translate.', 'jetpack' );
2064
	} else {
2065
		$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' );
2066
	}
2067
2068
	WP_CLI::line( $prompt_message );
2069
	$handle = fopen( 'php://stdin', 'r' );
2070
	$line   = fgets( $handle );
2071
	if ( 'yes' != trim( $line ) ) {
2072
		WP_CLI::error( $error_msg );
2073
	}
2074
}
2075