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