Completed
Push — add/geo-location-support ( 007f25...6296b5 )
by Brad
29:34 queued 17:46
created

class.jetpack-cli.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
WP_CLI::add_command( 'jetpack', 'Jetpack_CLI' );
4
5
/**
6
 * Control your local Jetpack installation.
7
 */
8
class Jetpack_CLI extends WP_CLI_Command {
9
	// Aesthetics
10
	public $green_open  = "\033[32m";
11
	public $red_open    = "\033[31m";
12
	public $yellow_open = "\033[33m";
13
	public $color_close = "\033[0m";
14
15
	/**
16
	 * Get Jetpack Details
17
	 *
18
	 * ## OPTIONS
19
	 *
20
	 * empty: Leave it empty for basic stats
21
	 *
22
	 * full: View full stats.  It's the data from the heartbeat
23
	 *
24
	 * ## EXAMPLES
25
	 *
26
	 * wp jetpack status
27
	 * wp jetpack status full
28
	 *
29
	 */
30
	public function status( $args, $assoc_args ) {
31
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-debugger.php' );
32
33
		WP_CLI::line( sprintf( __( 'Checking status for %s', 'jetpack' ), esc_url( get_home_url() ) ) );
34
35
		if ( ! Jetpack::is_active() ) {
36
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
37
		}
38
39 View Code Duplication
		if ( isset( $args[0] ) && 'full' !== $args[0] ) {
40
			/* translators: %s is a command like "prompt" */
41
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $args[0] ) );
42
		}
43
44
		$master_user_email = Jetpack::get_master_user_email();
45
46
		$jetpack_self_test = Jetpack_Debugger::run_self_test(); // Performs the same tests as jetpack.com/support/debug/
47
48
		if ( ! $jetpack_self_test || ! wp_remote_retrieve_response_code( $jetpack_self_test ) ) {
49
			WP_CLI::error( __( 'Jetpack connection status unknown.', 'jetpack' ) );
50
		} else if ( 200 == wp_remote_retrieve_response_code( $jetpack_self_test ) ) {
51
			WP_CLI::success( __( 'Jetpack is currently connected to WordPress.com', 'jetpack' ) );
52
		} else {
53
			WP_CLI::error( __( 'Jetpack connection is broken.', 'jetpack' ) );
54
		}
55
56
		WP_CLI::line( sprintf( __( 'The Jetpack Version is %s', 'jetpack' ), JETPACK__VERSION ) );
57
		WP_CLI::line( sprintf( __( 'The WordPress.com blog_id is %d', 'jetpack' ), Jetpack_Options::get_option( 'id' ) ) );
58
		WP_CLI::line( sprintf( __( 'The WordPress.com account for the primary connection is %s', 'jetpack' ), $master_user_email ) );
59
60
		/*
61
		 * Are they asking for all data?
62
		 *
63
		 * Loop through heartbeat data and organize by priority.
64
		 */
65
		$all_data = ( isset( $args[0] ) && 'full' == $args[0] ) ? 'full' : false;
66
		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...
67
			// Heartbeat data
68
			WP_CLI::line( "\n" . __( 'Additional data: ', 'jetpack' ) );
69
70
			// Get the filtered heartbeat data.
71
			// Filtered so we can color/list by severity
72
			$stats = Jetpack::jetpack_check_heartbeat_data();
73
74
			// Display red flags first
75
			foreach ( $stats['bad'] as $stat => $value ) {
76
				printf( "$this->red_open%-'.16s %s $this->color_close\n", $stat, $value );
77
			}
78
79
			// Display caution warnings next
80
			foreach ( $stats['caution'] as $stat => $value ) {
81
				printf( "$this->yellow_open%-'.16s %s $this->color_close\n", $stat, $value );
82
			}
83
84
			// The rest of the results are good!
85
			foreach ( $stats['good'] as $stat => $value ) {
86
87
				// Modules should get special spacing for aestetics
88
				if ( strpos( $stat, 'odule-' ) ) {
89
					printf( "%-'.30s %s\n", $stat, $value );
90
					usleep( 4000 ); // For dramatic effect lolz
91
					continue;
92
				}
93
				printf( "%-'.16s %s\n", $stat, $value );
94
				usleep( 4000 ); // For dramatic effect lolz
95
			}
96
		} else {
97
			// Just the basics
98
			WP_CLI::line( "\n" . _x( "View full status with 'wp jetpack status full'", '"wp jetpack status full" is a command - do not translate', 'jetpack' ) );
99
		}
100
	}
101
102
	/**
103
	 * Tests the active connection
104
	 *
105
	 * 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.
106
	 *
107
	 * ## EXAMPLES
108
	 *
109
	 * wp jetpack test-connection
110
	 *
111
	 * @subcommand test-connection
112
	 */
113
	public function test_connection( $args, $assoc_args ) {
114
115
		WP_CLI::line( sprintf( __( 'Testing connection for %s', 'jetpack' ), esc_url( get_site_url() ) ) );
116
117
		if ( ! Jetpack::is_active() ) {
118
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
119
		}
120
121
		$response = Jetpack_Client::wpcom_json_api_request_as_blog(
122
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
123
			Jetpack_Client::WPCOM_JSON_API_VERSION
124
		);
125
126 View Code Duplication
		if ( is_wp_error( $response ) ) {
127
			/* translators: %1$s is the error code, %2$s is the error message */
128
			WP_CLI::error( sprintf( __( 'Failed to test connection (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ) );
129
		}
130
131
		$body = wp_remote_retrieve_body( $response );
132
		if ( ! $body ) {
133
			WP_CLI::error( __( 'Failed to test connection (empty response body)', 'jetpack' ) );
134
		}
135
136
		$result = json_decode( $body );
137
		$is_connected = (bool) $result->connected;
138
		$message = $result->message;
139
140
		if ( $is_connected ) {
141
			WP_CLI::success( $message );
142
		} else {
143
			WP_CLI::error( $message );
144
		}
145
	}
146
147
	/**
148
	 * Disconnect Jetpack Blogs or Users
149
	 *
150
	 * ## OPTIONS
151
	 *
152
	 * blog: Disconnect the entire blog.
153
	 *
154
	 * user <user_identifier>: Disconnect a specific user from WordPress.com.
155
	 *
156
	 * Please note, the primary account that the blog is connected
157
	 * to WordPress.com with cannot be disconnected without
158
	 * disconnecting the entire blog.
159
	 *
160
	 * ## EXAMPLES
161
	 *
162
	 * wp jetpack disconnect blog
163
	 * wp jetpack disconnect user 13
164
	 * wp jetpack disconnect user username
165
	 * wp jetpack disconnect user [email protected]
166
	 *
167
	 * @synopsis <blog|user> [<user_identifier>]
168
	 */
169
	public function disconnect( $args, $assoc_args ) {
170
		if ( ! Jetpack::is_active() ) {
171
			WP_CLI::error( __( 'You cannot disconnect, without having first connected.', 'jetpack' ) );
172
		}
173
174
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
175
		if ( ! in_array( $action, array( 'blog', 'user', 'prompt' ) ) ) {
176
			/* translators: %s is a command like "prompt" */
177
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
178
		}
179
180
		if ( in_array( $action, array( 'user' ) ) ) {
181
			if ( isset( $args[1] ) ) {
182
				$user_id = $args[1];
183
				if ( ctype_digit( $user_id ) ) {
184
					$field = 'id';
185
					$user_id = (int) $user_id;
186
				} elseif ( is_email( $user_id ) ) {
187
					$field = 'email';
188
					$user_id = sanitize_user( $user_id, true );
189
				} else {
190
					$field = 'login';
191
					$user_id = sanitize_user( $user_id, true );
192
				}
193
				if ( ! $user = get_user_by( $field, $user_id ) ) {
194
					WP_CLI::error( __( 'Please specify a valid user.', 'jetpack' ) );
195
				}
196
			} else {
197
				WP_CLI::error( __( 'Please specify a user by either ID, username, or email.', 'jetpack' ) );
198
			}
199
		}
200
201
		switch ( $action ) {
202
			case 'blog':
203
				Jetpack::log( 'disconnect' );
204
				Jetpack::disconnect();
205
				WP_CLI::success( sprintf(
206
					__( 'Jetpack has been successfully disconnected for %s.', 'jetpack' ),
207
					esc_url( get_site_url() )
208
				) );
209
				break;
210
			case 'user':
211
				if ( Jetpack::unlink_user( $user->ID ) ) {
212
					Jetpack::log( 'unlink', $user->ID );
213
					WP_CLI::success( __( 'User has been successfully disconnected.', 'jetpack' ) );
214
				} else {
215
					/* translators: %s is a username */
216
					WP_CLI::error( sprintf( __( "User %s could not be disconnected. Are you sure they're connected currently?", 'jetpack' ), "{$user->login} <{$user->email}>" ) );
217
				}
218
				break;
219
			case 'prompt':
220
				WP_CLI::error( __( 'Please specify if you would like to disconnect a blog or user.', 'jetpack' ) );
221
				break;
222
		}
223
	}
224
225
	/**
226
	 * Reset Jetpack options and settings to default
227
	 *
228
	 * ## OPTIONS
229
	 *
230
	 * modules: Resets modules to default state ( get_default_modules() )
231
	 *
232
	 * options: Resets all Jetpack options except:
233
	 *  - All private options (Blog token, user token, etc...)
234
	 *  - id (The Client ID/WP.com Blog ID of this site)
235
	 *  - master_user
236
	 *  - version
237
	 *  - activated
238
	 *
239
	 * ## EXAMPLES
240
	 *
241
	 * wp jetpack reset options
242
	 * wp jetpack reset modules
243
	 *
244
	 * @synopsis <modules|options>
245
	 */
246
	public function reset( $args, $assoc_args ) {
247
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
248 View Code Duplication
		if ( ! in_array( $action, array( 'options', 'modules' ) ) ) {
249
			/* translators: %s is a command like "prompt" */
250
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
251
		}
252
253
		// Are you sure?
254
		jetpack_cli_are_you_sure();
255
256
		switch ( $action ) {
257
			case 'options':
258
				$options_to_reset = Jetpack_Options::get_options_for_reset();
259
260
				// Reset the Jetpack options
261
				WP_CLI::line( sprintf(
262
					__( "Resetting Jetpack Options for %s...\n", "jetpack" ),
263
					esc_url( get_site_url() )
264
				) );
265
				sleep(1); // Take a breath
266
				foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
267
					Jetpack_Options::delete_option( $option_to_reset );
268
					usleep( 100000 );
269
					/* translators: This is the result of an action. The option named %s was reset */
270
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
271
				}
272
273
				// Reset the WP options
274
				WP_CLI::line( __( "Resetting the jetpack options stored in wp_options...\n", "jetpack" ) );
275
				usleep( 500000 ); // Take a breath
276
				foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
277
					delete_option( $option_to_reset );
278
					usleep( 100000 );
279
					/* translators: This is the result of an action. The option named %s was reset */
280
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
281
				}
282
283
				// Reset to default modules
284
				WP_CLI::line( __( "Resetting default modules...\n", "jetpack" ) );
285
				usleep( 500000 ); // Take a breath
286
				$default_modules = Jetpack::get_default_modules();
287
				Jetpack::update_active_modules( $default_modules );
288
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
289
290
				// Jumpstart option is special
291
				Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
292
				WP_CLI::success( __( 'jumpstart option reset', 'jetpack' ) );
293
				break;
294 View Code Duplication
			case 'modules':
295
				$default_modules = Jetpack::get_default_modules();
296
				Jetpack::update_active_modules( $default_modules );
297
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
298
				break;
299
			case 'prompt':
300
				WP_CLI::error( __( 'Please specify if you would like to reset your options, or modules', 'jetpack' ) );
301
				break;
302
		}
303
	}
304
305
	/**
306
	 * Manage Jetpack Modules
307
	 *
308
	 * ## OPTIONS
309
	 *
310
	 * <list|activate|deactivate|toggle>
311
	 * : The action to take.
312
	 * ---
313
	 * default: list
314
	 * options:
315
	 *  - list
316
	 *  - activate
317
	 *  - deactivate
318
	 *  - toggle
319
	 * ---
320
	 *
321
	 * [<module_slug>]
322
	 * : The slug of the module to perform an action on.
323
	 *
324
	 * [--format=<format>]
325
	 * : Allows overriding the output of the command when listing modules.
326
	 * ---
327
	 * default: table
328
	 * options:
329
	 *  - table
330
	 *  - json
331
	 *  - csv
332
	 *  - yaml
333
	 *  - ids
334
	 *  - count
335
	 * ---
336
	 *
337
	 * ## EXAMPLES
338
	 *
339
	 * wp jetpack module list
340
	 * wp jetpack module list --format=json
341
	 * wp jetpack module activate stats
342
	 * wp jetpack module deactivate stats
343
	 * wp jetpack module toggle stats
344
	 * wp jetpack module activate all
345
	 * wp jetpack module deactivate all
346
	 */
347
	public function module( $args, $assoc_args ) {
348
		$action = isset( $args[0] ) ? $args[0] : 'list';
349
350
		if ( isset( $args[1] ) ) {
351
			$module_slug = $args[1];
352
			if ( 'all' !== $module_slug && ! Jetpack::is_module( $module_slug ) ) {
353
				/* translators: %s is a module slug like "stats" */
354
				WP_CLI::error( sprintf( __( '%s is not a valid module.', 'jetpack' ), $module_slug ) );
355
			}
356
			if ( 'toggle' === $action ) {
357
				$action = Jetpack::is_module_active( $module_slug )
358
					? 'deactivate'
359
					: 'activate';
360
			}
361
			if ( 'all' === $args[1] ) {
362
				$action = ( 'deactivate' === $action )
363
					? 'deactivate_all'
364
					: 'activate_all';
365
			}
366
		} elseif ( 'list' !== $action ) {
367
			WP_CLI::line( __( 'Please specify a valid module.', 'jetpack' ) );
368
			$action = 'list';
369
		}
370
371
		switch ( $action ) {
372
			case 'list':
373
				$modules_list = array();
374
				$modules      = Jetpack::get_available_modules();
375
				sort( $modules );
376
				foreach ( (array) $modules as $module_slug ) {
377
					if ( 'vaultpress' === $module_slug ) {
378
						continue;
379
					}
380
					$modules_list[] = array(
381
						'slug'   => $module_slug,
382
						'status' => Jetpack::is_module_active( $module_slug )
383
							? __( 'Active', 'jetpack' )
384
							: __( 'Inactive', 'jetpack' ),
385
					);
386
				}
387
				WP_CLI\Utils\format_items( $assoc_args['format'], $modules_list, array( 'slug', 'status' ) );
388
				break;
389
			case 'activate':
390
				$module = Jetpack::get_module( $module_slug );
391
				Jetpack::log( 'activate', $module_slug );
392
				if ( Jetpack::activate_module( $module_slug, false, false ) ) {
393
					WP_CLI::success( sprintf( __( '%s has been activated.', 'jetpack' ), $module['name'] ) );
394
				} else {
395
					WP_CLI::error( sprintf( __( '%s could not be activated.', 'jetpack' ), $module['name'] ) );
396
				}
397
				break;
398 View Code Duplication
			case 'activate_all':
399
				$modules = Jetpack::get_available_modules();
400
				Jetpack::update_active_modules( $modules );
401
				WP_CLI::success( __( 'All modules activated!', 'jetpack' ) );
402
				break;
403
			case 'deactivate':
404
				$module = Jetpack::get_module( $module_slug );
405
				Jetpack::log( 'deactivate', $module_slug );
406
				Jetpack::deactivate_module( $module_slug );
407
				WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) );
408
				break;
409
			case 'deactivate_all':
410
				Jetpack::delete_active_modules();
411
				WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) );
412
				break;
413
			case 'toggle':
414
				// Will never happen, should have been handled above and changed to activate or deactivate.
415
				break;
416
		}
417
	}
418
419
	/**
420
	 * Manage Protect Settings
421
	 *
422
	 * ## OPTIONS
423
	 *
424
	 * whitelist: Whitelist an IP address.  You can also read or clear the whitelist.
425
	 *
426
	 *
427
	 * ## EXAMPLES
428
	 *
429
	 * wp jetpack protect whitelist <ip address>
430
	 * wp jetpack protect whitelist list
431
	 * wp jetpack protect whitelist clear
432
	 *
433
	 * @synopsis <whitelist> [<ip|ip_low-ip_high|list|clear>]
434
	 */
435
	public function protect( $args, $assoc_args ) {
436
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
437
		if ( ! in_array( $action, array( 'whitelist' ) ) ) {
438
			/* translators: %s is a command like "prompt" */
439
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
440
		}
441
		// Check if module is active
442
		if ( ! Jetpack::is_module_active( __FUNCTION__ ) ) {
443
			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__ ) );
444
		}
445
		if ( in_array( $action, array( 'whitelist' ) ) ) {
446
			if ( isset( $args[1] ) ) {
447
				$action = 'whitelist';
448
			} else {
449
				$action = 'prompt';
450
			}
451
		}
452
		switch ( $action ) {
453
			case 'whitelist':
454
				$whitelist         = array();
455
				$new_ip            = $args[1];
456
				$current_whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
457
458
				// Build array of IPs that are already whitelisted.
459
				// Re-build manually instead of using jetpack_protect_format_whitelist() so we can easily get
460
				// low & high range params for jetpack_protect_ip_address_is_in_range();
461
				foreach( $current_whitelist as $whitelisted ) {
462
463
					// IP ranges
464
					if ( $whitelisted->range ) {
465
466
						// Is it already whitelisted?
467
						if ( jetpack_protect_ip_address_is_in_range( $new_ip, $whitelisted->range_low, $whitelisted->range_high ) ) {
468
							/* translators: %s is an IP address */
469
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
470
							break;
471
						}
472
						$whitelist[] = $whitelisted->range_low . " - " . $whitelisted->range_high;
473
474
					} else { // Individual IPs
475
476
						// Check if the IP is already whitelisted (single IP only)
477
						if ( $new_ip == $whitelisted->ip_address ) {
478
							/* translators: %s is an IP address */
479
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
480
							break;
481
						}
482
						$whitelist[] = $whitelisted->ip_address;
483
484
					}
485
				}
486
487
				/*
488
				 * List the whitelist
489
				 * Done here because it's easier to read the $whitelist array after it's been rebuilt
490
				 */
491
				if ( isset( $args[1] ) && 'list' == $args[1] ) {
492 View Code Duplication
					if ( ! empty( $whitelist ) ) {
493
						WP_CLI::success( __( 'Here are your whitelisted IPs:', 'jetpack' ) );
494
						foreach ( $whitelist as $ip ) {
495
							WP_CLI::line( "\t" . str_pad( $ip, 24 ) ) ;
496
						}
497
					} else {
498
						WP_CLI::line( __( 'Whitelist is empty.', "jetpack" ) ) ;
499
					}
500
					break;
501
				}
502
503
				/*
504
				 * Clear the whitelist
505
				 */
506
				if ( isset( $args[1] ) && 'clear' == $args[1] ) {
507
					if ( ! empty( $whitelist ) ) {
508
						$whitelist = array();
509
						jetpack_protect_save_whitelist( $whitelist );
510
						WP_CLI::success( __( 'Cleared all whitelisted IPs', 'jetpack' ) );
511
					} else {
512
						WP_CLI::line( __( 'Whitelist is empty.', "jetpack" ) ) ;
513
					}
514
					break;
515
				}
516
517
				// Append new IP to whitelist array
518
				array_push( $whitelist, $new_ip );
519
520
				// Save whitelist if there are no errors
521
				$result = jetpack_protect_save_whitelist( $whitelist );
522
				if ( is_wp_error( $result ) ) {
523
					WP_CLI::error( __( $result, 'jetpack' ) );
524
				}
525
526
				/* translators: %s is an IP address */
527
				WP_CLI::success( sprintf( __( '%s has been whitelisted.', 'jetpack' ), $new_ip ) );
528
				break;
529
			case 'prompt':
530
				WP_CLI::error(
531
					__( 'No command found.', 'jetpack' ) . "\n" .
532
					__( 'Please enter the IP address you want to whitelist.', 'jetpack' ) . "\n" .
533
					_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" .
534
					_x( "You can also 'list' or 'clear' the whitelist.", "'list' and 'clear' are commands and should not be translated", 'jetpack' ) . "\n"
535
				);
536
				break;
537
		}
538
	}
539
540
	/**
541
	 * Manage Jetpack Options
542
	 *
543
	 * ## OPTIONS
544
	 *
545
	 * list   : List all jetpack options and their values
546
	 * delete : Delete an option
547
	 *          - can only delete options that are white listed.
548
	 * update : update an option
549
	 *          - can only update option strings
550
	 * get    : get the value of an option
551
	 *
552
	 * ## EXAMPLES
553
	 *
554
	 * wp jetpack options list
555
	 * wp jetpack options get    <option_name>
556
	 * wp jetpack options delete <option_name>
557
	 * wp jetpack options update <option_name> [<option_value>]
558
	 *
559
	 * @synopsis <list|get|delete|update> [<option_name>] [<option_value>]
560
	 */
561
	public function options( $args, $assoc_args ) {
562
		$action = isset( $args[0] ) ? $args[0] : 'list';
563
		$safe_to_modify = Jetpack_Options::get_options_for_reset();
564
565
		// Jumpstart is special
566
		array_push( $safe_to_modify, 'jumpstart' );
567
568
		// Is the option flagged as unsafe?
569
		$flagged = ! in_array( $args[1], $safe_to_modify );
570
571 View Code Duplication
		if ( ! in_array( $action, array( 'list', 'get', 'delete', 'update' ) ) ) {
572
			/* translators: %s is a command like "prompt" */
573
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
574
		}
575
576
		if ( isset( $args[0] ) ) {
577
			if ( 'get' == $args[0] && isset( $args[1] ) ) {
578
				$action = 'get';
579
			} else if ( 'delete' == $args[0] && isset( $args[1] ) ) {
580
				$action = 'delete';
581 View Code Duplication
			} else if ( 'update' == $args[0] && isset( $args[1] ) ) {
582
				$action = 'update';
583
			} else {
584
				$action = 'list';
585
			}
586
		}
587
588
		// Bail if the option isn't found
589
		$option = isset( $args[1] ) ? Jetpack_Options::get_option( $args[1] ) : false;
590 View Code Duplication
		if ( isset( $args[1] ) && ! $option && 'update' !== $args[0] ) {
591
			WP_CLI::error( __( 'Option not found or is empty.  Use "list" to list option names', 'jetpack' ) );
592
		}
593
594
		// Let's print_r the option if it's an array
595
		// Used in the 'get' and 'list' actions
596
		$option = is_array( $option ) ? print_r( $option ) : $option;
597
598
		switch ( $action ) {
599
			case 'get':
600
				WP_CLI::success( "\t" . $option );
601
				break;
602
			case 'delete':
603
				jetpack_cli_are_you_sure( $flagged );
604
605
				Jetpack_Options::delete_option( $args[1] );
606
				WP_CLI::success( sprintf( __( 'Deleted option: %s', 'jetpack' ), $args[1] ) );
607
				break;
608
			case 'update':
609
				jetpack_cli_are_you_sure( $flagged );
610
611
				// Updating arrays would get pretty tricky...
612
				$value = Jetpack_Options::get_option( $args[1] );
613
				if ( $value && is_array( $value ) ) {
614
					WP_CLI::error( __( 'Sorry, no updating arrays at this time', 'jetpack' ) );
615
				}
616
617
				Jetpack_Options::update_option( $args[1], $args[2] );
618
				WP_CLI::success( sprintf( _x( 'Updated option: %s to "%s"', 'Updating an option from "this" to "that".', 'jetpack' ), $args[1], $args[2] ) );
619
				break;
620
			case 'list':
621
				$options_compact     = Jetpack_Options::get_option_names();
622
				$options_non_compact = Jetpack_Options::get_option_names( 'non_compact' );
623
				$options_private     = Jetpack_Options::get_option_names( 'private' );
624
				$options             = array_merge( $options_compact, $options_non_compact, $options_private );
625
626
				// Table headers
627
				WP_CLI::line( "\t" . str_pad( __( 'Option', 'jetpack' ), 30 ) . __( 'Value', 'jetpack' ) );
628
629
				// List out the options and their values
630
				// Tell them if the value is empty or not
631
				// Tell them if it's an array
632
				foreach ( $options as $option ) {
633
					$value = Jetpack_Options::get_option( $option );
634
					if ( ! $value ) {
635
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Empty' );
636
						continue;
637
					}
638
639
					if ( ! is_array( $value ) ) {
640
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . $value );
641
					} else if ( is_array( $value ) ) {
642
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Array - Use "get <option>" to read option array.' );
643
					}
644
				}
645
				$option_text = '{' . _x( 'option', 'a variable command that a user can write, provided in the printed instructions', 'jetpack' ) . '}';
646
				$value_text  = '{' . _x( 'value', 'the value that they want to update the option to', 'jetpack' ) . '}';
647
648
				WP_CLI::success(
649
					_x( "Above are your options. You may 'get', 'delete', and 'update' them.", "'get', 'delete', and 'update' are commands - do not translate.", 'jetpack' ) . "\n" .
650
					str_pad( 'wp jetpack options get', 26 )    . $option_text . "\n" .
651
					str_pad( 'wp jetpack options delete', 26 ) . $option_text . "\n" .
652
					str_pad( 'wp jetpack options update', 26 ) . "$option_text $value_text" . "\n" .
653
					_x( "Type 'wp jetpack options' for more info.", "'wp jetpack options' is a command - do not translate.", 'jetpack' ) . "\n"
654
				);
655
				break;
656
		}
657
	}
658
659
	/**
660
	 * Get the status of or start a new Jetpack sync.
661
	 *
662
	 * ## OPTIONS
663
	 *
664
	 * status : Print the current sync status
665
	 * start  : Start a full sync from this site to WordPress.com
666
	 *
667
	 * ## EXAMPLES
668
	 *
669
	 * wp jetpack sync status
670
	 * wp jetpack sync start --modules=functions --sync_wait_time=5
671
	 *
672
	 * @synopsis <status|start> [--<field>=<value>]
673
	 */
674
	public function sync( $args, $assoc_args ) {
675
		if ( ! Jetpack_Sync_Actions::sync_allowed() ) {
676
			WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
677
		}
678
679
		$action = isset( $args[0] ) ? $args[0] : 'status';
680
681
		switch ( $action ) {
682
			case 'status':
683
				$status = Jetpack_Sync_Actions::get_sync_status();
684
				$collection = array();
685
				foreach ( $status as $key => $item ) {
686
					$collection[]  = array(
687
						'option' => $key,
688
						'value' => is_scalar( $item ) ? $item : json_encode( $item )
689
					);
690
				}
691
692
				WP_CLI\Utils\format_items( 'table', $collection, array( 'option', 'value' ) );
693
				break;
694
			case 'start':
695
				// Get the original settings so that we can restore them later
696
				$original_settings = Jetpack_Sync_Settings::get_settings();
697
698
				// Initialize sync settigns so we can sync as quickly as possible
699
				$sync_settings = wp_parse_args(
700
					array_intersect_key( $assoc_args, Jetpack_Sync_Settings::$valid_settings ),
701
					array(
702
						'sync_wait_time' => 0,
703
						'enqueue_wait_time' => 0,
704
						'queue_max_writes_sec' => 10000,
705
						'max_queue_size_full_sync' => 100000
706
					)
707
				);
708
				Jetpack_Sync_Settings::update_settings( $sync_settings );
709
710
				// Convert comma-delimited string of modules to an array
711 View Code Duplication
				if ( ! empty( $assoc_args['modules'] ) ) {
712
					$modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
713
714
					// Convert the array so that the keys are the module name and the value is true to indicate
715
					// that we want to sync the module
716
					$modules = array_map( '__return_true', array_flip( $modules ) );
717
				}
718
719 View Code Duplication
				foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
720
					if (
721
						'users' === $module_name &&
722
						isset( $assoc_args[ $module_name ] ) &&
723
						'initial' === $assoc_args[ $module_name ]
724
					) {
725
						$modules[ 'users' ] = 'initial';
726
					} elseif ( isset( $assoc_args[ $module_name ] ) ) {
727
						$ids = explode( ',', $assoc_args[ $module_name ] );
728
						if ( count( $ids ) > 0 ) {
729
							$modules[ $module_name ] = $ids;
730
						}
731
					}
732
				}
733
734
				if ( empty( $modules ) ) {
735
					$modules = null;
736
				}
737
738
				// Kick off a full sync
739
				if ( Jetpack_Sync_Actions::do_full_sync( $modules ) ) {
740
					if ( $modules ) {
741
						WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
742
					} else {
743
						WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
744
					}
745 View Code Duplication
				} else {
746
747
					// Reset sync settings to original.
748
					Jetpack_Sync_Settings::update_settings( $original_settings );
749
750
					if ( $modules ) {
751
						WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
752
					} else {
753
						WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
754
					}
755
				}
756
757
				// Keep sending to WPCOM until there's nothing to send
758
				$i = 1;
759
				do {
760
					$result = Jetpack_Sync_Actions::$sender->do_full_sync();
761
					if ( is_wp_error( $result ) ) {
762
						$queue_empty_error = ( 'empty_queue_full_sync' == $result->get_error_code() );
763
						if ( ! $queue_empty_error || ( $queue_empty_error && ( 1 == $i ) ) ) {
764
							WP_CLI::error( sprintf( __( 'Sync errored with code: %s', 'jetpack' ), $result->get_error_code() ) );
765
						}
766
					} else {
767
						if ( 1 == $i ) {
768
							WP_CLI::log( __( 'Sent data to WordPress.com', 'jetpack' ) );
769
						} else {
770
							WP_CLI::log( __( 'Sent more data to WordPress.com', 'jetpack' ) );
771
						}
772
					}
773
					$i++;
774
				} while ( $result && ! is_wp_error( $result ) );
775
776
				// Reset sync settings to original.
777
				Jetpack_Sync_Settings::update_settings( $original_settings );
778
779
				WP_CLI::success( __( 'Finished syncing to WordPress.com', 'jetpack' ) );
780
				break;
781
		}
782
	}
783
784
	/**
785
	 * List the contents of a specific Jetpack sync queue.
786
	 *
787
	 * ## OPTIONS
788
	 *
789
	 * peek : List the 100 front-most items on the queue.
790
	 *
791
	 * ## EXAMPLES
792
	 *
793
	 * wp jetpack sync_queue full_sync peek
794
	 *
795
	 * @synopsis <incremental|full_sync> <peek>
796
	 */
797
	public function sync_queue( $args, $assoc_args ) {
798
		if ( ! Jetpack_Sync_Actions::sync_allowed() ) {
799
			WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
800
		}
801
802
		$queue_name = isset( $args[0] ) ? $args[0] : 'sync';
803
		$action = isset( $args[1] ) ? $args[1] : 'peek';
804
805
		// We map the queue name that way we can support more friendly queue names in the commands, but still use
806
		// the queue name that the code expects.
807
		$queue_name_map = $allowed_queues = array(
808
			'incremental' => 'sync',
809
			'full'        => 'full_sync',
810
		);
811
		$mapped_queue_name = isset( $queue_name_map[ $queue_name ] ) ? $queue_name_map[ $queue_name ] : $queue_name;
812
813
		switch( $action ) {
814
			case 'peek':
815
				require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-queue.php';
816
				$queue = new Jetpack_Sync_Queue( $mapped_queue_name );
817
				$items = $queue->peek( 100 );
818
819
				if ( empty( $items ) ) {
820
					/* translators: %s is the name of the queue, either 'incremental' or 'full' */
821
					WP_CLI::log( sprintf( __( 'Nothing is in the queue: %s', 'jetpack' ), $queue_name  ) );
822
				} else {
823
					$collection = array();
824
					foreach ( $items as $item ) {
825
						$collection[] = array(
826
							'action'          => $item[0],
827
							'args'            => json_encode( $item[1] ),
828
							'current_user_id' => $item[2],
829
							'microtime'       => $item[3],
830
							'importing'       => (string) $item[4],
831
						);
832
					}
833
					WP_CLI\Utils\format_items(
834
						'table',
835
						$collection,
836
						array(
837
							'action',
838
							'args',
839
							'current_user_id',
840
							'microtime',
841
							'importing',
842
						)
843
					);
844
				}
845
				break;
846
		}
847
	}
848
849
	/**
850
	 * Cancel's the current Jetpack plan granted by this partner, if applicable
851
	 *
852
	 * Returns success or error JSON
853
	 *
854
	 * <token_json>
855
	 * : JSON blob of WPCOM API token
856
	 *  [--partner_tracking_id=<partner_tracking_id>]
857
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
858
	 *
859
	 *  * @synopsis <token_json> [--partner_tracking_id=<partner_tracking_id>]
860
	 */
861
	public function partner_cancel( $args, $named_args ) {
862
		list( $token_json ) = $args;
863
864 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
865
			$this->partner_provision_error( new WP_Error( 'missing_access_token',  sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
866
		}
867
868
		if ( isset( $token->error ) ) {
869
			$this->partner_provision_error( new WP_Error( $token->error, $token->message ) );
0 ignored issues
show
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...
870
		}
871
872
		if ( ! isset( $token->access_token ) ) {
873
			$this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
874
		}
875
876
		if ( Jetpack::validate_sync_error_idc_option() ) {
877
			$this->partner_provision_error( new WP_Error(
878
				'site_in_safe_mode',
879
				esc_html__( 'Can not cancel a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
880
			) );
881
		}
882
883
		$site_identifier = Jetpack_Options::get_option( 'id' );
884
885
		if ( ! $site_identifier ) {
886
			$site_identifier = Jetpack::build_raw_urls( get_home_url() );
887
		}
888
889
		$request = array(
890
			'headers' => array(
891
				'Authorization' => "Bearer " . $token->access_token,
892
				'Host'          => defined( 'JETPACK__WPCOM_JSON_API_HOST_HEADER' ) ? JETPACK__WPCOM_JSON_API_HOST_HEADER : 'public-api.wordpress.com',
893
			),
894
			'timeout' => 60,
895
			'method'  => 'POST',
896
		);
897
898
		$url = sprintf( 'https://%s/rest/v1.3/jpphp/%s/partner-cancel', $this->get_api_host(), $site_identifier );
899 View Code Duplication
		if ( ! empty( $named_args ) && ! empty( $named_args['partner_tracking_id'] ) ) {
900
			$url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
901
		}
902
903
		$result = Jetpack_Client::_wp_remote_request( $url, $request );
904
905
		Jetpack_Options::delete_option( 'onboarding' );
906
907
		if ( is_wp_error( $result ) ) {
908
			$this->partner_provision_error( $result );
909
		}
910
911
		WP_CLI::log( wp_remote_retrieve_body( $result ) );
912
	}
913
914
	/**
915
	 * Provision a site using a Jetpack Partner license
916
	 *
917
	 * Returns JSON blob
918
	 *
919
	 * ## OPTIONS
920
	 *
921
	 * <token_json>
922
	 * : JSON blob of WPCOM API token
923
	 * [--plan=<plan_name>]
924
	 * : Slug of the requested plan, e.g. premium
925
	 * [--wpcom_user_id=<user_id>]
926
	 * : WordPress.com ID of user to connect as (must be whitelisted against partner key)
927
	 * [--wpcom_user_email=<wpcom_user_email>]
928
	 * : Override the email we send to WordPress.com for registration
929
	 * [--onboarding=<onboarding>]
930
	 * : Guide the user through an onboarding wizard
931
	 * [--force_register=<register>]
932
	 * : Whether to force a site to register
933
	 * [--force_connect=<force_connect>]
934
	 * : Force JPS to not reuse existing credentials
935
	 * [--home_url=<home_url>]
936
	 * : Overrides the home option via the home_url filter, or the WP_HOME constant
937
	 * [--site_url=<site_url>]
938
	 * : Overrides the siteurl option via the site_url filter, or the WP_SITEURL constant
939
	 * [--partner_tracking_id=<partner_tracking_id>]
940
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
941
	 *
942
	 * ## EXAMPLES
943
	 *
944
	 *     $ wp jetpack partner_provision '{ some: "json" }' premium 1
945
	 *     { success: true }
946
	 *
947
	 * @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>]
948
	 */
949
	public function partner_provision( $args, $named_args ) {
950
		list( $token_json ) = $args;
951
952 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
953
			$this->partner_provision_error( new WP_Error( 'missing_access_token',  sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
954
		}
955
956
		if ( isset( $token->error ) ) {
957
			$message = isset( $token->message )
958
				? $token->message
959
				: '';
960
			$this->partner_provision_error( new WP_Error( $token->error, $message ) );
961
		}
962
963
		if ( ! isset( $token->access_token ) ) {
964
			$this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
965
		}
966
967
		require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
968
969
		$body_json = Jetpack_Provision::partner_provision( $token->access_token, $named_args );
970
971
		if ( is_wp_error( $body_json ) ) {
972
			error_log( json_encode( array(
973
				'success'       => false,
974
				'error_code'    => $body_json->get_error_code(),
975
				'error_message' => $body_json->get_error_message()
976
			) ) );
977
			exit( 1 );
978
		}
979
980
		WP_CLI::log( json_encode( $body_json ) );
981
	}
982
983
	/**
984
	 * Manages your Jetpack sitemap
985
	 *
986
	 * ## OPTIONS
987
	 *
988
	 * rebuild : Rebuild all sitemaps
989
	 * --purge : if set, will remove all existing sitemap data before rebuilding
990
	 *
991
	 * ## EXAMPLES
992
	 *
993
	 * wp jetpack sitemap rebuild
994
	 *
995
	 * @subcommand sitemap
996
	 * @synopsis <rebuild> [--purge]
997
	 */
998
	public function sitemap( $args, $assoc_args ) {
999
		if ( ! Jetpack::is_active() ) {
1000
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1001
		}
1002
		if ( ! Jetpack::is_module_active( 'sitemaps' ) ) {
1003
			WP_CLI::error( __( 'Jetpack Sitemaps module is not currently active. Activate it first if you want to work with sitemaps.', 'jetpack' ) );
1004
		}
1005
		if ( ! class_exists( 'Jetpack_Sitemap_Builder' ) ) {
1006
			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' ) );
1007
		}
1008
1009
		if ( isset( $assoc_args['purge'] ) && $assoc_args['purge'] ) {
1010
			$librarian = new Jetpack_Sitemap_Librarian();
1011
			$librarian->delete_all_stored_sitemap_data();
1012
		}
1013
1014
		$sitemap_builder = new Jetpack_Sitemap_Builder();
1015
		$sitemap_builder->update_sitemap();
1016
	}
1017
1018
	/**
1019
	 * Allows authorizing a user via the command line and will activate
1020
	 *
1021
	 * ## EXAMPLES
1022
	 *
1023
	 * wp jetpack authorize_user --token=123456789abcdef
1024
	 *
1025
	 * @synopsis --token=<value>
1026
	 */
1027
	public function authorize_user( $args, $named_args ) {
1028
		if ( ! is_user_logged_in() ) {
1029
			WP_CLI::error( __( 'Please select a user to authorize via the --user global argument.', 'jetpack' ) );
1030
		}
1031
1032
		if ( empty( $named_args['token'] ) ) {
1033
			WP_CLI::error( __( 'A non-empty token argument must be passed.', 'jetpack' ) );
1034
		}
1035
1036
		$token = sanitize_text_field( $named_args['token'] );
1037
1038
		$is_master_user  = ! Jetpack::is_active();
1039
		$current_user_id = get_current_user_id();
1040
1041
		Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_master_user );
1042
1043
		WP_CLI::log( wp_json_encode( $named_args ) );
1044
1045
		if ( $is_master_user ) {
1046
			/**
1047
			 * Auto-enable SSO module for new Jetpack Start connections
1048
			*
1049
			* @since 5.0.0
1050
			*
1051
			* @param bool $enable_sso Whether to enable the SSO module. Default to true.
1052
			*/
1053
			$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1054
			Jetpack::handle_post_authorization_actions( $enable_sso, false );
1055
1056
			/* translators: %d is a user ID */
1057
			WP_CLI::success( sprintf( __( 'Authorized %d and activated default modules.', 'jetpack' ), $current_user_id ) );
1058
		} else {
1059
			/* translators: %d is a user ID */
1060
			WP_CLI::success( sprintf( __( 'Authorized %d.', 'jetpack' ), $current_user_id ) );
1061
		}
1062
	}
1063
1064
	/**
1065
	 * Allows calling a WordPress.com API endpoint using the current blog's token.
1066
	 *
1067
	 * ## OPTIONS
1068
	 * --resource=<resource>
1069
	 * : The resource to call with the current blog's token, where `%d` represents the current blog's ID.
1070
	 *
1071
	 * [--api_version=<api_version>]
1072
	 * : The API version to query against.
1073
	 *
1074
	 * [--base_api_path=<base_api_path>]
1075
	 * : The base API path to query.
1076
	 * ---
1077
	 * default: rest
1078
	 * ---
1079
	 *
1080
	 * [--body=<body>]
1081
	 * : A JSON encoded string representing arguments to send in the body.
1082
	 *
1083
	 * [--field=<value>]
1084
	 * : Any number of arguments that should be passed to the resource.
1085
	 *
1086
	 * [--pretty]
1087
	 * : Will pretty print the results of a successful API call.
1088
	 *
1089
	 * [--strip-success]
1090
	 * : Will remove the green success label from successful API calls.
1091
	 *
1092
	 * ## EXAMPLES
1093
	 *
1094
	 * wp jetpack call_api --resource='/sites/%d'
1095
	 */
1096
	public function call_api( $args, $named_args ) {
1097
		if ( ! Jetpack::is_active() ) {
1098
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1099
		}
1100
1101
		$consumed_args = array(
1102
			'resource',
1103
			'api_version',
1104
			'base_api_path',
1105
			'body',
1106
			'pretty',
1107
		);
1108
1109
		// Get args that should be passed to resource.
1110
		$other_args = array_diff_key( $named_args, array_flip( $consumed_args ) );
1111
1112
		$decoded_body = ! empty( $named_args['body'] )
1113
			? json_decode( $named_args['body'] )
1114
			: false;
1115
1116
		$resource_url = ( false === strpos( $named_args['resource'], '%d' ) )
1117
			? $named_args['resource']
1118
			: sprintf( $named_args['resource'], Jetpack_Options::get_option( 'id' ) );
1119
1120
		$response = Jetpack_Client::wpcom_json_api_request_as_blog(
1121
			$resource_url,
1122
			empty( $named_args['api_version'] ) ? Jetpack_Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
1123
			$other_args,
1124
			empty( $decoded_body ) ? null : $decoded_body,
1125
			$named_args['base_api_path']
1126
		);
1127
1128 View Code Duplication
		if ( is_wp_error( $response ) ) {
1129
			WP_CLI::error( sprintf(
1130
				/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
1131
				__( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
1132
				$resource_url,
1133
				$response->get_error_code(),
1134
				$response->get_error_message()
1135
			) );
1136
		}
1137
1138
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1139
			WP_CLI::error( sprintf(
1140
				/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
1141
				__( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
1142
				$resource_url,
1143
				wp_remote_retrieve_response_code( $response )
1144
			) );
1145
		}
1146
1147
		$output = wp_remote_retrieve_body( $response );
1148
		if ( isset( $named_args['pretty'] ) ) {
1149
			$decoded_output = json_decode( $output );
1150
			if ( $decoded_output ) {
1151
				$output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
1152
			}
1153
		}
1154
1155
		if ( isset( $named_args['strip-success'] ) ) {
1156
			WP_CLI::log( $output );
1157
			WP_CLI::halt( 0 );
1158
		}
1159
1160
		WP_CLI::success( $output );
1161
	}
1162
1163
	/**
1164
	 * API wrapper for getting stats from the WordPress.com API for the current site.
1165
	 *
1166
	 * ## OPTIONS
1167
	 *
1168
	 * [--quantity=<quantity>]
1169
	 * : The number of units to include.
1170
	 * ---
1171
	 * default: 30
1172
	 * ---
1173
	 *
1174
	 * [--period=<period>]
1175
	 * : The unit of time to query stats for.
1176
	 * ---
1177
	 * default: day
1178
	 * options:
1179
	 *  - day
1180
	 *  - week
1181
	 *  - month
1182
	 *  - year
1183
	 * ---
1184
	 *
1185
	 * [--date=<date>]
1186
	 * : The latest date to return stats for. Ex. - 2018-01-01.
1187
	 *
1188
	 * [--pretty]
1189
	 * : Will pretty print the results of a successful API call.
1190
	 *
1191
	 * [--strip-success]
1192
	 * : Will remove the green success label from successful API calls.
1193
	 *
1194
	 * ## EXAMPLES
1195
	 *
1196
	 * wp jetpack get_stats
1197
	 */
1198
	public function get_stats( $args, $named_args ) {
1199
		$selected_args = array_intersect_key(
1200
			$named_args,
1201
			array_flip( array(
1202
				'quantity',
1203
				'date',
1204
			) )
1205
		);
1206
1207
		// The API expects unit, but period seems to be more correct.
1208
		$selected_args['unit'] = $named_args['period'];
1209
1210
		$command = sprintf(
1211
			'jetpack call_api --resource=/sites/%d/stats/%s',
1212
			Jetpack_Options::get_option( 'id' ),
1213
			add_query_arg( $selected_args, 'visits' )
1214
		);
1215
1216
		if ( isset( $named_args['pretty'] ) ) {
1217
			$command .= ' --pretty';
1218
		}
1219
1220
		if ( isset( $named_args['strip-success'] ) ) {
1221
			$command .= ' --strip-success';
1222
		}
1223
1224
		WP_CLI::runcommand(
1225
			$command,
1226
			array(
1227
				'launch' => false, // Use the current process.
1228
			)
1229
		);
1230
	}
1231
1232
	/*
1233
	 * Allows management of publicize connections.
1234
	 *
1235
	 * ## OPTIONS
1236
	 *
1237
	 * <list|disconnect>
1238
	 * : The action to perform.
1239
	 * ---
1240
	 * options:
1241
	 *  - list
1242
	 *  - disconnect
1243
	 * ---
1244
	 *
1245
	 * [<identifier>]
1246
	 * : The connection ID or service to perform an action on.
1247
	 *
1248
	 * [--format=<format>]
1249
	 * : Allows overriding the output of the command when listing connections.
1250
	 * ---
1251
	 * default: table
1252
	 * options:
1253
	 *  - table
1254
	 *  - json
1255
	 *  - csv
1256
	 *  - yaml
1257
	 *  - ids
1258
	 *  - count
1259
	 * ---
1260
	 *
1261
	 * ## EXAMPLES
1262
	 *
1263
	 * wp jetpack publicize list
1264
	 * wp jetpack publicize list twitter
1265
	 * wp --user=1 jetpack publicize list
1266
	 * wp --user=1 jetpack publicize list twitter
1267
	 * wp jetpack publicize list 123456
1268
	 * wp jetpack publicize disconnect 123456
1269
	 * wp jetpack publicize disconnect all
1270
	 * wp jetpack publicize disconnect twitter
1271
	 */
1272
	public function publicize( $args, $named_args ) {
1273
		if ( ! Jetpack::is_active() ) {
1274
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1275
		}
1276
1277
		$action        = $args[0];
1278
		$publicize     = new Publicize();
1279
		$identifier    = ! empty( $args[1] ) ? $args[1] : false;
1280
		$services      = array_keys( $publicize->get_services() );
1281
		$id_is_service = in_array( $identifier, $services, true );
1282
1283
		switch ( $action ) {
1284
			case 'list':
1285
				$connections_to_return = array();
1286
1287
				// For the CLI command, let's return all connections when a user isn't specified. This
1288
				// differs from the logic in the Publicize class.
1289
				$option_connections = is_user_logged_in()
1290
					? (array) $publicize->get_all_connections_for_user()
1291
					: Jetpack_Options::get_option( 'publicize_connections' );
1292
1293
				foreach ( $option_connections as $service_name => $connections ) {
1294
					foreach ( (array) $connections as $id => $connection ) {
1295
						$connection['id']        = $id;
1296
						$connection['service']   = $service_name;
1297
						$connections_to_return[] = $connection;
1298
					}
1299
				}
1300
1301
				if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
1302
					$temp_connections      = $connections_to_return;
1303
					$connections_to_return = array();
1304
1305
					foreach ( $temp_connections as $connection ) {
1306
						if ( $identifier === $connection['service'] ) {
1307
							$connections_to_return[] = $connection;
1308
						}
1309
					}
1310
				}
1311
1312
				if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
1313
					$connections_to_return = wp_list_filter( $connections_to_return, array( 'id' => $identifier ) );
1314
				}
1315
1316
				if ( empty( $connections_to_return ) ) {
1317
					return false;
1318
				}
1319
1320
				$expected_keys = array(
1321
					'id',
1322
					'service',
1323
					'user_id',
1324
					'provider',
1325
					'issued',
1326
					'expires',
1327
					'external_id',
1328
					'external_name',
1329
					'external_display',
1330
					'type',
1331
					'connection_data',
1332
				);
1333
1334
				WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
1335
				break; // list.
1336
			case 'disconnect':
1337
				if ( ! $identifier ) {
1338
					WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
1339
				}
1340
1341
				// If the connection ID is 'all' then delete all connections. If the connection ID
1342
				// matches a service, delete all connections for that service.
1343
				if ( 'all' === $identifier || $id_is_service ) {
1344
					if ( 'all' === $identifier ) {
1345
						WP_CLI::log( __( "You're about to delete all publicize connections.", 'jetpack' ) );
1346
					} else {
1347
						/* translators: %s is a lowercase string for a social network. */
1348
						WP_CLI::log( sprintf( __( "You're about to delete all publicize connections to %s.", 'jetpack' ), $identifier ) );
1349
					}
1350
1351
					jetpack_cli_are_you_sure();
1352
1353
					$connections = array();
1354
					$service     = $identifier;
1355
1356
					$option_connections = is_user_logged_in()
1357
						? (array) $publicize->get_all_connections_for_user()
1358
						: Jetpack_Options::get_option( 'publicize_connections' );
1359
1360
					if ( 'all' === $service ) {
1361
						foreach ( (array) $option_connections as $service_name => $service_connections ) {
1362
							foreach ( $service_connections as $id => $connection ) {
1363
								$connections[ $id ] = $connection;
1364
							}
1365
						}
1366
					} elseif ( ! empty( $option_connections[ $service ] ) ) {
1367
						$connections = $option_connections[ $service ];
1368
					}
1369
1370
					if ( ! empty( $connections ) ) {
1371
						$count    = count( $connections );
1372
						$progress = \WP_CLI\Utils\make_progress_bar(
1373
							/* translators: %s is a lowercase string for a social network. */
1374
							sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
1375
							$count
1376
						);
1377
1378
						foreach ( $connections as $id => $connection ) {
1379
							if ( false === $publicize->disconnect( false, $id ) ) {
1380
								WP_CLI::error( sprintf(
1381
									/* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
1382
									__( 'Publicize connection %d could not be disconnected', 'jetpack' ),
1383
									$id
1384
								) );
1385
							}
1386
1387
							$progress->tick();
1388
						}
1389
1390
						$progress->finish();
1391
1392
						if ( 'all' === $service ) {
1393
							WP_CLI::success( __( 'All publicize connections were successfully disconnected.', 'jetpack' ) );
1394
						} else {
1395
							/* translators: %s is a lowercase string for a social network. */
1396
							WP_CLI::success( __( 'All publicize connections to %s were successfully disconnected.', 'jetpack' ), $service );
1397
						}
1398
					}
1399
				} else {
1400
					if ( false !== $publicize->disconnect( false, $identifier ) ) {
1401
						/* translators: %d is a numeric ID. Example: 1234. */
1402
						WP_CLI::success( sprintf( __( 'Publicize connection %d has been disconnected.', 'jetpack' ), $identifier ) );
1403
					} else {
1404
						/* translators: %d is a numeric ID. Example: 1234. */
1405
						WP_CLI::error( sprintf( __( 'Publicize connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
1406
					}
1407
				}
1408
				break; // disconnect.
1409
		}
1410
	}
1411
1412
	private function get_api_host() {
1413
		$env_api_host = getenv( 'JETPACK_START_API_HOST', true );
1414
		return $env_api_host ? $env_api_host : JETPACK__WPCOM_JSON_API_HOST;
1415
	}
1416
1417
	private function partner_provision_error( $error ) {
1418
		WP_CLI::log( json_encode( array(
1419
			'success'       => false,
1420
			'error_code'    => $error->get_error_code(),
1421
			'error_message' => $error->get_error_message()
1422
		) ) );
1423
		exit( 1 );
1424
	}
1425
}
1426
1427
/*
1428
 * Standard "ask for permission to continue" function.
1429
 * If action cancelled, ask if they need help.
1430
 *
1431
 * Written outside of the class so it's not listed as an executable command w/ 'wp jetpack'
1432
 *
1433
 * @param $flagged   bool   false = normal option | true = flagged by get_jetpack_options_for_reset()
1434
 * @param $error_msg string (optional)
1435
 */
1436
function jetpack_cli_are_you_sure( $flagged = false, $error_msg = false ) {
1437
	$cli = new Jetpack_CLI();
1438
1439
	// Default cancellation message
1440
	if ( ! $error_msg ) {
1441
		$error_msg =
1442
			__( 'Action cancelled. Have a question?', 'jetpack' )
1443
			. ' '
1444
			. $cli->green_open
1445
			. 'jetpack.com/support'
1446
			.  $cli->color_close;
1447
	}
1448
1449
	if ( ! $flagged ) {
1450
		$prompt_message = __( 'Are you sure? This cannot be undone. Type "yes" to continue:', '"yes" is a command.  Do not translate that.', 'jetpack' );
1451
	} else {
1452
		/* translators: Don't translate the word yes here. */
1453
		$prompt_message = __( 'Are you sure? Modifying this option may disrupt your Jetpack connection.  Type "yes" to continue.', 'jetpack' );
1454
	}
1455
1456
	WP_CLI::line( $prompt_message );
1457
	$handle = fopen( "php://stdin", "r" );
1458
	$line = fgets( $handle );
1459
	if ( 'yes' != trim( $line ) ){
1460
		WP_CLI::error( $error_msg );
1461
	}
1462
}
1463