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