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