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