PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 5.3.4
Jetpack – WP Security, Backup, Speed, & Growth v5.3.4
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 5.3.4, at class.jetpack-cli.php

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