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