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