PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.1.2
Jetpack – WP Security, Backup, Speed, & Growth v6.1.2
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack-cli.php

class.jetpack-cli.php in Jetpack – WP Security, Backup, Speed, & Growth 6.1.2, at class.jetpack-cli.php

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