PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.0.3
Jetpack – WP Security, Backup, Speed, & Growth v12.0.3
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 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.jetpack-cli.php
jetpack Last commit date
3rd-party 3 years ago _inc 3 years ago css 3 years ago extensions 3 years ago images 3 years ago jetpack_vendor 1 day ago json-endpoints 3 years ago modules 1 year ago sal 3 years ago src 3 years ago vendor 3 years ago views 3 years ago CHANGELOG.md 3 years ago LICENSE.txt 5 years ago SECURITY.md 5 years ago class-jetpack-connection-status.php 5 years ago class-jetpack-connection-widget.php 3 years ago class-jetpack-gallery-settings.php 3 years ago class-jetpack-pre-connection-jitms.php 4 years ago class-jetpack-recommendations-banner.php 3 years ago class-jetpack-stats-dashboard-widget.php 3 years ago class-jetpack-wizard-banner.php 5 years ago class-jetpack-xmlrpc-methods.php 3 years ago class.frame-nonce-preview.php 4 years ago class.jetpack-admin.php 3 years ago class.jetpack-affiliate.php 4 years ago class.jetpack-autoupdate.php 3 years ago class.jetpack-bbpress-json-api.compat.php 5 years ago class.jetpack-cli.php 3 years ago class.jetpack-client-server.php 4 years ago class.jetpack-connection-banner.php 3 years ago class.jetpack-data.php 5 years ago class.jetpack-gutenberg.php 3 years ago class.jetpack-heartbeat.php 4 years ago class.jetpack-idc.php 5 years ago class.jetpack-modules-list-table.php 3 years ago class.jetpack-network-sites-list-table.php 4 years ago class.jetpack-network.php 3 years ago class.jetpack-plan.php 3 years ago class.jetpack-post-images.php 3 years ago class.jetpack-twitter-cards.php 3 years ago class.jetpack-user-agent.php 3 years ago class.jetpack.php 3 years ago class.json-api-endpoints.php 3 years ago class.json-api.php 3 years ago class.photon.php 3 years ago composer.json 3 years ago enhanced-open-graph.php 3 years ago functions.compat.php 4 years ago functions.cookies.php 5 years ago functions.global.php 3 years ago functions.is-mobile.php 3 years ago functions.opengraph.php 3 years ago functions.photon.php 3 years ago jetpack.php 1 day ago json-api-config.php 3 years ago json-endpoints.php 3 years ago load-jetpack.php 3 years ago locales.php 4 years ago readme.txt 1 day ago uninstall.php 5 years ago wpml-config.xml 4 years ago
class.jetpack-cli.php
2182 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * WP-CLI command class.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Connection\Client;
9 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
10 use Automattic\Jetpack\Connection\Tokens;
11 use Automattic\Jetpack\Identity_Crisis;
12 use Automattic\Jetpack\IP\Utils as IP_Utils;
13 use Automattic\Jetpack\Status;
14 use Automattic\Jetpack\Sync\Actions;
15 use Automattic\Jetpack\Sync\Listener;
16 use Automattic\Jetpack\Sync\Modules;
17 use Automattic\Jetpack\Sync\Queue;
18 use Automattic\Jetpack\Sync\Settings;
19 use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection_Shared_Functions;
20
21 if ( ! class_exists( 'WP_CLI_Command' ) ) {
22 return;
23 }
24
25 WP_CLI::add_command( 'jetpack', 'Jetpack_CLI' );
26
27 /**
28 * Control your local Jetpack installation.
29 */
30 class Jetpack_CLI extends WP_CLI_Command {
31 /**
32 * Console escape code for green.
33 *
34 * @var string
35 */
36 public $green_open = "\033[32m";
37
38 /**
39 * Console escape code for red.
40 *
41 * @var string
42 */
43 public $red_open = "\033[31m";
44
45 /**
46 * Console escape code for yellow.
47 *
48 * @var string
49 */
50 public $yellow_open = "\033[33m";
51
52 /**
53 * Console escape code to reset coloring.
54 *
55 * @var string
56 */
57 public $color_close = "\033[0m";
58
59 /**
60 * Get Jetpack Details
61 *
62 * ## OPTIONS
63 *
64 * empty: Leave it empty for basic stats
65 *
66 * full: View full stats. It's the data from the heartbeat
67 *
68 * ## EXAMPLES
69 *
70 * wp jetpack status
71 * wp jetpack status full
72 *
73 * @param array $args Positional args.
74 */
75 public function status( $args ) {
76 require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger.php';
77
78 /* translators: %s is the site URL */
79 WP_CLI::line( sprintf( __( 'Checking status for %s', 'jetpack' ), esc_url( get_home_url() ) ) );
80
81 if ( isset( $args[0] ) && 'full' !== $args[0] ) {
82 /* translators: %s is a command like "prompt" */
83 WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $args[0] ) );
84 }
85
86 $master_user_email = Jetpack::get_master_user_email();
87
88 $cxntests = new Jetpack_Cxn_Tests();
89
90 if ( $cxntests->pass() ) {
91 $cxntests->output_results_for_cli();
92
93 WP_CLI::success( __( 'Jetpack is currently connected to WordPress.com', 'jetpack' ) );
94 } else {
95 $error = array();
96 foreach ( $cxntests->list_fails() as $fail ) {
97 $error[] = $fail['name'] . ( empty( $fail['message'] ) ? '' : ': ' . $fail['message'] );
98 }
99 WP_CLI::error_multi_line( $error );
100
101 $cxntests->output_results_for_cli();
102
103 WP_CLI::error( __( 'One or more tests did not pass. Please investigate!', 'jetpack' ) ); // Exit CLI.
104 }
105
106 /* translators: %s is current version of Jetpack, for example 7.3 */
107 WP_CLI::line( sprintf( __( 'The Jetpack Version is %s', 'jetpack' ), JETPACK__VERSION ) );
108 /* translators: %d is WP.com ID of this blog */
109 WP_CLI::line( sprintf( __( 'The WordPress.com blog_id is %d', 'jetpack' ), Jetpack_Options::get_option( 'id' ) ) );
110 /* translators: %s is the email address of the connection owner */
111 WP_CLI::line( sprintf( __( 'The WordPress.com account for the primary connection is %s', 'jetpack' ), $master_user_email ) );
112
113 /*
114 * Are they asking for all data?
115 *
116 * Loop through heartbeat data and organize by priority.
117 */
118 $all_data = ( isset( $args[0] ) && 'full' === $args[0] ) ? 'full' : false;
119 if ( $all_data ) {
120 // Heartbeat data.
121 WP_CLI::line( "\n" . __( 'Additional data: ', 'jetpack' ) );
122
123 // Get the filtered heartbeat data.
124 // Filtered so we can color/list by severity.
125 $stats = Jetpack::jetpack_check_heartbeat_data();
126
127 // Display red flags first.
128 foreach ( $stats['bad'] as $stat => $value ) {
129 WP_CLI::line( sprintf( "$this->red_open%-'.16s %s $this->color_close", $stat, $value ) );
130 }
131
132 // Display caution warnings next.
133 foreach ( $stats['caution'] as $stat => $value ) {
134 WP_CLI::line( sprintf( "$this->yellow_open%-'.16s %s $this->color_close", $stat, $value ) );
135 }
136
137 // The rest of the results are good!
138 foreach ( $stats['good'] as $stat => $value ) {
139
140 // Modules should get special spacing for aestetics.
141 if ( strpos( $stat, 'odule-' ) ) {
142 WP_CLI::line( sprintf( "%-'.30s %s", $stat, $value ) );
143 usleep( 4000 ); // For dramatic effect lolz.
144 continue;
145 }
146 WP_CLI::line( sprintf( "%-'.16s %s", $stat, $value ) );
147 usleep( 4000 ); // For dramatic effect lolz.
148 }
149 } else {
150 // Just the basics.
151 WP_CLI::line( "\n" . _x( "View full status with 'wp jetpack status full'", '"wp jetpack status full" is a command - do not translate', 'jetpack' ) );
152 }
153 }
154
155 /**
156 * Tests the active connection
157 *
158 * 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.
159 *
160 * ## EXAMPLES
161 *
162 * wp jetpack test-connection
163 *
164 * @subcommand test-connection
165 */
166 public function test_connection() {
167
168 /* translators: %s is the site URL */
169 WP_CLI::line( sprintf( __( 'Testing connection for %s', 'jetpack' ), esc_url( get_site_url() ) ) );
170
171 if ( ! Jetpack::is_connection_ready() ) {
172 WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
173 }
174
175 $response = Client::wpcom_json_api_request_as_blog(
176 sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
177 Client::WPCOM_JSON_API_VERSION
178 );
179
180 if ( is_wp_error( $response ) ) {
181 /* translators: %1$s is the error code, %2$s is the error message */
182 WP_CLI::error( sprintf( __( 'Failed to test connection (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ) );
183 }
184
185 $body = wp_remote_retrieve_body( $response );
186 if ( ! $body ) {
187 WP_CLI::error( __( 'Failed to test connection (empty response body)', 'jetpack' ) );
188 }
189
190 $result = json_decode( $body );
191 $is_connected = (bool) $result->connected;
192 $message = $result->message;
193
194 if ( $is_connected ) {
195 WP_CLI::success( $message );
196 } else {
197 WP_CLI::error( $message );
198 }
199 }
200
201 /**
202 * Disconnect Jetpack Blogs or Users
203 *
204 * ## OPTIONS
205 *
206 * blog: Disconnect the entire blog.
207 *
208 * user <user_identifier>: Disconnect a specific user from WordPress.com.
209 *
210 * [--force]
211 * If the user ID provided is the connection owner, it will only be disconnected if --force is passed
212 *
213 * ## EXAMPLES
214 *
215 * wp jetpack disconnect blog
216 * wp jetpack disconnect user 13
217 * wp jetpack disconnect user 1 --force
218 * wp jetpack disconnect user username
219 * wp jetpack disconnect user email@domain.com
220 *
221 * @synopsis <blog|user> [<user_identifier>] [--force]
222 *
223 * @param array $args Positional args.
224 * @param array $assoc_args Named args.
225 */
226 public function disconnect( $args, $assoc_args ) {
227 if ( ! Jetpack::is_connection_ready() ) {
228 WP_CLI::success( __( 'The site is not currently connected, so nothing to do!', 'jetpack' ) );
229 return;
230 }
231
232 $action = isset( $args[0] ) ? $args[0] : 'prompt';
233 if ( ! in_array( $action, array( 'blog', 'user', 'prompt' ), true ) ) {
234 /* translators: %s is a command like "prompt" */
235 WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
236 }
237
238 if ( in_array( $action, array( 'user' ), true ) ) {
239 if ( isset( $args[1] ) ) {
240 $user_id = $args[1];
241 if ( ctype_digit( $user_id ) ) {
242 $field = 'id';
243 $user_id = (int) $user_id;
244 } elseif ( is_email( $user_id ) ) {
245 $field = 'email';
246 $user_id = sanitize_user( $user_id, true );
247 } else {
248 $field = 'login';
249 $user_id = sanitize_user( $user_id, true );
250 }
251 $user = get_user_by( $field, $user_id );
252 if ( ! $user ) {
253 WP_CLI::error( __( 'Please specify a valid user.', 'jetpack' ) );
254 }
255 } else {
256 WP_CLI::error( __( 'Please specify a user by either ID, username, or email.', 'jetpack' ) );
257 }
258 }
259
260 $force_user_disconnect = ! empty( $assoc_args['force'] );
261
262 switch ( $action ) {
263 case 'blog':
264 Jetpack::log( 'disconnect' );
265 ( new Connection_Manager( 'jetpack' ) )->disconnect_site();
266 WP_CLI::success(
267 sprintf(
268 /* translators: %s is the site URL */
269 __( 'Jetpack has been successfully disconnected for %s.', 'jetpack' ),
270 esc_url( get_site_url() )
271 )
272 );
273 break;
274 case 'user':
275 $connection_manager = new Connection_Manager( 'jetpack' );
276 $disconnected = $connection_manager->disconnect_user( $user->ID, $force_user_disconnect );
277 if ( $disconnected ) {
278 Jetpack::log( 'unlink', $user->ID );
279 WP_CLI::success( __( 'User has been successfully disconnected.', 'jetpack' ) );
280 } else {
281 if ( ! $connection_manager->is_user_connected( $user->ID ) ) {
282 /* translators: %s is a username */
283 $error_message = sprintf( __( 'User %s could not be disconnected because it is not connected!', 'jetpack' ), "{$user->data->user_login} <{$user->data->user_email}>" );
284 } elseif ( ! $force_user_disconnect && $connection_manager->is_connection_owner( $user->ID ) ) {
285 /* translators: %s is a username */
286 $error_message = sprintf( __( 'User %s could not be disconnected because it is the connection owner! If you want to disconnect in anyway, use the --force parameter.', 'jetpack' ), "{$user->data->user_login} <{$user->data->user_email}>" );
287 } else {
288 /* translators: %s is a username */
289 $error_message = sprintf( __( 'User %s could not be disconnected.', 'jetpack' ), "{$user->data->user_login} <{$user->data->user_email}>" );
290 }
291 WP_CLI::error( $error_message );
292 }
293 break;
294 case 'prompt':
295 WP_CLI::error( __( 'Please specify if you would like to disconnect a blog or user.', 'jetpack' ) );
296 break;
297 }
298 }
299
300 /**
301 * Reset Jetpack options and settings to default
302 *
303 * ## OPTIONS
304 *
305 * modules: Resets modules to default state ( get_default_modules() )
306 *
307 * options: Resets all Jetpack options except:
308 * - All private options (Blog token, user token, etc...)
309 * - id (The Client ID/WP.com Blog ID of this site)
310 * - master_user
311 * - version
312 * - activated
313 *
314 * ## EXAMPLES
315 *
316 * wp jetpack reset options
317 * wp jetpack reset modules
318 * wp jetpack reset sync-checksum --dry-run --offset=0
319 *
320 * @synopsis <modules|options|sync-checksum> [--dry-run] [--offset=<offset>]
321 *
322 * @param array $args Positional args.
323 * @param array $assoc_args Named args.
324 */
325 public function reset( $args, $assoc_args ) {
326 $action = isset( $args[0] ) ? $args[0] : 'prompt';
327 if ( ! in_array( $action, array( 'options', 'modules', 'sync-checksum' ), true ) ) {
328 /* translators: %s is a command like "prompt" */
329 WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
330 }
331
332 $is_dry_run = ! empty( $assoc_args['dry-run'] );
333
334 if ( $is_dry_run ) {
335 WP_CLI::warning(
336 __( "\nThis is a dry run.\n", 'jetpack' ) .
337 __( "No actions will be taken.\n", 'jetpack' ) .
338 __( "The following messages will give you preview of what will happen when you run this command.\n\n", 'jetpack' )
339 );
340 } else {
341 // We only need to confirm "Are you sure?" when we are not doing a dry run.
342 jetpack_cli_are_you_sure();
343 }
344
345 switch ( $action ) {
346 case 'options':
347 $options_to_reset = Jetpack_Options::get_options_for_reset();
348 // Reset the Jetpack options.
349 WP_CLI::line(
350 sprintf(
351 /* translators: %s is the site URL */
352 __( "Resetting Jetpack Options for %s...\n", 'jetpack' ),
353 esc_url( get_site_url() )
354 )
355 );
356 sleep( 1 ); // Take a breath.
357 foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
358 if ( ! $is_dry_run ) {
359 Jetpack_Options::delete_option( $option_to_reset );
360 usleep( 100000 );
361 }
362
363 /* translators: This is the result of an action. The option named %s was reset */
364 WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
365 }
366
367 // Reset the WP options.
368 WP_CLI::line( __( "Resetting the jetpack options stored in wp_options...\n", 'jetpack' ) );
369 usleep( 500000 ); // Take a breath.
370 foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
371 if ( ! $is_dry_run ) {
372 delete_option( $option_to_reset );
373 usleep( 100000 );
374 }
375 /* translators: This is the result of an action. The option named %s was reset */
376 WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
377 }
378
379 // Reset to default modules.
380 WP_CLI::line( __( "Resetting default modules...\n", 'jetpack' ) );
381 usleep( 500000 ); // Take a breath.
382 $default_modules = Jetpack::get_default_modules();
383 if ( ! $is_dry_run ) {
384 Jetpack::update_active_modules( $default_modules );
385 }
386 WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
387 break;
388 case 'modules':
389 if ( ! $is_dry_run ) {
390 $default_modules = Jetpack::get_default_modules();
391 Jetpack::update_active_modules( $default_modules );
392 }
393
394 WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
395 break;
396 case 'prompt':
397 WP_CLI::error( __( 'Please specify if you would like to reset your options, modules or sync-checksum', 'jetpack' ) );
398 break;
399 case 'sync-checksum':
400 $option = 'jetpack_callables_sync_checksum';
401
402 if ( is_multisite() ) {
403 $offset = isset( $assoc_args['offset'] ) ? (int) $assoc_args['offset'] : 0;
404
405 /*
406 * 1000 is a good limit since we don't expect the number of sites to be more than 1000
407 * Offset can be used to paginate and try to clean up more sites.
408 */
409 $sites = get_sites(
410 array(
411 'number' => 1000,
412 'offset' => $offset,
413 )
414 );
415 $count_fixes = 0;
416 foreach ( $sites as $site ) {
417 switch_to_blog( $site->blog_id );
418 $count = self::count_option( $option );
419 if ( $count > 1 ) {
420 if ( ! $is_dry_run ) {
421 delete_option( $option );
422 }
423 WP_CLI::line(
424 sprintf(
425 /* translators: %1$d is a number, %2$s is the name of an option, %2$s is the site URL. */
426 __( 'Deleted %1$d %2$s options from %3$s', 'jetpack' ),
427 $count,
428 $option,
429 "{$site->domain}{$site->path}"
430 )
431 );
432 ++$count_fixes;
433 if ( ! $is_dry_run ) {
434 /*
435 * We could be deleting a lot of options rows at the same time.
436 * Allow some time for replication to catch up.
437 */
438 sleep( 3 );
439 }
440 }
441
442 restore_current_blog();
443 }
444 if ( $count_fixes ) {
445 WP_CLI::success(
446 sprintf(
447 /* translators: %1$s is the name of an option, %2$d is a number of sites. */
448 __( 'Successfully reset %1$s on %2$d sites.', 'jetpack' ),
449 $option,
450 $count_fixes
451 )
452 );
453 } else {
454 WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
455 }
456 return;
457 }
458
459 $count = self::count_option( $option );
460 if ( $count > 1 ) {
461 if ( ! $is_dry_run ) {
462 delete_option( $option );
463 }
464 WP_CLI::success(
465 sprintf(
466 /* translators: %1$d is a number, %2$s is the name of an option. */
467 __( 'Deleted %1$d %2$s options', 'jetpack' ),
468 $count,
469 $option
470 )
471 );
472 return;
473 }
474
475 WP_CLI::success( __( 'No options were deleted.', 'jetpack' ) );
476 break;
477
478 }
479 }
480
481 /**
482 * Return the number of times an option appears
483 * Normally an option would only appear 1 since the option key is supposed to be unique
484 * but if a site hasn't updated the DB schema then that would not be the case.
485 *
486 * @param string $option Option name.
487 *
488 * @return int
489 */
490 private static function count_option( $option ) {
491 global $wpdb;
492 return (int) $wpdb->get_var(
493 $wpdb->prepare(
494 "SELECT COUNT(*) FROM $wpdb->options WHERE option_name = %s",
495 $option
496 )
497 );
498 }
499
500 /**
501 * Manage Jetpack Modules
502 *
503 * ## OPTIONS
504 *
505 * <list|activate|deactivate|toggle>
506 * : The action to take.
507 * ---
508 * default: list
509 * options:
510 * - list
511 * - activate
512 * - deactivate
513 * - toggle
514 * ---
515 *
516 * [<module_slug>]
517 * : The slug of the module to perform an action on.
518 *
519 * [--format=<format>]
520 * : Allows overriding the output of the command when listing modules.
521 * ---
522 * default: table
523 * options:
524 * - table
525 * - json
526 * - csv
527 * - yaml
528 * - ids
529 * - count
530 * ---
531 *
532 * ## EXAMPLES
533 *
534 * wp jetpack module list
535 * wp jetpack module list --format=json
536 * wp jetpack module activate stats
537 * wp jetpack module deactivate stats
538 * wp jetpack module toggle stats
539 * wp jetpack module activate all
540 * wp jetpack module deactivate all
541 *
542 * @param array $args Positional args.
543 * @param array $assoc_args Named args.
544 */
545 public function module( $args, $assoc_args ) {
546 $action = isset( $args[0] ) ? $args[0] : 'list';
547
548 if ( isset( $args[1] ) ) {
549 $module_slug = $args[1];
550 if ( 'all' !== $module_slug && ! Jetpack::is_module( $module_slug ) ) {
551 /* translators: %s is a module slug like "stats" */
552 WP_CLI::error( sprintf( __( '%s is not a valid module.', 'jetpack' ), $module_slug ) );
553 }
554 if ( 'toggle' === $action ) {
555 $action = Jetpack::is_module_active( $module_slug )
556 ? 'deactivate'
557 : 'activate';
558 }
559 if ( 'all' === $args[1] ) {
560 $action = ( 'deactivate' === $action )
561 ? 'deactivate_all'
562 : 'activate_all';
563 }
564 } elseif ( 'list' !== $action ) {
565 WP_CLI::line( __( 'Please specify a valid module.', 'jetpack' ) );
566 $action = 'list';
567 }
568
569 switch ( $action ) {
570 case 'list':
571 $modules_list = array();
572 $modules = Jetpack::get_available_modules();
573 sort( $modules );
574 foreach ( (array) $modules as $module_slug ) {
575 if ( 'vaultpress' === $module_slug ) {
576 continue;
577 }
578 $modules_list[] = array(
579 'slug' => $module_slug,
580 'status' => Jetpack::is_module_active( $module_slug )
581 ? __( 'Active', 'jetpack' )
582 : __( 'Inactive', 'jetpack' ),
583 );
584 }
585 WP_CLI\Utils\format_items( $assoc_args['format'], $modules_list, array( 'slug', 'status' ) );
586 break;
587 case 'activate':
588 $module = Jetpack::get_module( $module_slug );
589 Jetpack::log( 'activate', $module_slug );
590 if ( Jetpack::activate_module( $module_slug, false, false ) ) {
591 /* translators: %s is the name of a Jetpack module */
592 WP_CLI::success( sprintf( __( '%s has been activated.', 'jetpack' ), $module['name'] ) );
593 } else {
594 /* translators: %s is the name of a Jetpack module */
595 WP_CLI::error( sprintf( __( '%s could not be activated.', 'jetpack' ), $module['name'] ) );
596 }
597 break;
598 case 'activate_all':
599 $modules = Jetpack::get_available_modules();
600 Jetpack::update_active_modules( $modules );
601 WP_CLI::success( __( 'All modules activated!', 'jetpack' ) );
602 break;
603 case 'deactivate':
604 $module = Jetpack::get_module( $module_slug );
605 Jetpack::log( 'deactivate', $module_slug );
606 Jetpack::deactivate_module( $module_slug );
607 /* translators: %s is the name of a Jetpack module */
608 WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) );
609 break;
610 case 'deactivate_all':
611 Jetpack::delete_active_modules();
612 WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) );
613 break;
614 case 'toggle':
615 // Will never happen, should have been handled above and changed to activate or deactivate.
616 break;
617 }
618 }
619
620 /**
621 * Manage Protect Settings
622 *
623 * ## OPTIONS
624 *
625 * allow: Add an IP address to an always allow list. You can also read or clear the allow list.
626 *
627 *
628 * ## EXAMPLES
629 *
630 * wp jetpack protect allow <ip address>
631 * wp jetpack protect allow list
632 * wp jetpack protect allow clear
633 *
634 * @synopsis <allow> [<ip|ip_low-ip_high|list|clear>]
635 *
636 * @param array $args Positional args.
637 */
638 public function protect( $args ) {
639 $action = isset( $args[0] ) ? $args[0] : 'prompt';
640 if ( ! in_array( $action, array( 'whitelist', 'allow' ), true ) ) { // Still allow "whitelist" for legacy support.
641 /* translators: %s is a command like "prompt" */
642 WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
643 }
644 // Check if module is active.
645 if ( ! Jetpack::is_module_active( __FUNCTION__ ) ) {
646 /* translators: %s is a module name */
647 WP_CLI::error( sprintf( _x( '%1$s is not active. You can activate it with "wp jetpack module activate %2$s"', '"wp jetpack module activate" is a command - do not translate', 'jetpack' ), __FUNCTION__, __FUNCTION__ ) );
648 }
649 if ( in_array( $action, array( 'allow', 'whitelist' ), true ) ) {
650 if ( isset( $args[1] ) ) {
651 $action = 'allow';
652 } else {
653 $action = 'prompt';
654 }
655 }
656 switch ( $action ) {
657 case 'allow':
658 $allow = array();
659 $new_ip = $args[1];
660 $current_allow = get_site_option( 'jetpack_protect_whitelist', array() ); // @todo Update the option name.
661
662 // Build array of IPs that are already on the allowed list.
663 // Re-build manually instead of using jetpack_protect_format_allow_list() so we can easily get
664 // low & high range params for IP_Utils::ip_address_is_in_range().
665 foreach ( $current_allow as $allowed ) {
666
667 // IP ranges.
668 if ( $allowed->range ) {
669
670 // Is it already on the allowed list?
671 if ( IP_Utils::ip_address_is_in_range( $new_ip, $allowed->range_low, $allowed->range_high ) ) {
672 /* translators: %s is an IP address */
673 WP_CLI::error( sprintf( __( '%s is already on the always allow list.', 'jetpack' ), $new_ip ) );
674 break;
675 }
676 $allow[] = $allowed->range_low . ' - ' . $allowed->range_high;
677
678 } else { // Individual IPs.
679
680 // Check if the IP is already on the allow list (single IP only).
681 if ( $new_ip === $allowed->ip_address ) {
682 /* translators: %s is an IP address */
683 WP_CLI::error( sprintf( __( '%s is already on the always allow list.', 'jetpack' ), $new_ip ) );
684 break;
685 }
686 $allow[] = $allowed->ip_address;
687
688 }
689 }
690
691 /*
692 * List the allowed IPs.
693 * Done here because it's easier to read the $allow array after it's been rebuilt.
694 */
695 if ( isset( $args[1] ) && 'list' === $args[1] ) {
696 if ( ! empty( $allow ) ) {
697 WP_CLI::success( __( 'Here are your always allowed IPs:', 'jetpack' ) );
698 foreach ( $allow as $ip ) {
699 WP_CLI::line( "\t" . str_pad( $ip, 24 ) );
700 }
701 } else {
702 WP_CLI::line( __( 'Always allow list is empty.', 'jetpack' ) );
703 }
704 break;
705 }
706
707 /*
708 * Clear the always allow list.
709 */
710 if ( isset( $args[1] ) && 'clear' === $args[1] ) {
711 if ( ! empty( $allow ) ) {
712 $allow = array();
713 Brute_Force_Protection_Shared_Functions::save_allow_list( $allow ); // @todo Need to update function name in the Protect module.
714 WP_CLI::success( __( 'Cleared all IPs from the always allow list.', 'jetpack' ) );
715 } else {
716 WP_CLI::line( __( 'Always allow list is empty.', 'jetpack' ) );
717 }
718 break;
719 }
720
721 // Append new IP to allow array.
722 array_push( $allow, $new_ip );
723
724 // Save allow list if there are no errors.
725 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $allow ); // @todo Need to update function name in the Protect module.
726 if ( is_wp_error( $result ) ) {
727 WP_CLI::error( $result );
728 }
729
730 /* translators: %s is an IP address */
731 WP_CLI::success( sprintf( __( '%s has been added to the always allowed list.', 'jetpack' ), $new_ip ) );
732 break;
733 case 'prompt':
734 WP_CLI::error(
735 __( 'No command found.', 'jetpack' ) . "\n" .
736 __( 'Please enter the IP address you want to always allow.', 'jetpack' ) . "\n" .
737 _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 add IP ranges - low_range/high_range should be translated.', 'jetpack' ) . "\n" .
738 _x( "You can also 'list' or 'clear' the always allowed list.", "'list' and 'clear' are commands and should not be translated", 'jetpack' ) . "\n"
739 );
740 break;
741 }
742 }
743
744 /**
745 * Manage Jetpack Options
746 *
747 * ## OPTIONS
748 *
749 * list : List all jetpack options and their values
750 * delete : Delete an option
751 * - can only delete options that are white listed.
752 * update : update an option
753 * - can only update option strings
754 * get : get the value of an option
755 *
756 * ## EXAMPLES
757 *
758 * wp jetpack options list
759 * wp jetpack options get <option_name>
760 * wp jetpack options delete <option_name>
761 * wp jetpack options update <option_name> [<option_value>]
762 *
763 * @synopsis <list|get|delete|update> [<option_name>] [<option_value>]
764 *
765 * @param array $args Positional args.
766 */
767 public function options( $args ) {
768 $action = isset( $args[0] ) ? $args[0] : 'list';
769 $safe_to_modify = Jetpack_Options::get_options_for_reset();
770
771 // Is the option flagged as unsafe?
772 $flagged = ! in_array( $args[1], $safe_to_modify, true );
773
774 if ( ! in_array( $action, array( 'list', 'get', 'delete', 'update' ), true ) ) {
775 /* translators: %s is a command like "prompt" */
776 WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
777 }
778
779 if ( isset( $args[0] ) ) {
780 if ( 'get' === $args[0] && isset( $args[1] ) ) {
781 $action = 'get';
782 } elseif ( 'delete' === $args[0] && isset( $args[1] ) ) {
783 $action = 'delete';
784 } elseif ( 'update' === $args[0] && isset( $args[1] ) ) {
785 $action = 'update';
786 } else {
787 $action = 'list';
788 }
789 }
790
791 // Bail if the option isn't found.
792 $option = isset( $args[1] ) ? Jetpack_Options::get_option( $args[1] ) : false;
793 if ( isset( $args[1] ) && ! $option && 'update' !== $args[0] ) {
794 WP_CLI::error( __( 'Option not found or is empty. Use "list" to list option names', 'jetpack' ) );
795 }
796
797 // Let's print_r the option if it's an array.
798 // Used in the 'get' and 'list' actions.
799 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
800 $option = is_array( $option ) ? print_r( $option, true ) : $option;
801
802 switch ( $action ) {
803 case 'get':
804 WP_CLI::success( "\t" . $option );
805 break;
806 case 'delete':
807 jetpack_cli_are_you_sure( $flagged );
808
809 Jetpack_Options::delete_option( $args[1] );
810 /* translators: %s is the option name */
811 WP_CLI::success( sprintf( __( 'Deleted option: %s', 'jetpack' ), $args[1] ) );
812 break;
813 case 'update':
814 jetpack_cli_are_you_sure( $flagged );
815
816 // Updating arrays would get pretty tricky...
817 $value = Jetpack_Options::get_option( $args[1] );
818 if ( $value && is_array( $value ) ) {
819 WP_CLI::error( __( 'Sorry, no updating arrays at this time', 'jetpack' ) );
820 }
821
822 Jetpack_Options::update_option( $args[1], $args[2] );
823 /* translators: %1$s is the previous value, %2$s is the new value */
824 WP_CLI::success( sprintf( _x( 'Updated option: %1$s to "%2$s"', 'Updating an option from "this" to "that".', 'jetpack' ), $args[1], $args[2] ) );
825 break;
826 case 'list':
827 $options_compact = Jetpack_Options::get_option_names();
828 $options_non_compact = Jetpack_Options::get_option_names( 'non_compact' );
829 $options_private = Jetpack_Options::get_option_names( 'private' );
830 $options = array_merge( $options_compact, $options_non_compact, $options_private );
831
832 // Table headers.
833 WP_CLI::line( "\t" . str_pad( __( 'Option', 'jetpack' ), 30 ) . __( 'Value', 'jetpack' ) );
834
835 // List out the options and their values.
836 // Tell them if the value is empty or not.
837 // Tell them if it's an array.
838 foreach ( $options as $option ) {
839 $value = Jetpack_Options::get_option( $option );
840 if ( ! $value ) {
841 WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Empty' );
842 continue;
843 }
844
845 if ( ! is_array( $value ) ) {
846 WP_CLI::line( "\t" . str_pad( $option, 30 ) . $value );
847 } elseif ( is_array( $value ) ) {
848 WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Array - Use "get <option>" to read option array.' );
849 }
850 }
851 $option_text = '{' . _x( 'option', 'a variable command that a user can write, provided in the printed instructions', 'jetpack' ) . '}';
852 $value_text = '{' . _x( 'value', 'the value that they want to update the option to', 'jetpack' ) . '}';
853
854 WP_CLI::success(
855 _x( "Above are your options. You may 'get', 'delete', and 'update' them.", "'get', 'delete', and 'update' are commands - do not translate.", 'jetpack' ) . "\n" .
856 str_pad( 'wp jetpack options get', 26 ) . $option_text . "\n" .
857 str_pad( 'wp jetpack options delete', 26 ) . $option_text . "\n" .
858 str_pad( 'wp jetpack options update', 26 ) . "$option_text $value_text\n" .
859 _x( "Type 'wp jetpack options' for more info.", "'wp jetpack options' is a command - do not translate.", 'jetpack' ) . "\n"
860 );
861 break;
862 }
863 }
864
865 /**
866 * Get the status of or start a new Jetpack sync.
867 *
868 * ## OPTIONS
869 *
870 * status : Print the current sync status
871 * settings : Prints the current sync settings
872 * start : Start a full sync from this site to WordPress.com
873 * enable : Enables sync on the site
874 * disable : Disable sync on a site
875 * reset : Disables sync and Resets the sync queues on a site
876 *
877 * ## EXAMPLES
878 *
879 * wp jetpack sync status
880 * wp jetpack sync settings
881 * wp jetpack sync start --modules=functions --sync_wait_time=5
882 * wp jetpack sync enable
883 * wp jetpack sync disable
884 * wp jetpack sync reset
885 * wp jetpack sync reset --queue=full or regular
886 *
887 * @synopsis <status|start> [--<field>=<value>]
888 *
889 * @param array $args Positional args.
890 * @param array $assoc_args Named args.
891 */
892 public function sync( $args, $assoc_args ) {
893
894 $action = isset( $args[0] ) ? $args[0] : 'status';
895
896 switch ( $action ) {
897 case 'status':
898 $status = Actions::get_sync_status();
899 $collection = array();
900 foreach ( $status as $key => $item ) {
901 $collection[] = array(
902 'option' => $key,
903 'value' => is_scalar( $item ) ? $item : wp_json_encode( $item ),
904 );
905 }
906 WP_CLI::log( __( 'Sync Status:', 'jetpack' ) );
907 WP_CLI\Utils\format_items( 'table', $collection, array( 'option', 'value' ) );
908 break;
909 case 'settings':
910 WP_CLI::log( __( 'Sync Settings:', 'jetpack' ) );
911 $settings = array();
912 foreach ( Settings::get_settings() as $setting => $item ) {
913 $settings[] = array(
914 'setting' => $setting,
915 'value' => is_scalar( $item ) ? $item : wp_json_encode( $item ),
916 );
917 }
918 WP_CLI\Utils\format_items( 'table', $settings, array( 'setting', 'value' ) );
919 break;
920 case 'disable':
921 // Don't set it via the Settings since that also resets the queues.
922 update_option( 'jetpack_sync_settings_disable', 1 );
923 /* translators: %s is the site URL */
924 WP_CLI::log( sprintf( __( 'Sync Disabled on %s', 'jetpack' ), get_site_url() ) );
925 break;
926 case 'enable':
927 Settings::update_settings( array( 'disable' => 0 ) );
928 /* translators: %s is the site URL */
929 WP_CLI::log( sprintf( __( 'Sync Enabled on %s', 'jetpack' ), get_site_url() ) );
930 break;
931 case 'reset':
932 // Don't set it via the Settings since that also resets the queues.
933 update_option( 'jetpack_sync_settings_disable', 1 );
934
935 /* translators: %s is the site URL */
936 WP_CLI::log( sprintf( __( 'Sync Disabled on %s. Use `wp jetpack sync enable` to enable syncing again.', 'jetpack' ), get_site_url() ) );
937 $listener = Listener::get_instance();
938 if ( empty( $assoc_args['queue'] ) ) {
939 $listener->get_sync_queue()->reset();
940 $listener->get_full_sync_queue()->reset();
941 /* translators: %s is the site URL */
942 WP_CLI::log( sprintf( __( 'Reset Full Sync and Regular Queues Queue on %s', 'jetpack' ), get_site_url() ) );
943 break;
944 }
945
946 if ( ! empty( $assoc_args['queue'] ) ) {
947 switch ( $assoc_args['queue'] ) {
948 case 'regular':
949 $listener->get_sync_queue()->reset();
950 /* translators: %s is the site URL */
951 WP_CLI::log( sprintf( __( 'Reset Regular Sync Queue on %s', 'jetpack' ), get_site_url() ) );
952 break;
953 case 'full':
954 $listener->get_full_sync_queue()->reset();
955 /* translators: %s is the site URL */
956 WP_CLI::log( sprintf( __( 'Reset Full Sync Queue on %s', 'jetpack' ), get_site_url() ) );
957 break;
958 default:
959 WP_CLI::error( __( 'Please specify what type of queue do you want to reset: `full` or `regular`.', 'jetpack' ) );
960 break;
961 }
962 }
963
964 break;
965 case 'start':
966 if ( ! Actions::sync_allowed() ) {
967 if ( Settings::get_setting( 'disable' ) ) {
968 WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. It is currently disabled. Run `wp jetpack sync enable` to enable it.', 'jetpack' ) );
969 return;
970 }
971 $connection = new Connection_Manager();
972 if ( ! $connection->is_connected() ) {
973 if ( ! doing_action( 'jetpack_site_registered' ) ) {
974 WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. Jetpack is not connected.', 'jetpack' ) );
975 return;
976 }
977 }
978
979 $status = new Status();
980
981 if ( $status->is_offline_mode() ) {
982 WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in offline mode.', 'jetpack' ) );
983 return;
984 }
985 if ( $status->is_staging_site() ) {
986 WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in staging mode.', 'jetpack' ) );
987 return;
988 }
989 }
990 // Get the original settings so that we can restore them later.
991 $original_settings = Settings::get_settings();
992
993 // Initialize sync settigns so we can sync as quickly as possible.
994 $sync_settings = wp_parse_args(
995 array_intersect_key( $assoc_args, Settings::$valid_settings ),
996 array(
997 'sync_wait_time' => 0,
998 'enqueue_wait_time' => 0,
999 'queue_max_writes_sec' => 10000,
1000 'max_queue_size_full_sync' => 100000,
1001 'full_sync_send_duration' => HOUR_IN_SECONDS,
1002 )
1003 );
1004 Settings::update_settings( $sync_settings );
1005
1006 // Convert comma-delimited string of modules to an array.
1007 if ( ! empty( $assoc_args['modules'] ) ) {
1008 $modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
1009
1010 // Convert the array so that the keys are the module name and the value is true to indicate
1011 // that we want to sync the module.
1012 $modules = array_map( '__return_true', array_flip( $modules ) );
1013 }
1014
1015 foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
1016 if (
1017 'users' === $module_name &&
1018 isset( $assoc_args[ $module_name ] ) &&
1019 'initial' === $assoc_args[ $module_name ]
1020 ) {
1021 $modules['users'] = 'initial';
1022 } elseif ( isset( $assoc_args[ $module_name ] ) ) {
1023 $ids = explode( ',', $assoc_args[ $module_name ] );
1024 if ( count( $ids ) > 0 ) {
1025 $modules[ $module_name ] = $ids;
1026 }
1027 }
1028 }
1029
1030 if ( empty( $modules ) ) {
1031 $modules = null;
1032 }
1033
1034 // Kick off a full sync.
1035 if ( Actions::do_full_sync( $modules ) ) {
1036 if ( $modules ) {
1037 /* translators: %s is a comma separated list of Jetpack modules */
1038 WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
1039 } else {
1040 WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
1041 }
1042 } else {
1043
1044 // Reset sync settings to original.
1045 Settings::update_settings( $original_settings );
1046
1047 if ( $modules ) {
1048 /* translators: %s is a comma separated list of Jetpack modules */
1049 WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
1050 } else {
1051 WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
1052 }
1053 }
1054
1055 // Keep sending to WPCOM until there's nothing to send.
1056 $i = 1;
1057 do {
1058 $result = Actions::$sender->do_full_sync();
1059 if ( is_wp_error( $result ) ) {
1060 $queue_empty_error = ( 'empty_queue_full_sync' === $result->get_error_code() );
1061 if ( ! $queue_empty_error || ( $queue_empty_error && ( 1 === $i ) ) ) {
1062 /* translators: %s is an error code */
1063 WP_CLI::error( sprintf( __( 'Sync errored with code: %s', 'jetpack' ), $result->get_error_code() ) );
1064 }
1065 } else {
1066 if ( 1 === $i ) {
1067 WP_CLI::log( __( 'Sent data to WordPress.com', 'jetpack' ) );
1068 } else {
1069 WP_CLI::log( __( 'Sent more data to WordPress.com', 'jetpack' ) );
1070 }
1071
1072 // Immediate Full Sync does not wait for WP.com to process data so we need to enforce a wait.
1073 if ( false !== strpos( get_class( Modules::get_module( 'full-sync' ) ), 'Full_Sync_Immediately' ) ) {
1074 sleep( 15 );
1075 }
1076 }
1077 ++$i;
1078 } while ( $result && ! is_wp_error( $result ) );
1079
1080 // Reset sync settings to original.
1081 Settings::update_settings( $original_settings );
1082
1083 WP_CLI::success( __( 'Finished syncing to WordPress.com', 'jetpack' ) );
1084 break;
1085 }
1086 }
1087
1088 /**
1089 * List the contents of a specific Jetpack sync queue.
1090 *
1091 * ## OPTIONS
1092 *
1093 * peek : List the 100 front-most items on the queue.
1094 *
1095 * ## EXAMPLES
1096 *
1097 * wp jetpack sync_queue full_sync peek
1098 *
1099 * @synopsis <incremental|full_sync> <peek>
1100 *
1101 * @param array $args Positional args.
1102 */
1103 public function sync_queue( $args ) {
1104 if ( ! Actions::sync_allowed() ) {
1105 WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
1106 }
1107
1108 $queue_name = isset( $args[0] ) ? $args[0] : 'sync';
1109 $action = isset( $args[1] ) ? $args[1] : 'peek';
1110
1111 // We map the queue name that way we can support more friendly queue names in the commands, but still use
1112 // the queue name that the code expects.
1113 $allowed_queues = array(
1114 'incremental' => 'sync',
1115 'full' => 'full_sync',
1116 );
1117 $queue_name_map = $allowed_queues;
1118 $mapped_queue_name = isset( $queue_name_map[ $queue_name ] ) ? $queue_name_map[ $queue_name ] : $queue_name;
1119
1120 switch ( $action ) {
1121 case 'peek':
1122 $queue = new Queue( $mapped_queue_name );
1123 $items = $queue->peek( 100 );
1124
1125 if ( empty( $items ) ) {
1126 /* translators: %s is the name of the queue, either 'incremental' or 'full' */
1127 WP_CLI::log( sprintf( __( 'Nothing is in the queue: %s', 'jetpack' ), $queue_name ) );
1128 } else {
1129 $collection = array();
1130 foreach ( $items as $item ) {
1131 $collection[] = array(
1132 'action' => $item[0],
1133 'args' => wp_json_encode( $item[1] ),
1134 'current_user_id' => $item[2],
1135 'microtime' => $item[3],
1136 'importing' => (string) $item[4],
1137 );
1138 }
1139 WP_CLI\Utils\format_items(
1140 'table',
1141 $collection,
1142 array(
1143 'action',
1144 'args',
1145 'current_user_id',
1146 'microtime',
1147 'importing',
1148 )
1149 );
1150 }
1151 break;
1152 }
1153 }
1154
1155 /**
1156 * Cancel's the current Jetpack plan granted by this partner, if applicable
1157 *
1158 * Returns success or error JSON
1159 *
1160 * <token_json>
1161 * : JSON blob of WPCOM API token
1162 * [--partner_tracking_id=<partner_tracking_id>]
1163 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1164 *
1165 * @synopsis <token_json> [--partner_tracking_id=<partner_tracking_id>]
1166 *
1167 * @param array $args Positional args.
1168 * @param array $named_args Named args.
1169 */
1170 public function partner_cancel( $args, $named_args ) {
1171 list( $token_json ) = $args;
1172
1173 $token = $token_json ? json_decode( $token_json ) : null;
1174 if ( ! $token ) {
1175 /* translators: %s is the invalid JSON string */
1176 $this->partner_provision_error( new WP_Error( 'missing_access_token', sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
1177 }
1178
1179 if ( isset( $token->error ) ) {
1180 $this->partner_provision_error( new WP_Error( $token->error, $token->message ) );
1181 }
1182
1183 if ( ! isset( $token->access_token ) ) {
1184 $this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
1185 }
1186
1187 if ( Identity_Crisis::validate_sync_error_idc_option() ) {
1188 $this->partner_provision_error(
1189 new WP_Error(
1190 'site_in_safe_mode',
1191 esc_html__( 'Can not cancel a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
1192 )
1193 );
1194 }
1195
1196 $site_identifier = Jetpack_Options::get_option( 'id' );
1197
1198 if ( ! $site_identifier ) {
1199 $status = new Status();
1200 $site_identifier = $status->get_site_suffix();
1201 }
1202
1203 $request = array(
1204 'headers' => array(
1205 'Authorization' => 'Bearer ' . $token->access_token,
1206 'Host' => 'public-api.wordpress.com',
1207 ),
1208 'timeout' => 60,
1209 'method' => 'POST',
1210 );
1211
1212 $url = sprintf( '%s/rest/v1.3/jpphp/%s/partner-cancel', $this->get_api_host(), $site_identifier );
1213 if ( ! empty( $named_args ) && ! empty( $named_args['partner_tracking_id'] ) ) {
1214 $url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
1215 }
1216
1217 $result = Client::_wp_remote_request( $url, $request );
1218
1219 Jetpack_Options::delete_option( 'onboarding' );
1220
1221 if ( is_wp_error( $result ) ) {
1222 $this->partner_provision_error( $result );
1223 }
1224
1225 WP_CLI::log( wp_remote_retrieve_body( $result ) );
1226 }
1227
1228 /**
1229 * Provision a site using a Jetpack Partner license
1230 *
1231 * Returns JSON blob
1232 *
1233 * ## OPTIONS
1234 *
1235 * <token_json>
1236 * : JSON blob of WPCOM API token
1237 * [--plan=<plan_name>]
1238 * : Slug of the requested plan, e.g. premium
1239 * [--wpcom_user_id=<user_id>]
1240 * : WordPress.com ID of user to connect as (must be whitelisted against partner key)
1241 * [--wpcom_user_email=<wpcom_user_email>]
1242 * : Override the email we send to WordPress.com for registration
1243 * [--onboarding=<onboarding>]
1244 * : Guide the user through an onboarding wizard
1245 * [--force_register=<register>]
1246 * : Whether to force a site to register
1247 * [--force_connect=<force_connect>]
1248 * : Force JPS to not reuse existing credentials
1249 * [--home_url=<home_url>]
1250 * : Overrides the home option via the home_url filter, or the WP_HOME constant
1251 * [--site_url=<site_url>]
1252 * : Overrides the siteurl option via the site_url filter, or the WP_SITEURL constant
1253 * [--partner_tracking_id=<partner_tracking_id>]
1254 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1255 *
1256 * ## EXAMPLES
1257 *
1258 * $ wp jetpack partner_provision '{ some: "json" }' premium 1
1259 * { success: true }
1260 *
1261 * @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>]
1262 *
1263 * @param array $args Positional args.
1264 * @param array $named_args Named args.
1265 */
1266 public function partner_provision( $args, $named_args ) {
1267 list( $token_json ) = $args;
1268
1269 $token = $token_json ? json_decode( $token_json ) : null;
1270 if ( ! $token ) {
1271 /* translators: %s is the invalid JSON string */
1272 $this->partner_provision_error( new WP_Error( 'missing_access_token', sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
1273 }
1274
1275 if ( isset( $token->error ) ) {
1276 $message = isset( $token->message )
1277 ? $token->message
1278 : '';
1279 $this->partner_provision_error( new WP_Error( $token->error, $message ) );
1280 }
1281
1282 if ( ! isset( $token->access_token ) ) {
1283 $this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
1284 }
1285
1286 require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
1287
1288 $body_json = Jetpack_Provision::partner_provision( $token->access_token, $named_args );
1289
1290 if ( is_wp_error( $body_json ) ) {
1291 WP_CLI::error(
1292 wp_json_encode(
1293 array(
1294 'success' => false,
1295 'error_code' => $body_json->get_error_code(),
1296 'error_message' => $body_json->get_error_message(),
1297 )
1298 )
1299 );
1300 exit( 1 );
1301 }
1302
1303 WP_CLI::log( wp_json_encode( $body_json ) );
1304 }
1305
1306 /**
1307 * Manages your Jetpack sitemap
1308 *
1309 * ## OPTIONS
1310 *
1311 * rebuild : Rebuild all sitemaps
1312 * --purge : if set, will remove all existing sitemap data before rebuilding
1313 *
1314 * ## EXAMPLES
1315 *
1316 * wp jetpack sitemap rebuild
1317 *
1318 * @subcommand sitemap
1319 * @synopsis <rebuild> [--purge]
1320 *
1321 * @param array $args Positional args.
1322 * @param array $assoc_args Named args.
1323 */
1324 public function sitemap( $args, $assoc_args ) {
1325 if ( ! Jetpack::is_connection_ready() ) {
1326 WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1327 }
1328 if ( ! Jetpack::is_module_active( 'sitemaps' ) ) {
1329 WP_CLI::error( __( 'Jetpack Sitemaps module is not currently active. Activate it first if you want to work with sitemaps.', 'jetpack' ) );
1330 }
1331 if ( ! class_exists( 'Jetpack_Sitemap_Builder' ) ) {
1332 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' ) );
1333 }
1334
1335 if ( isset( $assoc_args['purge'] ) && $assoc_args['purge'] ) {
1336 $librarian = new Jetpack_Sitemap_Librarian();
1337 $librarian->delete_all_stored_sitemap_data();
1338 }
1339
1340 $sitemap_builder = new Jetpack_Sitemap_Builder();
1341 $sitemap_builder->update_sitemap();
1342 }
1343
1344 /**
1345 * Allows authorizing a user via the command line and will activate
1346 *
1347 * ## EXAMPLES
1348 *
1349 * wp jetpack authorize_user --token=123456789abcdef
1350 *
1351 * @synopsis --token=<value>
1352 *
1353 * @param array $args Positional args.
1354 * @param array $named_args Named args.
1355 */
1356 public function authorize_user( $args, $named_args ) {
1357 if ( ! is_user_logged_in() ) {
1358 WP_CLI::error( __( 'Please select a user to authorize via the --user global argument.', 'jetpack' ) );
1359 }
1360
1361 if ( empty( $named_args['token'] ) ) {
1362 WP_CLI::error( __( 'A non-empty token argument must be passed.', 'jetpack' ) );
1363 }
1364
1365 $is_connection_owner = ! Jetpack::connection()->has_connected_owner();
1366 $current_user_id = get_current_user_id();
1367
1368 ( new Tokens() )->update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_connection_owner );
1369
1370 WP_CLI::log( wp_json_encode( $named_args ) );
1371
1372 if ( $is_connection_owner ) {
1373 /**
1374 * Auto-enable SSO module for new Jetpack Start connections
1375 *
1376 * @since 5.0.0
1377 *
1378 * @param bool $enable_sso Whether to enable the SSO module. Default to true.
1379 */
1380 $enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1381 Jetpack::handle_post_authorization_actions( $enable_sso, false );
1382
1383 /* translators: %d is a user ID */
1384 WP_CLI::success( sprintf( __( 'Authorized %d and activated default modules.', 'jetpack' ), $current_user_id ) );
1385 } else {
1386 /* translators: %d is a user ID */
1387 WP_CLI::success( sprintf( __( 'Authorized %d.', 'jetpack' ), $current_user_id ) );
1388 }
1389 }
1390
1391 /**
1392 * Allows calling a WordPress.com API endpoint using the current blog's token.
1393 *
1394 * ## OPTIONS
1395 * --resource=<resource>
1396 * : The resource to call with the current blog's token, where `%d` represents the current blog's ID.
1397 *
1398 * [--api_version=<api_version>]
1399 * : The API version to query against.
1400 *
1401 * [--base_api_path=<base_api_path>]
1402 * : The base API path to query.
1403 * ---
1404 * default: rest
1405 * ---
1406 *
1407 * [--body=<body>]
1408 * : A JSON encoded string representing arguments to send in the body.
1409 *
1410 * [--field=<value>]
1411 * : Any number of arguments that should be passed to the resource.
1412 *
1413 * [--pretty]
1414 * : Will pretty print the results of a successful API call.
1415 *
1416 * [--strip-success]
1417 * : Will remove the green success label from successful API calls.
1418 *
1419 * ## EXAMPLES
1420 *
1421 * wp jetpack call_api --resource='/sites/%d'
1422 *
1423 * @param array $args Positional args.
1424 * @param array $named_args Named args.
1425 */
1426 public function call_api( $args, $named_args ) {
1427 if ( ! Jetpack::is_connection_ready() ) {
1428 WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1429 }
1430
1431 $consumed_args = array(
1432 'resource',
1433 'api_version',
1434 'base_api_path',
1435 'body',
1436 'pretty',
1437 );
1438
1439 // Get args that should be passed to resource.
1440 $other_args = array_diff_key( $named_args, array_flip( $consumed_args ) );
1441
1442 $decoded_body = ! empty( $named_args['body'] )
1443 ? json_decode( $named_args['body'], true )
1444 : false;
1445
1446 $resource_url = ( false === strpos( $named_args['resource'], '%d' ) )
1447 ? $named_args['resource']
1448 : sprintf( $named_args['resource'], Jetpack_Options::get_option( 'id' ) );
1449
1450 $response = Client::wpcom_json_api_request_as_blog(
1451 $resource_url,
1452 empty( $named_args['api_version'] ) ? Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
1453 $other_args,
1454 empty( $decoded_body ) ? null : $decoded_body,
1455 empty( $named_args['base_api_path'] ) ? 'rest' : $named_args['base_api_path']
1456 );
1457
1458 if ( is_wp_error( $response ) ) {
1459 WP_CLI::error(
1460 sprintf(
1461 /* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
1462 __( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
1463 $resource_url,
1464 $response->get_error_code(),
1465 $response->get_error_message()
1466 )
1467 );
1468 }
1469
1470 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1471 WP_CLI::error(
1472 sprintf(
1473 /* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
1474 __( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
1475 $resource_url,
1476 wp_remote_retrieve_response_code( $response )
1477 )
1478 );
1479 }
1480
1481 $output = wp_remote_retrieve_body( $response );
1482 if ( isset( $named_args['pretty'] ) ) {
1483 $decoded_output = json_decode( $output );
1484 if ( $decoded_output ) {
1485 $output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
1486 }
1487 }
1488
1489 if ( isset( $named_args['strip-success'] ) ) {
1490 WP_CLI::log( $output );
1491 WP_CLI::halt( 0 );
1492 }
1493
1494 WP_CLI::success( $output );
1495 }
1496
1497 /**
1498 * Allows uploading SSH Credentials to the current site for backups, restores, and security scanning.
1499 *
1500 * ## OPTIONS
1501 *
1502 * [--host=<host>]
1503 * : The SSH server's address.
1504 *
1505 * [--ssh-user=<user>]
1506 * : The username to use to log in to the SSH server.
1507 *
1508 * [--pass=<pass>]
1509 * : The password used to log in, if using a password. (optional)
1510 *
1511 * [--kpri=<kpri>]
1512 * : The private key used to log in, if using a private key. (optional)
1513 *
1514 * [--pretty]
1515 * : Will pretty print the results of a successful API call. (optional)
1516 *
1517 * [--strip-success]
1518 * : Will remove the green success label from successful API calls. (optional)
1519 *
1520 * ## EXAMPLES
1521 *
1522 * wp jetpack upload_ssh_creds --host=example.com --ssh-user=example --pass=password
1523 * wp jetpack updload_ssh_creds --host=example.com --ssh-user=example --kpri=key
1524 *
1525 * @param array $args Positional args.
1526 * @param array $named_args Named args.
1527 */
1528 public function upload_ssh_creds( $args, $named_args ) {
1529 if ( ! Jetpack::is_connection_ready() ) {
1530 WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1531 }
1532
1533 $required_args = array(
1534 'host',
1535 'ssh-user',
1536 );
1537
1538 foreach ( $required_args as $arg ) {
1539 if ( empty( $named_args[ $arg ] ) ) {
1540 WP_CLI::error(
1541 sprintf(
1542 /* translators: %s is a slug, such as 'host'. */
1543 __( '`%s` cannot be empty.', 'jetpack' ),
1544 $arg
1545 )
1546 );
1547 }
1548 }
1549
1550 if ( empty( $named_args['pass'] ) && empty( $named_args['kpri'] ) ) {
1551 WP_CLI::error( __( 'Both `pass` and `kpri` fields cannot be blank.', 'jetpack' ) );
1552 }
1553
1554 $values = array(
1555 'credentials' => array(
1556 'site_url' => get_site_url(),
1557 'abspath' => ABSPATH,
1558 'protocol' => 'ssh',
1559 'port' => 22,
1560 'role' => 'main',
1561 'host' => $named_args['host'],
1562 'user' => $named_args['ssh-user'],
1563 'pass' => empty( $named_args['pass'] ) ? '' : $named_args['pass'],
1564 'kpri' => empty( $named_args['kpri'] ) ? '' : $named_args['kpri'],
1565 ),
1566 );
1567
1568 $named_args = wp_parse_args(
1569 array(
1570 'resource' => '/activity-log/%d/update-credentials',
1571 'method' => 'POST',
1572 'api_version' => '1.1',
1573 'body' => wp_json_encode( $values ),
1574 'timeout' => 30,
1575 ),
1576 $named_args
1577 );
1578
1579 self::call_api( $args, $named_args );
1580 }
1581
1582 /**
1583 * API wrapper for getting stats from the WordPress.com API for the current site.
1584 *
1585 * ## OPTIONS
1586 *
1587 * [--quantity=<quantity>]
1588 * : The number of units to include.
1589 * ---
1590 * default: 30
1591 * ---
1592 *
1593 * [--period=<period>]
1594 * : The unit of time to query stats for.
1595 * ---
1596 * default: day
1597 * options:
1598 * - day
1599 * - week
1600 * - month
1601 * - year
1602 * ---
1603 *
1604 * [--date=<date>]
1605 * : The latest date to return stats for. Ex. - 2018-01-01.
1606 *
1607 * [--pretty]
1608 * : Will pretty print the results of a successful API call.
1609 *
1610 * [--strip-success]
1611 * : Will remove the green success label from successful API calls.
1612 *
1613 * ## EXAMPLES
1614 *
1615 * wp jetpack get_stats
1616 *
1617 * @param array $args Positional args.
1618 * @param array $named_args Named args.
1619 */
1620 public function get_stats( $args, $named_args ) {
1621 $selected_args = array_intersect_key(
1622 $named_args,
1623 array_flip(
1624 array(
1625 'quantity',
1626 'date',
1627 )
1628 )
1629 );
1630
1631 // The API expects unit, but period seems to be more correct.
1632 $selected_args['unit'] = $named_args['period'];
1633
1634 $command = sprintf(
1635 'jetpack call_api --resource=/sites/%d/stats/%s',
1636 Jetpack_Options::get_option( 'id' ),
1637 add_query_arg( $selected_args, 'visits' )
1638 );
1639
1640 if ( isset( $named_args['pretty'] ) ) {
1641 $command .= ' --pretty';
1642 }
1643
1644 if ( isset( $named_args['strip-success'] ) ) {
1645 $command .= ' --strip-success';
1646 }
1647
1648 WP_CLI::runcommand(
1649 $command,
1650 array(
1651 'launch' => false, // Use the current process.
1652 )
1653 );
1654 }
1655
1656 /**
1657 * Allows management of publicize connections.
1658 *
1659 * ## OPTIONS
1660 *
1661 * <list|disconnect>
1662 * : The action to perform.
1663 * ---
1664 * options:
1665 * - list
1666 * - disconnect
1667 * ---
1668 *
1669 * [<identifier>]
1670 * : The connection ID or service to perform an action on.
1671 *
1672 * [--format=<format>]
1673 * : Allows overriding the output of the command when listing connections.
1674 * ---
1675 * default: table
1676 * options:
1677 * - table
1678 * - json
1679 * - csv
1680 * - yaml
1681 * - ids
1682 * - count
1683 * ---
1684 *
1685 * ## EXAMPLES
1686 *
1687 * # List all publicize connections.
1688 * $ wp jetpack publicize list
1689 *
1690 * # List publicize connections for a given service.
1691 * $ wp jetpack publicize list twitter
1692 *
1693 * # List all publicize connections for a given user.
1694 * $ wp --user=1 jetpack publicize list
1695 *
1696 * # List all publicize connections for a given user and service.
1697 * $ wp --user=1 jetpack publicize list twitter
1698 *
1699 * # Display details for a given connection.
1700 * $ wp jetpack publicize list 123456
1701 *
1702 * # Diconnection a given connection.
1703 * $ wp jetpack publicize disconnect 123456
1704 *
1705 * # Disconnect all connections.
1706 * $ wp jetpack publicize disconnect all
1707 *
1708 * # Disconnect all connections for a given service.
1709 * $ wp jetpack publicize disconnect twitter
1710 *
1711 * @param array $args Positional args.
1712 * @param array $named_args Named args.
1713 */
1714 public function publicize( $args, $named_args ) {
1715 if ( ! Jetpack::connection()->has_connected_owner() ) {
1716 WP_CLI::error( __( 'Jetpack Social requires a user-level connection to WordPress.com', 'jetpack' ) );
1717 }
1718
1719 if ( ! Jetpack::is_module_active( 'publicize' ) ) {
1720 WP_CLI::error( __( 'The Jetpack Social module is not active.', 'jetpack' ) );
1721 }
1722
1723 if ( ( new Status() )->is_offline_mode() ) {
1724 if (
1725 ! defined( 'JETPACK_DEV_DEBUG' ) &&
1726 ! has_filter( 'jetpack_development_mode' ) &&
1727 ! has_filter( 'jetpack_offline_mode' ) &&
1728 false === strpos( site_url(), '.' )
1729 ) {
1730 WP_CLI::error( __( "Jetpack is current in offline mode because the site url does not contain a '.', which often occurs when dynamically setting the WP_SITEURL constant. While in offline mode, the Jetpack Social module will not load.", 'jetpack' ) );
1731 }
1732
1733 WP_CLI::error( __( 'Jetpack is currently in offline mode, so the Jetpack Social module will not load.', 'jetpack' ) );
1734 }
1735
1736 if ( ! class_exists( 'Publicize' ) ) {
1737 WP_CLI::error( __( 'The Jetpack Social module is not loaded.', 'jetpack' ) );
1738 }
1739
1740 $action = $args[0];
1741 $publicize = new Publicize();
1742 $identifier = ! empty( $args[1] ) ? $args[1] : false;
1743 $services = array_keys( $publicize->get_services() );
1744 $id_is_service = in_array( $identifier, $services, true );
1745
1746 switch ( $action ) {
1747 case 'list':
1748 $connections_to_return = array();
1749
1750 // For the CLI command, let's return all connections when a user isn't specified. This
1751 // differs from the logic in the Publicize class.
1752 $option_connections = is_user_logged_in()
1753 ? (array) $publicize->get_all_connections_for_user()
1754 : (array) $publicize->get_all_connections();
1755
1756 foreach ( $option_connections as $service_name => $connections ) {
1757 foreach ( (array) $connections as $id => $connection ) {
1758 $connection['id'] = $id;
1759 $connection['service'] = $service_name;
1760 $connections_to_return[] = $connection;
1761 }
1762 }
1763
1764 if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
1765 $temp_connections = $connections_to_return;
1766 $connections_to_return = array();
1767
1768 foreach ( $temp_connections as $connection ) {
1769 if ( $identifier === $connection['service'] ) {
1770 $connections_to_return[] = $connection;
1771 }
1772 }
1773 }
1774
1775 if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
1776 $connections_to_return = wp_list_filter( $connections_to_return, array( 'id' => $identifier ) );
1777 }
1778
1779 $expected_keys = array(
1780 'id',
1781 'service',
1782 'user_id',
1783 'provider',
1784 'issued',
1785 'expires',
1786 'external_id',
1787 'external_name',
1788 'external_display',
1789 'type',
1790 'connection_data',
1791 );
1792
1793 // Somehow, a test site ended up in a state where $connections_to_return looked like:
1794 // array( array( array( 'id' => 0, 'service' => 0 ) ) ) // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1795 // This caused the CLI command to error when running WP_CLI\Utils\format_items() below. So
1796 // to minimize future issues, this nested loop will remove any connections that don't contain
1797 // any keys that we expect.
1798 foreach ( (array) $connections_to_return as $connection_key => $connection ) {
1799 foreach ( $expected_keys as $expected_key ) {
1800 if ( ! isset( $connection[ $expected_key ] ) ) {
1801 unset( $connections_to_return[ $connection_key ] );
1802 continue;
1803 }
1804 }
1805 }
1806
1807 if ( empty( $connections_to_return ) ) {
1808 return false;
1809 }
1810
1811 WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
1812 break; // list.
1813 case 'disconnect':
1814 if ( ! $identifier ) {
1815 WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
1816 }
1817
1818 // If the connection ID is 'all' then delete all connections. If the connection ID
1819 // matches a service, delete all connections for that service.
1820 if ( 'all' === $identifier || $id_is_service ) {
1821 if ( 'all' === $identifier ) {
1822 WP_CLI::log( __( "You're about to delete all Jetpack Social connections.", 'jetpack' ) );
1823 } else {
1824 /* translators: %s is a lowercase string for a social network. */
1825 WP_CLI::log( sprintf( __( "You're about to delete all Jetpack Social connections to %s.", 'jetpack' ), $identifier ) );
1826 }
1827
1828 jetpack_cli_are_you_sure();
1829
1830 $connections = array();
1831 $service = $identifier;
1832
1833 $option_connections = is_user_logged_in()
1834 ? (array) $publicize->get_all_connections_for_user()
1835 : (array) $publicize->get_all_connections();
1836
1837 if ( 'all' === $service ) {
1838 foreach ( (array) $option_connections as $service_name => $service_connections ) {
1839 foreach ( $service_connections as $id => $connection ) {
1840 $connections[ $id ] = $connection;
1841 }
1842 }
1843 } elseif ( ! empty( $option_connections[ $service ] ) ) {
1844 $connections = $option_connections[ $service ];
1845 }
1846
1847 if ( ! empty( $connections ) ) {
1848 $count = count( $connections );
1849 $progress = \WP_CLI\Utils\make_progress_bar(
1850 /* translators: %s is a lowercase string for a social network. */
1851 sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
1852 $count
1853 );
1854
1855 foreach ( $connections as $id => $connection ) {
1856 if ( false === $publicize->disconnect( false, $id ) ) {
1857 WP_CLI::error(
1858 sprintf(
1859 /* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
1860 __( 'Jetpack Social connection %d could not be disconnected', 'jetpack' ),
1861 $id
1862 )
1863 );
1864 }
1865
1866 $progress->tick();
1867 }
1868
1869 $progress->finish();
1870
1871 if ( 'all' === $service ) {
1872 WP_CLI::success( __( 'All Jetpack Social connections were successfully disconnected.', 'jetpack' ) );
1873 } else {
1874 /* translators: %s is a lowercase string for a social network. */
1875 WP_CLI::success( __( 'All Jetpack Social connections to %s were successfully disconnected.', 'jetpack' ), $service );
1876 }
1877 }
1878 } elseif ( false !== $publicize->disconnect( false, $identifier ) ) {
1879 /* translators: %d is a numeric ID. Example: 1234. */
1880 WP_CLI::success( sprintf( __( 'Jetpack Social connection %d has been disconnected.', 'jetpack' ), $identifier ) );
1881 } else {
1882 /* translators: %d is a numeric ID. Example: 1234. */
1883 WP_CLI::error( sprintf( __( 'Jetpack Social connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
1884 }
1885 break; // disconnect.
1886 }
1887 }
1888
1889 /**
1890 * Get the API host.
1891 *
1892 * @return string URL.
1893 */
1894 private function get_api_host() {
1895 $env_api_host = getenv( 'JETPACK_START_API_HOST', true );
1896 return $env_api_host ? 'https://' . $env_api_host : JETPACK__WPCOM_JSON_API_BASE;
1897 }
1898
1899 /**
1900 * Log and exit on a partner provision error.
1901 *
1902 * @param WP_Error $error Error.
1903 */
1904 private function partner_provision_error( $error ) {
1905 WP_CLI::log(
1906 wp_json_encode(
1907 array(
1908 'success' => false,
1909 'error_code' => $error->get_error_code(),
1910 'error_message' => $error->get_error_message(),
1911 )
1912 )
1913 );
1914 exit( 1 );
1915 }
1916
1917 /**
1918 * Creates the essential files in Jetpack to start building a Gutenberg block or plugin.
1919 *
1920 * ## TYPES
1921 *
1922 * block: it creates a Jetpack block. All files will be created in a directory under extensions/blocks named based on the block title or a specific given slug.
1923 *
1924 * ## BLOCK TYPE OPTIONS
1925 *
1926 * The first parameter is the block title and it's not associative. Add it wrapped in quotes.
1927 * The title is also used to create the slug and the edit PHP class name. If it's something like "Logo gallery", the slug will be 'logo-gallery' and the class name will be LogoGalleryEdit.
1928 * --slug: Specific slug to identify the block that overrides the one generated based on the title.
1929 * --description: Allows to provide a text description of the block.
1930 * --keywords: Provide up to three keywords separated by comma so users can find this block when they search in Gutenberg's inserter.
1931 * --variation: Allows to decide whether the block should be a production block, experimental, or beta. Defaults to Beta when arg not provided.
1932 *
1933 * ## BLOCK TYPE EXAMPLES
1934 *
1935 * wp jetpack scaffold block "Cool Block"
1936 * wp jetpack scaffold block "Amazing Rock" --slug="good-music" --description="Rock the best music on your site"
1937 * wp jetpack scaffold block "Jukebox" --keywords="music, audio, media"
1938 * wp jetpack scaffold block "Jukebox" --variation="experimental"
1939 *
1940 * @subcommand scaffold block
1941 * @synopsis <type> <title> [--slug] [--description] [--keywords] [--variation]
1942 *
1943 * @param array $args Positional parameters, when strings are passed, wrap them in quotes.
1944 * @param array $assoc_args Associative parameters like --slug="nice-block".
1945 */
1946 public function scaffold( $args, $assoc_args ) {
1947 // It's ok not to check if it's set, because otherwise WPCLI exits earlier.
1948 switch ( $args[0] ) {
1949 case 'block':
1950 $this->block( $args, $assoc_args );
1951 break;
1952 default:
1953 /* translators: %s is the subcommand */
1954 WP_CLI::error( sprintf( esc_html__( 'Invalid subcommand %s.', 'jetpack' ), $args[0] ) . ' 👻' );
1955 exit( 1 );
1956 }
1957 }
1958
1959 /**
1960 * Creates the essential files in Jetpack to build a Gutenberg block.
1961 *
1962 * @param array $args Positional parameters. Only one is used, that corresponds to the block title.
1963 * @param array $assoc_args Associative parameters defined in the scaffold() method.
1964 */
1965 public function block( $args, $assoc_args ) {
1966 if ( isset( $args[1] ) ) {
1967 $title = ucwords( $args[1] );
1968 } else {
1969 WP_CLI::error( esc_html__( 'The title parameter is required.', 'jetpack' ) . ' 👻' );
1970 exit( 1 );
1971 }
1972
1973 $slug = isset( $assoc_args['slug'] )
1974 ? $assoc_args['slug']
1975 : sanitize_title( $title );
1976
1977 $variation_options = array( 'production', 'experimental', 'beta' );
1978 $variation = ( isset( $assoc_args['variation'] ) && in_array( $assoc_args['variation'], $variation_options, true ) )
1979 ? $assoc_args['variation']
1980 : 'beta';
1981
1982 if ( preg_match( '#^jetpack/#', $slug ) ) {
1983 $slug = preg_replace( '#^jetpack/#', '', $slug );
1984 }
1985
1986 if ( ! preg_match( '/^[a-z][a-z0-9\-]*$/', $slug ) ) {
1987 WP_CLI::error( esc_html__( 'Invalid block slug. They can contain only lowercase alphanumeric characters or dashes, and start with a letter', 'jetpack' ) . ' 👻' );
1988 }
1989
1990 global $wp_filesystem;
1991 if ( ! WP_Filesystem() ) {
1992 WP_CLI::error( esc_html__( "Can't write files", 'jetpack' ) . ' 😱' );
1993 }
1994
1995 $path = JETPACK__PLUGIN_DIR . "extensions/blocks/$slug";
1996
1997 if ( $wp_filesystem->exists( $path ) && $wp_filesystem->is_dir( $path ) ) {
1998 /* translators: %s is path to the conflicting block */
1999 WP_CLI::error( sprintf( esc_html__( 'Name conflicts with the existing block %s', 'jetpack' ), $path ) . ' ⛔️' );
2000 exit( 1 );
2001 }
2002
2003 $wp_filesystem->mkdir( $path );
2004
2005 $has_keywords = isset( $assoc_args['keywords'] );
2006
2007 $files = array(
2008 "$path/$slug.php" => $this->render_block_file(
2009 'block-register-php',
2010 array(
2011 'slug' => $slug,
2012 'title' => $title,
2013 'underscoredSlug' => str_replace( '-', '_', $slug ),
2014 'underscoredTitle' => str_replace( ' ', '_', $title ),
2015 )
2016 ),
2017 "$path/index.js" => $this->render_block_file(
2018 'block-index-js',
2019 array(
2020 'slug' => $slug,
2021 'title' => $title,
2022 'description' => isset( $assoc_args['description'] )
2023 ? $assoc_args['description']
2024 : $title,
2025 'keywords' => $has_keywords
2026 ? array_map(
2027 function ( $keyword ) {
2028 // Construction necessary for Mustache lists.
2029 return array( 'keyword' => trim( $keyword ) );
2030 },
2031 explode( ',', $assoc_args['keywords'], 3 )
2032 )
2033 : '',
2034 'hasKeywords' => $has_keywords,
2035 )
2036 ),
2037 "$path/editor.js" => $this->render_block_file( 'block-editor-js' ),
2038 "$path/editor.scss" => $this->render_block_file(
2039 'block-editor-scss',
2040 array(
2041 'slug' => $slug,
2042 'title' => $title,
2043 )
2044 ),
2045 "$path/edit.js" => $this->render_block_file(
2046 'block-edit-js',
2047 array(
2048 'title' => $title,
2049 'className' => str_replace( ' ', '', ucwords( str_replace( '-', ' ', $slug ) ) ),
2050 )
2051 ),
2052 "$path/icon.js" => $this->render_block_file( 'block-icon-js' ),
2053 "$path/attributes.js" => $this->render_block_file( 'block-attributes-js' ),
2054 );
2055
2056 $files_written = array();
2057
2058 foreach ( $files as $filename => $contents ) {
2059 if ( $wp_filesystem->put_contents( $filename, $contents ) ) {
2060 $files_written[] = $filename;
2061 } else {
2062 /* translators: %s is a file name */
2063 WP_CLI::error( sprintf( esc_html__( 'Error creating %s', 'jetpack' ), $filename ) );
2064 }
2065 }
2066
2067 if ( empty( $files_written ) ) {
2068 WP_CLI::log( esc_html__( 'No files were created', 'jetpack' ) );
2069 } else {
2070 // Load index.json and insert the slug of the new block in its block variation array.
2071 $block_list_path = JETPACK__PLUGIN_DIR . 'extensions/index.json';
2072 $block_list = $wp_filesystem->get_contents( $block_list_path );
2073 if ( empty( $block_list ) ) {
2074 /* translators: %s is the path to the file with the block list */
2075 WP_CLI::error( sprintf( esc_html__( 'Error fetching contents of %s', 'jetpack' ), $block_list_path ) );
2076 } elseif ( false === stripos( $block_list, $slug ) ) {
2077 $new_block_list = json_decode( $block_list );
2078 $new_block_list->{ $variation }[] = $slug;
2079
2080 // Format the JSON to match our coding standards.
2081 $new_block_list_formatted = wp_json_encode( $new_block_list, JSON_PRETTY_PRINT ) . "\n";
2082 $new_block_list_formatted = preg_replace_callback(
2083 // Find all occurrences of multiples of 4 spaces a the start of the line.
2084 '/^((?: )+)/m',
2085 function ( $matches ) {
2086 // Replace each occurrence of 4 spaces with a tab character.
2087 return str_repeat( "\t", substr_count( $matches[0], ' ' ) );
2088 },
2089 $new_block_list_formatted
2090 );
2091
2092 if ( ! $wp_filesystem->put_contents( $block_list_path, $new_block_list_formatted ) ) {
2093 /* translators: %s is the path to the file with the block list */
2094 WP_CLI::error( sprintf( esc_html__( 'Error writing new %s', 'jetpack' ), $block_list_path ) );
2095 }
2096 }
2097
2098 if ( 'beta' === $variation || 'experimental' === $variation ) {
2099 $block_constant = sprintf(
2100 /* translators: the placeholder is a constant name */
2101 esc_html__( 'To load the block, add the constant JETPACK_BLOCKS_VARIATION set to %1$s to your wp-config.php file', 'jetpack' ),
2102 $variation
2103 );
2104 } else {
2105 $block_constant = '';
2106 }
2107
2108 WP_CLI::success(
2109 sprintf(
2110 /* translators: the placeholders are a human readable title, and a series of words separated by dashes */
2111 esc_html__( 'Successfully created block %1$s with slug %2$s', 'jetpack' ) . ' 🎉' . "\n" .
2112 "--------------------------------------------------------------------------------------------------------------------\n" .
2113 /* translators: the placeholder is a directory path */
2114 esc_html__( 'The files were created at %3$s', 'jetpack' ) . "\n" .
2115 esc_html__( 'To start using the block, build the blocks with pnpm run build-extensions', 'jetpack' ) . "\n" .
2116 /* translators: the placeholder is a file path */
2117 esc_html__( 'The block slug has been added to the %4$s list at %5$s', 'jetpack' ) . "\n" .
2118 '%6$s' . "\n" .
2119 /* translators: the placeholder is a URL */
2120 "\n" . esc_html__( 'Read more at %7$s', 'jetpack' ) . "\n",
2121 $title,
2122 $slug,
2123 $path,
2124 $variation,
2125 $block_list_path,
2126 $block_constant,
2127 'https://github.com/Automattic/jetpack/blob/trunk/projects/plugins/jetpack/extensions/README.md#developing-block-editor-extensions-in-jetpack'
2128 ) . '--------------------------------------------------------------------------------------------------------------------'
2129 );
2130 }
2131 }
2132
2133 /**
2134 * Built the file replacing the placeholders in the template with the data supplied.
2135 *
2136 * @param string $template Template.
2137 * @param array $data Data.
2138 * @return string mixed
2139 */
2140 private static function render_block_file( $template, $data = array() ) {
2141 return \WP_CLI\Utils\mustache_render( JETPACK__PLUGIN_DIR . "wp-cli-templates/$template.mustache", $data );
2142 }
2143 }
2144
2145 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move these functions to some other file.
2146
2147 /**
2148 * Standard "ask for permission to continue" function.
2149 * If action cancelled, ask if they need help.
2150 *
2151 * Written outside of the class so it's not listed as an executable command w/ 'wp jetpack'
2152 *
2153 * @param bool $flagged false = normal option | true = flagged by get_jetpack_options_for_reset().
2154 * @param string $error_msg Error message.
2155 */
2156 function jetpack_cli_are_you_sure( $flagged = false, $error_msg = false ) {
2157 $cli = new Jetpack_CLI();
2158
2159 // Default cancellation message.
2160 if ( ! $error_msg ) {
2161 $error_msg =
2162 __( 'Action cancelled. Have a question?', 'jetpack' )
2163 . ' '
2164 . $cli->green_open
2165 . 'jetpack.com/support'
2166 . $cli->color_close;
2167 }
2168
2169 if ( ! $flagged ) {
2170 $prompt_message = _x( 'Are you sure? This cannot be undone. Type "yes" to continue:', '"yes" is a command - do not translate.', 'jetpack' );
2171 } else {
2172 $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' );
2173 }
2174
2175 WP_CLI::line( $prompt_message );
2176 $handle = fopen( 'php://stdin', 'r' );
2177 $line = fgets( $handle );
2178 if ( 'yes' !== trim( $line ) ) {
2179 WP_CLI::error( $error_msg );
2180 }
2181 }
2182