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

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