Completed
Push — master ( 47e264...1b4d4d )
by Marin
33:33 queued 24:30
created

Jetpack_CLI::publicize()   F

Complexity

Conditions 40
Paths 12912

Size

Total Lines 173

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
900
					array(
901
						'sync_wait_time' => 0,
902
						'enqueue_wait_time' => 0,
903
						'queue_max_writes_sec' => 10000,
904
						'max_queue_size_full_sync' => 100000
905
					)
906
				);
907
				Jetpack_Sync_Settings::update_settings( $sync_settings );
908
909
				// Convert comma-delimited string of modules to an array
910 View Code Duplication
				if ( ! empty( $assoc_args['modules'] ) ) {
911
					$modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
912
913
					// Convert the array so that the keys are the module name and the value is true to indicate
914
					// that we want to sync the module
915
					$modules = array_map( '__return_true', array_flip( $modules ) );
916
				}
917
918 View Code Duplication
				foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
919
					if (
920
						'users' === $module_name &&
921
						isset( $assoc_args[ $module_name ] ) &&
922
						'initial' === $assoc_args[ $module_name ]
923
					) {
924
						$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...
925
					} elseif ( isset( $assoc_args[ $module_name ] ) ) {
926
						$ids = explode( ',', $assoc_args[ $module_name ] );
927
						if ( count( $ids ) > 0 ) {
928
							$modules[ $module_name ] = $ids;
929
						}
930
					}
931
				}
932
933
				if ( empty( $modules ) ) {
934
					$modules = null;
935
				}
936
937
				// Kick off a full sync
938
				if ( Jetpack_Sync_Actions::do_full_sync( $modules ) ) {
939 View Code Duplication
					if ( $modules ) {
940
						WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
941
					} else {
942
						WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
943
					}
944 View Code Duplication
				} else {
945
946
					// Reset sync settings to original.
947
					Jetpack_Sync_Settings::update_settings( $original_settings );
948
949
					if ( $modules ) {
950
						WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
951
					} else {
952
						WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
953
					}
954
				}
955
956
				// Keep sending to WPCOM until there's nothing to send
957
				$i = 1;
958
				do {
959
					$result = Jetpack_Sync_Actions::$sender->do_full_sync();
0 ignored issues
show
Bug introduced by
The property sender cannot be accessed from this context as it is declared private in class Jetpack_Sync_Actions.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

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