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