Completed
Push — add/cli-block-generation ( b2c573...095ba0 )
by
unknown
19:06 queued 12:15
created

Jetpack_CLI::sync()   F

Complexity

Conditions 40
Paths 1312

Size

Total Lines 175

Duplication

Lines 45
Ratio 25.71 %

Importance

Changes 0
Metric Value
cc 40
nc 1312
nop 2
dl 45
loc 175
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
WP_CLI::add_command( 'jetpack', 'Jetpack_CLI' );
4
5
/**
6
 * Control your local Jetpack installation.
7
 *
8
 * Minimum PHP requirement for WP-CLI is PHP 5.3, so ignore PHP 5.2 compatibility issues.
9
 * @phpcs:disable PHPCompatibility.PHP.NewLanguageConstructs.t_ns_separatorFound
10
 */
11
class Jetpack_CLI extends WP_CLI_Command {
12
	// Aesthetics
13
	public $green_open  = "\033[32m";
14
	public $red_open    = "\033[31m";
15
	public $yellow_open = "\033[33m";
16
	public $color_close = "\033[0m";
17
18
	/**
19
	 * Get Jetpack Details
20
	 *
21
	 * ## OPTIONS
22
	 *
23
	 * empty: Leave it empty for basic stats
24
	 *
25
	 * full: View full stats.  It's the data from the heartbeat
26
	 *
27
	 * ## EXAMPLES
28
	 *
29
	 * wp jetpack status
30
	 * wp jetpack status full
31
	 *
32
	 */
33
	public function status( $args, $assoc_args ) {
34
		jetpack_require_lib( 'debugger' );
35
36
		WP_CLI::line( sprintf( __( 'Checking status for %s', 'jetpack' ), esc_url( get_home_url() ) ) );
37
38 View Code Duplication
		if ( isset( $args[0] ) && 'full' !== $args[0] ) {
39
			/* translators: %s is a command like "prompt" */
40
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $args[0] ) );
41
		}
42
43
		$master_user_email = Jetpack::get_master_user_email();
44
45
		$cxntests = new Jetpack_Cxn_Tests();
46
47
		if ( $cxntests->pass() ) {
48
			$cxntests->output_results_for_cli();
49
50
			WP_CLI::success( __( 'Jetpack is currently connected to WordPress.com', 'jetpack' ) );
51
		} else {
52
			$error = array();
53
			foreach ( $cxntests->list_fails() as $fail ) {
0 ignored issues
show
Bug introduced by
The expression $cxntests->list_fails() of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
54
				$error[] = $fail['name'] . ': ' . $fail['message'];
55
			}
56
			WP_CLI::error_multi_line( $error );
57
58
			$cxntests->output_results_for_cli();
59
60
			WP_CLI::error( __('Jetpack connection is broken.', 'jetpack' ) ); // Exit CLI.
61
		}
62
63
		WP_CLI::line( sprintf( __( 'The Jetpack Version is %s', 'jetpack' ), JETPACK__VERSION ) );
64
		WP_CLI::line( sprintf( __( 'The WordPress.com blog_id is %d', 'jetpack' ), Jetpack_Options::get_option( 'id' ) ) );
65
		WP_CLI::line( sprintf( __( 'The WordPress.com account for the primary connection is %s', 'jetpack' ), $master_user_email ) );
66
67
		/*
68
		 * Are they asking for all data?
69
		 *
70
		 * Loop through heartbeat data and organize by priority.
71
		 */
72
		$all_data = ( isset( $args[0] ) && 'full' == $args[0] ) ? 'full' : false;
73
		if ( $all_data ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $all_data of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
74
			// Heartbeat data
75
			WP_CLI::line( "\n" . __( 'Additional data: ', 'jetpack' ) );
76
77
			// Get the filtered heartbeat data.
78
			// Filtered so we can color/list by severity
79
			$stats = Jetpack::jetpack_check_heartbeat_data();
80
81
			// Display red flags first
82
			foreach ( $stats['bad'] as $stat => $value ) {
83
				printf( "$this->red_open%-'.16s %s $this->color_close\n", $stat, $value );
84
			}
85
86
			// Display caution warnings next
87
			foreach ( $stats['caution'] as $stat => $value ) {
88
				printf( "$this->yellow_open%-'.16s %s $this->color_close\n", $stat, $value );
89
			}
90
91
			// The rest of the results are good!
92
			foreach ( $stats['good'] as $stat => $value ) {
93
94
				// Modules should get special spacing for aestetics
95
				if ( strpos( $stat, 'odule-' ) ) {
96
					printf( "%-'.30s %s\n", $stat, $value );
97
					usleep( 4000 ); // For dramatic effect lolz
98
					continue;
99
				}
100
				printf( "%-'.16s %s\n", $stat, $value );
101
				usleep( 4000 ); // For dramatic effect lolz
102
			}
103
		} else {
104
			// Just the basics
105
			WP_CLI::line( "\n" . _x( "View full status with 'wp jetpack status full'", '"wp jetpack status full" is a command - do not translate', 'jetpack' ) );
106
		}
107
	}
108
109
	/**
110
	 * Tests the active connection
111
	 *
112
	 * Does a two-way test to verify that the local site can communicate with remote Jetpack/WP.com servers and that Jetpack/WP.com servers can talk to the local site.
113
	 *
114
	 * ## EXAMPLES
115
	 *
116
	 * wp jetpack test-connection
117
	 *
118
	 * @subcommand test-connection
119
	 */
120
	public function test_connection( $args, $assoc_args ) {
121
122
		WP_CLI::line( sprintf( __( 'Testing connection for %s', 'jetpack' ), esc_url( get_site_url() ) ) );
123
124
		if ( ! Jetpack::is_active() ) {
125
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
126
		}
127
128
		$response = Jetpack_Client::wpcom_json_api_request_as_blog(
129
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
130
			Jetpack_Client::WPCOM_JSON_API_VERSION
131
		);
132
133 View Code Duplication
		if ( is_wp_error( $response ) ) {
134
			/* translators: %1$s is the error code, %2$s is the error message */
135
			WP_CLI::error( sprintf( __( 'Failed to test connection (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ) );
136
		}
137
138
		$body = wp_remote_retrieve_body( $response );
139
		if ( ! $body ) {
140
			WP_CLI::error( __( 'Failed to test connection (empty response body)', 'jetpack' ) );
141
		}
142
143
		$result = json_decode( $body );
144
		$is_connected = (bool) $result->connected;
145
		$message = $result->message;
146
147
		if ( $is_connected ) {
148
			WP_CLI::success( $message );
149
		} else {
150
			WP_CLI::error( $message );
151
		}
152
	}
153
154
	/**
155
	 * Disconnect Jetpack Blogs or Users
156
	 *
157
	 * ## OPTIONS
158
	 *
159
	 * blog: Disconnect the entire blog.
160
	 *
161
	 * user <user_identifier>: Disconnect a specific user from WordPress.com.
162
	 *
163
	 * Please note, the primary account that the blog is connected
164
	 * to WordPress.com with cannot be disconnected without
165
	 * disconnecting the entire blog.
166
	 *
167
	 * ## EXAMPLES
168
	 *
169
	 * wp jetpack disconnect blog
170
	 * wp jetpack disconnect user 13
171
	 * wp jetpack disconnect user username
172
	 * wp jetpack disconnect user [email protected]
173
	 *
174
	 * @synopsis <blog|user> [<user_identifier>]
175
	 */
176
	public function disconnect( $args, $assoc_args ) {
177
		if ( ! Jetpack::is_active() ) {
178
			WP_CLI::error( __( 'You cannot disconnect, without having first connected.', 'jetpack' ) );
179
		}
180
181
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
182
		if ( ! in_array( $action, array( 'blog', 'user', 'prompt' ) ) ) {
183
			/* translators: %s is a command like "prompt" */
184
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
185
		}
186
187
		if ( in_array( $action, array( 'user' ) ) ) {
188
			if ( isset( $args[1] ) ) {
189
				$user_id = $args[1];
190
				if ( ctype_digit( $user_id ) ) {
191
					$field = 'id';
192
					$user_id = (int) $user_id;
193
				} elseif ( is_email( $user_id ) ) {
194
					$field = 'email';
195
					$user_id = sanitize_user( $user_id, true );
196
				} else {
197
					$field = 'login';
198
					$user_id = sanitize_user( $user_id, true );
199
				}
200
				if ( ! $user = get_user_by( $field, $user_id ) ) {
201
					WP_CLI::error( __( 'Please specify a valid user.', 'jetpack' ) );
202
				}
203
			} else {
204
				WP_CLI::error( __( 'Please specify a user by either ID, username, or email.', 'jetpack' ) );
205
			}
206
		}
207
208
		switch ( $action ) {
209
			case 'blog':
210
				Jetpack::log( 'disconnect' );
211
				Jetpack::disconnect();
212
				WP_CLI::success( sprintf(
213
					__( 'Jetpack has been successfully disconnected for %s.', 'jetpack' ),
214
					esc_url( get_site_url() )
215
				) );
216
				break;
217
			case 'user':
218
				if ( Jetpack::unlink_user( $user->ID ) ) {
219
					Jetpack::log( 'unlink', $user->ID );
0 ignored issues
show
Bug introduced by
The variable $user does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
220
					WP_CLI::success( __( 'User has been successfully disconnected.', 'jetpack' ) );
221
				} else {
222
					/* translators: %s is a username */
223
					WP_CLI::error( sprintf( __( "User %s could not be disconnected. Are you sure they're connected currently?", 'jetpack' ), "{$user->login} <{$user->email}>" ) );
224
				}
225
				break;
226
			case 'prompt':
227
				WP_CLI::error( __( 'Please specify if you would like to disconnect a blog or user.', 'jetpack' ) );
228
				break;
229
		}
230
	}
231
232
	/**
233
	 * Reset Jetpack options and settings to default
234
	 *
235
	 * ## OPTIONS
236
	 *
237
	 * modules: Resets modules to default state ( get_default_modules() )
238
	 *
239
	 * options: Resets all Jetpack options except:
240
	 *  - All private options (Blog token, user token, etc...)
241
	 *  - id (The Client ID/WP.com Blog ID of this site)
242
	 *  - master_user
243
	 *  - version
244
	 *  - activated
245
	 *
246
	 * ## EXAMPLES
247
	 *
248
	 * wp jetpack reset options
249
	 * wp jetpack reset modules
250
	 *
251
	 * @synopsis <modules|options>
252
	 */
253
	public function reset( $args, $assoc_args ) {
254
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
255 View Code Duplication
		if ( ! in_array( $action, array( 'options', 'modules' ) ) ) {
256
			/* translators: %s is a command like "prompt" */
257
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
258
		}
259
260
		// Are you sure?
261
		jetpack_cli_are_you_sure();
262
263
		switch ( $action ) {
264
			case 'options':
265
				$options_to_reset = Jetpack_Options::get_options_for_reset();
266
267
				// Reset the Jetpack options
268
				WP_CLI::line( sprintf(
269
					__( "Resetting Jetpack Options for %s...\n", "jetpack" ),
270
					esc_url( get_site_url() )
271
				) );
272
				sleep(1); // Take a breath
273
				foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
274
					Jetpack_Options::delete_option( $option_to_reset );
275
					usleep( 100000 );
276
					/* translators: This is the result of an action. The option named %s was reset */
277
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
278
				}
279
280
				// Reset the WP options
281
				WP_CLI::line( __( "Resetting the jetpack options stored in wp_options...\n", "jetpack" ) );
282
				usleep( 500000 ); // Take a breath
283
				foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
284
					delete_option( $option_to_reset );
285
					usleep( 100000 );
286
					/* translators: This is the result of an action. The option named %s was reset */
287
					WP_CLI::success( sprintf( __( '%s option reset', 'jetpack' ), $option_to_reset ) );
288
				}
289
290
				// Reset to default modules
291
				WP_CLI::line( __( "Resetting default modules...\n", "jetpack" ) );
292
				usleep( 500000 ); // Take a breath
293
				$default_modules = Jetpack::get_default_modules();
294
				Jetpack::update_active_modules( $default_modules );
295
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
296
297
				// Jumpstart option is special
298
				Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
299
				WP_CLI::success( __( 'jumpstart option reset', 'jetpack' ) );
300
				break;
301 View Code Duplication
			case 'modules':
302
				$default_modules = Jetpack::get_default_modules();
303
				Jetpack::update_active_modules( $default_modules );
304
				WP_CLI::success( __( 'Modules reset to default.', 'jetpack' ) );
305
				break;
306
			case 'prompt':
307
				WP_CLI::error( __( 'Please specify if you would like to reset your options, or modules', 'jetpack' ) );
308
				break;
309
		}
310
	}
311
312
	/**
313
	 * Manage Jetpack Modules
314
	 *
315
	 * ## OPTIONS
316
	 *
317
	 * <list|activate|deactivate|toggle>
318
	 * : The action to take.
319
	 * ---
320
	 * default: list
321
	 * options:
322
	 *  - list
323
	 *  - activate
324
	 *  - deactivate
325
	 *  - toggle
326
	 * ---
327
	 *
328
	 * [<module_slug>]
329
	 * : The slug of the module to perform an action on.
330
	 *
331
	 * [--format=<format>]
332
	 * : Allows overriding the output of the command when listing modules.
333
	 * ---
334
	 * default: table
335
	 * options:
336
	 *  - table
337
	 *  - json
338
	 *  - csv
339
	 *  - yaml
340
	 *  - ids
341
	 *  - count
342
	 * ---
343
	 *
344
	 * ## EXAMPLES
345
	 *
346
	 * wp jetpack module list
347
	 * wp jetpack module list --format=json
348
	 * wp jetpack module activate stats
349
	 * wp jetpack module deactivate stats
350
	 * wp jetpack module toggle stats
351
	 * wp jetpack module activate all
352
	 * wp jetpack module deactivate all
353
	 */
354
	public function module( $args, $assoc_args ) {
355
		$action = isset( $args[0] ) ? $args[0] : 'list';
356
357
		if ( isset( $args[1] ) ) {
358
			$module_slug = $args[1];
359
			if ( 'all' !== $module_slug && ! Jetpack::is_module( $module_slug ) ) {
360
				/* translators: %s is a module slug like "stats" */
361
				WP_CLI::error( sprintf( __( '%s is not a valid module.', 'jetpack' ), $module_slug ) );
362
			}
363
			if ( 'toggle' === $action ) {
364
				$action = Jetpack::is_module_active( $module_slug )
365
					? 'deactivate'
366
					: 'activate';
367
			}
368
			if ( 'all' === $args[1] ) {
369
				$action = ( 'deactivate' === $action )
370
					? 'deactivate_all'
371
					: 'activate_all';
372
			}
373
		} elseif ( 'list' !== $action ) {
374
			WP_CLI::line( __( 'Please specify a valid module.', 'jetpack' ) );
375
			$action = 'list';
376
		}
377
378
		switch ( $action ) {
379
			case 'list':
380
				$modules_list = array();
381
				$modules      = Jetpack::get_available_modules();
382
				sort( $modules );
383
				foreach ( (array) $modules as $module_slug ) {
384
					if ( 'vaultpress' === $module_slug ) {
385
						continue;
386
					}
387
					$modules_list[] = array(
388
						'slug'   => $module_slug,
389
						'status' => Jetpack::is_module_active( $module_slug )
390
							? __( 'Active', 'jetpack' )
391
							: __( 'Inactive', 'jetpack' ),
392
					);
393
				}
394
				WP_CLI\Utils\format_items( $assoc_args['format'], $modules_list, array( 'slug', 'status' ) );
395
				break;
396
			case 'activate':
397
				$module = Jetpack::get_module( $module_slug );
0 ignored issues
show
Bug introduced by
The variable $module_slug does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
398
				Jetpack::log( 'activate', $module_slug );
399
				if ( Jetpack::activate_module( $module_slug, false, false ) ) {
400
					WP_CLI::success( sprintf( __( '%s has been activated.', 'jetpack' ), $module['name'] ) );
401
				} else {
402
					WP_CLI::error( sprintf( __( '%s could not be activated.', 'jetpack' ), $module['name'] ) );
403
				}
404
				break;
405 View Code Duplication
			case 'activate_all':
406
				$modules = Jetpack::get_available_modules();
407
				Jetpack::update_active_modules( $modules );
408
				WP_CLI::success( __( 'All modules activated!', 'jetpack' ) );
409
				break;
410
			case 'deactivate':
411
				$module = Jetpack::get_module( $module_slug );
412
				Jetpack::log( 'deactivate', $module_slug );
413
				Jetpack::deactivate_module( $module_slug );
414
				WP_CLI::success( sprintf( __( '%s has been deactivated.', 'jetpack' ), $module['name'] ) );
415
				break;
416
			case 'deactivate_all':
417
				Jetpack::delete_active_modules();
418
				WP_CLI::success( __( 'All modules deactivated!', 'jetpack' ) );
419
				break;
420
			case 'toggle':
421
				// Will never happen, should have been handled above and changed to activate or deactivate.
422
				break;
423
		}
424
	}
425
426
	/**
427
	 * Manage Protect Settings
428
	 *
429
	 * ## OPTIONS
430
	 *
431
	 * whitelist: Whitelist an IP address.  You can also read or clear the whitelist.
432
	 *
433
	 *
434
	 * ## EXAMPLES
435
	 *
436
	 * wp jetpack protect whitelist <ip address>
437
	 * wp jetpack protect whitelist list
438
	 * wp jetpack protect whitelist clear
439
	 *
440
	 * @synopsis <whitelist> [<ip|ip_low-ip_high|list|clear>]
441
	 */
442
	public function protect( $args, $assoc_args ) {
443
		$action = isset( $args[0] ) ? $args[0] : 'prompt';
444
		if ( ! in_array( $action, array( 'whitelist' ) ) ) {
445
			/* translators: %s is a command like "prompt" */
446
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
447
		}
448
		// Check if module is active
449
		if ( ! Jetpack::is_module_active( __FUNCTION__ ) ) {
450
			WP_CLI::error( sprintf( _x( '%s is not active. You can activate it with "wp jetpack module activate %s"', '"wp jetpack module activate" is a command - do not translate', 'jetpack' ), __FUNCTION__, __FUNCTION__ ) );
451
		}
452
		if ( in_array( $action, array( 'whitelist' ) ) ) {
453
			if ( isset( $args[1] ) ) {
454
				$action = 'whitelist';
455
			} else {
456
				$action = 'prompt';
457
			}
458
		}
459
		switch ( $action ) {
460
			case 'whitelist':
461
				$whitelist         = array();
462
				$new_ip            = $args[1];
463
				$current_whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
464
465
				// Build array of IPs that are already whitelisted.
466
				// Re-build manually instead of using jetpack_protect_format_whitelist() so we can easily get
467
				// low & high range params for jetpack_protect_ip_address_is_in_range();
468
				foreach( $current_whitelist as $whitelisted ) {
469
470
					// IP ranges
471
					if ( $whitelisted->range ) {
472
473
						// Is it already whitelisted?
474
						if ( jetpack_protect_ip_address_is_in_range( $new_ip, $whitelisted->range_low, $whitelisted->range_high ) ) {
475
							/* translators: %s is an IP address */
476
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
477
							break;
478
						}
479
						$whitelist[] = $whitelisted->range_low . " - " . $whitelisted->range_high;
480
481
					} else { // Individual IPs
482
483
						// Check if the IP is already whitelisted (single IP only)
484
						if ( $new_ip == $whitelisted->ip_address ) {
485
							/* translators: %s is an IP address */
486
							WP_CLI::error( sprintf( __( '%s has already been whitelisted', 'jetpack' ), $new_ip ) );
487
							break;
488
						}
489
						$whitelist[] = $whitelisted->ip_address;
490
491
					}
492
				}
493
494
				/*
495
				 * List the whitelist
496
				 * Done here because it's easier to read the $whitelist array after it's been rebuilt
497
				 */
498
				if ( isset( $args[1] ) && 'list' == $args[1] ) {
499
					if ( ! empty( $whitelist ) ) {
500
						WP_CLI::success( __( 'Here are your whitelisted IPs:', 'jetpack' ) );
501
						foreach ( $whitelist as $ip ) {
502
							WP_CLI::line( "\t" . str_pad( $ip, 24 ) ) ;
503
						}
504
					} else {
505
						WP_CLI::line( __( 'Whitelist is empty.', "jetpack" ) ) ;
506
					}
507
					break;
508
				}
509
510
				/*
511
				 * Clear the whitelist
512
				 */
513
				if ( isset( $args[1] ) && 'clear' == $args[1] ) {
514
					if ( ! empty( $whitelist ) ) {
515
						$whitelist = array();
516
						jetpack_protect_save_whitelist( $whitelist );
517
						WP_CLI::success( __( 'Cleared all whitelisted IPs', 'jetpack' ) );
518
					} else {
519
						WP_CLI::line( __( 'Whitelist is empty.', "jetpack" ) ) ;
520
					}
521
					break;
522
				}
523
524
				// Append new IP to whitelist array
525
				array_push( $whitelist, $new_ip );
526
527
				// Save whitelist if there are no errors
528
				$result = jetpack_protect_save_whitelist( $whitelist );
529
				if ( is_wp_error( $result ) ) {
530
					WP_CLI::error( __( $result, 'jetpack' ) );
531
				}
532
533
				/* translators: %s is an IP address */
534
				WP_CLI::success( sprintf( __( '%s has been whitelisted.', 'jetpack' ), $new_ip ) );
535
				break;
536
			case 'prompt':
537
				WP_CLI::error(
538
					__( 'No command found.', 'jetpack' ) . "\n" .
539
					__( 'Please enter the IP address you want to whitelist.', 'jetpack' ) . "\n" .
540
					_x( 'You can save a range of IPs {low_range}-{high_range}. No spaces allowed.  (example: 1.1.1.1-2.2.2.2)', 'Instructions on how to whitelist IP ranges - low_range/high_range should be translated.', 'jetpack' ) . "\n" .
541
					_x( "You can also 'list' or 'clear' the whitelist.", "'list' and 'clear' are commands and should not be translated", 'jetpack' ) . "\n"
542
				);
543
				break;
544
		}
545
	}
546
547
	/**
548
	 * Manage Jetpack Options
549
	 *
550
	 * ## OPTIONS
551
	 *
552
	 * list   : List all jetpack options and their values
553
	 * delete : Delete an option
554
	 *          - can only delete options that are white listed.
555
	 * update : update an option
556
	 *          - can only update option strings
557
	 * get    : get the value of an option
558
	 *
559
	 * ## EXAMPLES
560
	 *
561
	 * wp jetpack options list
562
	 * wp jetpack options get    <option_name>
563
	 * wp jetpack options delete <option_name>
564
	 * wp jetpack options update <option_name> [<option_value>]
565
	 *
566
	 * @synopsis <list|get|delete|update> [<option_name>] [<option_value>]
567
	 */
568
	public function options( $args, $assoc_args ) {
569
		$action = isset( $args[0] ) ? $args[0] : 'list';
570
		$safe_to_modify = Jetpack_Options::get_options_for_reset();
571
572
		// Jumpstart is special
573
		array_push( $safe_to_modify, 'jumpstart' );
574
575
		// Is the option flagged as unsafe?
576
		$flagged = ! in_array( $args[1], $safe_to_modify );
577
578 View Code Duplication
		if ( ! in_array( $action, array( 'list', 'get', 'delete', 'update' ) ) ) {
579
			/* translators: %s is a command like "prompt" */
580
			WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack' ), $action ) );
581
		}
582
583
		if ( isset( $args[0] ) ) {
584
			if ( 'get' == $args[0] && isset( $args[1] ) ) {
585
				$action = 'get';
586
			} else if ( 'delete' == $args[0] && isset( $args[1] ) ) {
587
				$action = 'delete';
588 View Code Duplication
			} else if ( 'update' == $args[0] && isset( $args[1] ) ) {
589
				$action = 'update';
590
			} else {
591
				$action = 'list';
592
			}
593
		}
594
595
		// Bail if the option isn't found
596
		$option = isset( $args[1] ) ? Jetpack_Options::get_option( $args[1] ) : false;
597 View Code Duplication
		if ( isset( $args[1] ) && ! $option && 'update' !== $args[0] ) {
598
			WP_CLI::error( __( 'Option not found or is empty.  Use "list" to list option names', 'jetpack' ) );
599
		}
600
601
		// Let's print_r the option if it's an array
602
		// Used in the 'get' and 'list' actions
603
		$option = is_array( $option ) ? print_r( $option ) : $option;
604
605
		switch ( $action ) {
606
			case 'get':
607
				WP_CLI::success( "\t" . $option );
608
				break;
609
			case 'delete':
610
				jetpack_cli_are_you_sure( $flagged );
611
612
				Jetpack_Options::delete_option( $args[1] );
613
				WP_CLI::success( sprintf( __( 'Deleted option: %s', 'jetpack' ), $args[1] ) );
614
				break;
615
			case 'update':
616
				jetpack_cli_are_you_sure( $flagged );
617
618
				// Updating arrays would get pretty tricky...
619
				$value = Jetpack_Options::get_option( $args[1] );
620
				if ( $value && is_array( $value ) ) {
621
					WP_CLI::error( __( 'Sorry, no updating arrays at this time', 'jetpack' ) );
622
				}
623
624
				Jetpack_Options::update_option( $args[1], $args[2] );
625
				WP_CLI::success( sprintf( _x( 'Updated option: %s to "%s"', 'Updating an option from "this" to "that".', 'jetpack' ), $args[1], $args[2] ) );
626
				break;
627
			case 'list':
628
				$options_compact     = Jetpack_Options::get_option_names();
629
				$options_non_compact = Jetpack_Options::get_option_names( 'non_compact' );
630
				$options_private     = Jetpack_Options::get_option_names( 'private' );
631
				$options             = array_merge( $options_compact, $options_non_compact, $options_private );
632
633
				// Table headers
634
				WP_CLI::line( "\t" . str_pad( __( 'Option', 'jetpack' ), 30 ) . __( 'Value', 'jetpack' ) );
635
636
				// List out the options and their values
637
				// Tell them if the value is empty or not
638
				// Tell them if it's an array
639
				foreach ( $options as $option ) {
640
					$value = Jetpack_Options::get_option( $option );
641
					if ( ! $value ) {
642
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Empty' );
643
						continue;
644
					}
645
646
					if ( ! is_array( $value ) ) {
647
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . $value );
648
					} else if ( is_array( $value ) ) {
649
						WP_CLI::line( "\t" . str_pad( $option, 30 ) . 'Array - Use "get <option>" to read option array.' );
650
					}
651
				}
652
				$option_text = '{' . _x( 'option', 'a variable command that a user can write, provided in the printed instructions', 'jetpack' ) . '}';
653
				$value_text  = '{' . _x( 'value', 'the value that they want to update the option to', 'jetpack' ) . '}';
654
655
				WP_CLI::success(
656
					_x( "Above are your options. You may 'get', 'delete', and 'update' them.", "'get', 'delete', and 'update' are commands - do not translate.", 'jetpack' ) . "\n" .
657
					str_pad( 'wp jetpack options get', 26 )    . $option_text . "\n" .
658
					str_pad( 'wp jetpack options delete', 26 ) . $option_text . "\n" .
659
					str_pad( 'wp jetpack options update', 26 ) . "$option_text $value_text" . "\n" .
660
					_x( "Type 'wp jetpack options' for more info.", "'wp jetpack options' is a command - do not translate.", 'jetpack' ) . "\n"
661
				);
662
				break;
663
		}
664
	}
665
666
	/**
667
	 * Get the status of or start a new Jetpack sync.
668
	 *
669
	 * ## OPTIONS
670
	 *
671
	 * status   : Print the current sync status
672
	 * settings : Prints the current sync settings
673
	 * start    : Start a full sync from this site to WordPress.com
674
	 * enable   : Enables sync on the site
675
	 * disable  : Disable sync on a site
676
	 * reset    : Disables sync and Resets the sync queues on a site
677
	 *
678
	 * ## EXAMPLES
679
	 *
680
	 * wp jetpack sync status
681
	 * wp jetpack sync settings
682
	 * wp jetpack sync start --modules=functions --sync_wait_time=5
683
	 * wp jetpack sync enable
684
	 * wp jetpack sync disable
685
	 * wp jetpack sync reset
686
	 * wp jetpack sync reset --queue=full or regular
687
	 *
688
	 * @synopsis <status|start> [--<field>=<value>]
689
	 */
690
	public function sync( $args, $assoc_args ) {
691
692
		$action = isset( $args[0] ) ? $args[0] : 'status';
693
694
		switch ( $action ) {
695
			case 'status':
696
				$status = Jetpack_Sync_Actions::get_sync_status();
697
				$collection = array();
698
				foreach ( $status as $key => $item ) {
699
					$collection[]  = array(
700
						'option' => $key,
701
						'value' => is_scalar( $item ) ? $item : json_encode( $item )
702
					);
703
				}
704
				WP_CLI::log( __( 'Sync Status:', 'jetpack' ) );
705
				WP_CLI\Utils\format_items( 'table', $collection, array( 'option', 'value' ) );
706
				break;
707
			case 'settings':
708
				WP_CLI::log( __( 'Sync Settings:', 'jetpack' ) );
709
				foreach( Jetpack_Sync_Settings::get_settings() as $setting => $item ) {
710
					$settings[]  = array(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$settings was never initialized. Although not strictly required by PHP, it is generally a good practice to add $settings = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
711
						'setting' => $setting,
712
						'value' => is_scalar( $item ) ? $item : json_encode( $item )
713
					);
714
				}
715
				WP_CLI\Utils\format_items( 'table', $settings, array( 'setting', 'value' ) );
0 ignored issues
show
Bug introduced by
The variable $settings does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
716
717
			case 'disable':
718
				// Don't set it via the Jetpack_Sync_Settings since that also resets the queues.
719
				update_option( 'jetpack_sync_settings_disable', 1 );
720
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s', 'jetpack' ), get_site_url() ) );
721
				break;
722
			case 'enable':
723
				Jetpack_Sync_Settings::update_settings( array( 'disable' => 0 ) );
724
				WP_CLI::log( sprintf( __( 'Sync Enabled on %s', 'jetpack' ), get_site_url() ) );
725
				break;
726
			case 'reset':
727
				// Don't set it via the Jetpack_Sync_Settings since that also resets the queues.
728
				update_option( 'jetpack_sync_settings_disable', 1 );
729
730
				WP_CLI::log( sprintf( __( 'Sync Disabled on %s. Use `wp jetpack sync enable` to enable syncing again.', 'jetpack' ), get_site_url() ) );
731
				require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-listener.php';
732
				$listener = Jetpack_Sync_Listener::get_instance();
733
				if ( empty( $assoc_args['queue'] ) ) {
734
					$listener->get_sync_queue()->reset();
735
					$listener->get_full_sync_queue()->reset();
736
					WP_CLI::log( sprintf( __( 'Reset Full Sync and Regular Queues Queue on %s', 'jetpack' ), get_site_url() ) );
737
					break;
738
				}
739
740
				if ( ! empty( $assoc_args['queue'] ) ) {
741
					switch ( $assoc_args['queue'] ) {
742 View Code Duplication
						case 'regular':
743
							$listener->get_sync_queue()->reset();
744
							WP_CLI::log( sprintf( __( 'Reset Regular Sync Queue on %s', 'jetpack' ), get_site_url() ) );
745
							break;
746 View Code Duplication
						case 'full':
747
							$listener->get_full_sync_queue()->reset();
748
							WP_CLI::log( sprintf( __( 'Reset Full Sync Queue on %s', 'jetpack' ), get_site_url() ) );
749
							break;
750
						default:
751
							WP_CLI::error( __( 'Please specify what type of queue do you want to reset: `full` or `regular`.', 'jetpack' ) );
752
							break;
753
					}
754
				}
755
756
				break;
757
			case 'start':
758
				if ( ! Jetpack_Sync_Actions::sync_allowed() ) {
759
					if( ! Jetpack_Sync_Settings::get_setting( 'disable' ) ) {
760
						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' ) );
761
						return;
762
					}
763
					if ( doing_action( 'jetpack_user_authorized' ) || Jetpack::is_active() ) {
764
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. Jetpack is not connected.', 'jetpack' ) );
765
						return;
766
					}
767
					if ( Jetpack::is_development_mode() ) {
768
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in development mode.', 'jetpack' ) );
769
						return;
770
					}
771
					if (  Jetpack::is_staging_site() ) {
772
						WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site. The site is in staging mode.', 'jetpack' ) );
773
						return;
774
					}
775
776
				}
777
				// Get the original settings so that we can restore them later
778
				$original_settings = Jetpack_Sync_Settings::get_settings();
779
780
				// Initialize sync settigns so we can sync as quickly as possible
781
				$sync_settings = wp_parse_args(
782
					array_intersect_key( $assoc_args, Jetpack_Sync_Settings::$valid_settings ),
0 ignored issues
show
Bug introduced by
The property valid_settings cannot be accessed from this context as it is declared private in class Jetpack_Sync_Settings.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
783
					array(
784
						'sync_wait_time' => 0,
785
						'enqueue_wait_time' => 0,
786
						'queue_max_writes_sec' => 10000,
787
						'max_queue_size_full_sync' => 100000
788
					)
789
				);
790
				Jetpack_Sync_Settings::update_settings( $sync_settings );
791
792
				// Convert comma-delimited string of modules to an array
793 View Code Duplication
				if ( ! empty( $assoc_args['modules'] ) ) {
794
					$modules = array_map( 'trim', explode( ',', $assoc_args['modules'] ) );
795
796
					// Convert the array so that the keys are the module name and the value is true to indicate
797
					// that we want to sync the module
798
					$modules = array_map( '__return_true', array_flip( $modules ) );
799
				}
800
801 View Code Duplication
				foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
802
					if (
803
						'users' === $module_name &&
804
						isset( $assoc_args[ $module_name ] ) &&
805
						'initial' === $assoc_args[ $module_name ]
806
					) {
807
						$modules[ 'users' ] = 'initial';
0 ignored issues
show
Bug introduced by
The variable $modules does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
808
					} elseif ( isset( $assoc_args[ $module_name ] ) ) {
809
						$ids = explode( ',', $assoc_args[ $module_name ] );
810
						if ( count( $ids ) > 0 ) {
811
							$modules[ $module_name ] = $ids;
812
						}
813
					}
814
				}
815
816
				if ( empty( $modules ) ) {
817
					$modules = null;
818
				}
819
820
				// Kick off a full sync
821
				if ( Jetpack_Sync_Actions::do_full_sync( $modules ) ) {
822 View Code Duplication
					if ( $modules ) {
823
						WP_CLI::log( sprintf( __( 'Initialized a new full sync with modules: %s', 'jetpack' ), join( ', ', array_keys( $modules ) ) ) );
824
					} else {
825
						WP_CLI::log( __( 'Initialized a new full sync', 'jetpack' ) );
826
					}
827 View Code Duplication
				} else {
828
829
					// Reset sync settings to original.
830
					Jetpack_Sync_Settings::update_settings( $original_settings );
831
832
					if ( $modules ) {
833
						WP_CLI::error( sprintf( __( 'Could not start a new full sync with modules: %s', 'jetpack' ), join( ', ', $modules ) ) );
834
					} else {
835
						WP_CLI::error( __( 'Could not start a new full sync', 'jetpack' ) );
836
					}
837
				}
838
839
				// Keep sending to WPCOM until there's nothing to send
840
				$i = 1;
841
				do {
842
					$result = Jetpack_Sync_Actions::$sender->do_full_sync();
0 ignored issues
show
Bug introduced by
The property sender cannot be accessed from this context as it is declared private in class Jetpack_Sync_Actions.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
843
					if ( is_wp_error( $result ) ) {
844
						$queue_empty_error = ( 'empty_queue_full_sync' == $result->get_error_code() );
845
						if ( ! $queue_empty_error || ( $queue_empty_error && ( 1 == $i ) ) ) {
846
							WP_CLI::error( sprintf( __( 'Sync errored with code: %s', 'jetpack' ), $result->get_error_code() ) );
847
						}
848
					} else {
849
						if ( 1 == $i ) {
850
							WP_CLI::log( __( 'Sent data to WordPress.com', 'jetpack' ) );
851
						} else {
852
							WP_CLI::log( __( 'Sent more data to WordPress.com', 'jetpack' ) );
853
						}
854
					}
855
					$i++;
856
				} while ( $result && ! is_wp_error( $result ) );
857
858
				// Reset sync settings to original.
859
				Jetpack_Sync_Settings::update_settings( $original_settings );
860
861
				WP_CLI::success( __( 'Finished syncing to WordPress.com', 'jetpack' ) );
862
				break;
863
		}
864
	}
865
866
	/**
867
	 * List the contents of a specific Jetpack sync queue.
868
	 *
869
	 * ## OPTIONS
870
	 *
871
	 * peek : List the 100 front-most items on the queue.
872
	 *
873
	 * ## EXAMPLES
874
	 *
875
	 * wp jetpack sync_queue full_sync peek
876
	 *
877
	 * @synopsis <incremental|full_sync> <peek>
878
	 */
879
	public function sync_queue( $args, $assoc_args ) {
880
		if ( ! Jetpack_Sync_Actions::sync_allowed() ) {
881
			WP_CLI::error( __( 'Jetpack sync is not currently allowed for this site.', 'jetpack' ) );
882
		}
883
884
		$queue_name = isset( $args[0] ) ? $args[0] : 'sync';
885
		$action = isset( $args[1] ) ? $args[1] : 'peek';
886
887
		// We map the queue name that way we can support more friendly queue names in the commands, but still use
888
		// the queue name that the code expects.
889
		$queue_name_map = $allowed_queues = array(
0 ignored issues
show
Unused Code introduced by
$allowed_queues is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
890
			'incremental' => 'sync',
891
			'full'        => 'full_sync',
892
		);
893
		$mapped_queue_name = isset( $queue_name_map[ $queue_name ] ) ? $queue_name_map[ $queue_name ] : $queue_name;
894
895
		switch( $action ) {
896
			case 'peek':
897
				require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-queue.php';
898
				$queue = new Jetpack_Sync_Queue( $mapped_queue_name );
899
				$items = $queue->peek( 100 );
900
901
				if ( empty( $items ) ) {
902
					/* translators: %s is the name of the queue, either 'incremental' or 'full' */
903
					WP_CLI::log( sprintf( __( 'Nothing is in the queue: %s', 'jetpack' ), $queue_name  ) );
904
				} else {
905
					$collection = array();
906
					foreach ( $items as $item ) {
907
						$collection[] = array(
908
							'action'          => $item[0],
909
							'args'            => json_encode( $item[1] ),
910
							'current_user_id' => $item[2],
911
							'microtime'       => $item[3],
912
							'importing'       => (string) $item[4],
913
						);
914
					}
915
					WP_CLI\Utils\format_items(
916
						'table',
917
						$collection,
918
						array(
919
							'action',
920
							'args',
921
							'current_user_id',
922
							'microtime',
923
							'importing',
924
						)
925
					);
926
				}
927
				break;
928
		}
929
	}
930
931
	/**
932
	 * Cancel's the current Jetpack plan granted by this partner, if applicable
933
	 *
934
	 * Returns success or error JSON
935
	 *
936
	 * <token_json>
937
	 * : JSON blob of WPCOM API token
938
	 *  [--partner_tracking_id=<partner_tracking_id>]
939
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
940
	 *
941
	 *  * @synopsis <token_json> [--partner_tracking_id=<partner_tracking_id>]
942
	 */
943
	public function partner_cancel( $args, $named_args ) {
944
		list( $token_json ) = $args;
945
946 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
947
			$this->partner_provision_error( new WP_Error( 'missing_access_token',  sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
948
		}
949
950
		if ( isset( $token->error ) ) {
951
			$this->partner_provision_error( new WP_Error( $token->error, $token->message ) );
0 ignored issues
show
Bug introduced by
The variable $token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
952
		}
953
954
		if ( ! isset( $token->access_token ) ) {
955
			$this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
956
		}
957
958
		if ( Jetpack::validate_sync_error_idc_option() ) {
959
			$this->partner_provision_error( new WP_Error(
960
				'site_in_safe_mode',
961
				esc_html__( 'Can not cancel a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
962
			) );
963
		}
964
965
		$site_identifier = Jetpack_Options::get_option( 'id' );
966
967
		if ( ! $site_identifier ) {
968
			$site_identifier = Jetpack::build_raw_urls( get_home_url() );
969
		}
970
971
		$request = array(
972
			'headers' => array(
973
				'Authorization' => "Bearer " . $token->access_token,
974
				'Host'          => 'public-api.wordpress.com',
975
			),
976
			'timeout' => 60,
977
			'method'  => 'POST',
978
		);
979
980
		$url = sprintf( 'https://%s/rest/v1.3/jpphp/%s/partner-cancel', $this->get_api_host(), $site_identifier );
981 View Code Duplication
		if ( ! empty( $named_args ) && ! empty( $named_args['partner_tracking_id'] ) ) {
982
			$url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
983
		}
984
985
		$result = Jetpack_Client::_wp_remote_request( $url, $request );
986
987
		Jetpack_Options::delete_option( 'onboarding' );
988
989
		if ( is_wp_error( $result ) ) {
990
			$this->partner_provision_error( $result );
991
		}
992
993
		WP_CLI::log( wp_remote_retrieve_body( $result ) );
994
	}
995
996
	/**
997
	 * Provision a site using a Jetpack Partner license
998
	 *
999
	 * Returns JSON blob
1000
	 *
1001
	 * ## OPTIONS
1002
	 *
1003
	 * <token_json>
1004
	 * : JSON blob of WPCOM API token
1005
	 * [--plan=<plan_name>]
1006
	 * : Slug of the requested plan, e.g. premium
1007
	 * [--wpcom_user_id=<user_id>]
1008
	 * : WordPress.com ID of user to connect as (must be whitelisted against partner key)
1009
	 * [--wpcom_user_email=<wpcom_user_email>]
1010
	 * : Override the email we send to WordPress.com for registration
1011
	 * [--onboarding=<onboarding>]
1012
	 * : Guide the user through an onboarding wizard
1013
	 * [--force_register=<register>]
1014
	 * : Whether to force a site to register
1015
	 * [--force_connect=<force_connect>]
1016
	 * : Force JPS to not reuse existing credentials
1017
	 * [--home_url=<home_url>]
1018
	 * : Overrides the home option via the home_url filter, or the WP_HOME constant
1019
	 * [--site_url=<site_url>]
1020
	 * : Overrides the siteurl option via the site_url filter, or the WP_SITEURL constant
1021
	 * [--partner_tracking_id=<partner_tracking_id>]
1022
	 * : This is an optional ID that a host can pass to help identify a site in logs on WordPress.com
1023
	 *
1024
	 * ## EXAMPLES
1025
	 *
1026
	 *     $ wp jetpack partner_provision '{ some: "json" }' premium 1
1027
	 *     { success: true }
1028
	 *
1029
	 * @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>]
1030
	 */
1031
	public function partner_provision( $args, $named_args ) {
1032
		list( $token_json ) = $args;
1033
1034 View Code Duplication
		if ( ! $token_json || ! ( $token = json_decode( $token_json ) ) ) {
1035
			$this->partner_provision_error( new WP_Error( 'missing_access_token',  sprintf( __( 'Invalid token JSON: %s', 'jetpack' ), $token_json ) ) );
1036
		}
1037
1038
		if ( isset( $token->error ) ) {
1039
			$message = isset( $token->message )
0 ignored issues
show
Bug introduced by
The variable $token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1040
				? $token->message
1041
				: '';
1042
			$this->partner_provision_error( new WP_Error( $token->error, $message ) );
1043
		}
1044
1045
		if ( ! isset( $token->access_token ) ) {
1046
			$this->partner_provision_error( new WP_Error( 'missing_access_token', __( 'Missing or invalid access token', 'jetpack' ) ) );
1047
		}
1048
1049
		require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
1050
1051
		$body_json = Jetpack_Provision::partner_provision( $token->access_token, $named_args );
1052
1053
		if ( is_wp_error( $body_json ) ) {
1054
			error_log( json_encode( array(
1055
				'success'       => false,
1056
				'error_code'    => $body_json->get_error_code(),
1057
				'error_message' => $body_json->get_error_message()
1058
			) ) );
1059
			exit( 1 );
1060
		}
1061
1062
		WP_CLI::log( json_encode( $body_json ) );
1063
	}
1064
1065
	/**
1066
	 * Manages your Jetpack sitemap
1067
	 *
1068
	 * ## OPTIONS
1069
	 *
1070
	 * rebuild : Rebuild all sitemaps
1071
	 * --purge : if set, will remove all existing sitemap data before rebuilding
1072
	 *
1073
	 * ## EXAMPLES
1074
	 *
1075
	 * wp jetpack sitemap rebuild
1076
	 *
1077
	 * @subcommand sitemap
1078
	 * @synopsis <rebuild> [--purge]
1079
	 */
1080
	public function sitemap( $args, $assoc_args ) {
1081
		if ( ! Jetpack::is_active() ) {
1082
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1083
		}
1084
		if ( ! Jetpack::is_module_active( 'sitemaps' ) ) {
1085
			WP_CLI::error( __( 'Jetpack Sitemaps module is not currently active. Activate it first if you want to work with sitemaps.', 'jetpack' ) );
1086
		}
1087
		if ( ! class_exists( 'Jetpack_Sitemap_Builder' ) ) {
1088
			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' ) );
1089
		}
1090
1091
		if ( isset( $assoc_args['purge'] ) && $assoc_args['purge'] ) {
1092
			$librarian = new Jetpack_Sitemap_Librarian();
1093
			$librarian->delete_all_stored_sitemap_data();
1094
		}
1095
1096
		$sitemap_builder = new Jetpack_Sitemap_Builder();
1097
		$sitemap_builder->update_sitemap();
1098
	}
1099
1100
	/**
1101
	 * Allows authorizing a user via the command line and will activate
1102
	 *
1103
	 * ## EXAMPLES
1104
	 *
1105
	 * wp jetpack authorize_user --token=123456789abcdef
1106
	 *
1107
	 * @synopsis --token=<value>
1108
	 */
1109
	public function authorize_user( $args, $named_args ) {
1110
		if ( ! is_user_logged_in() ) {
1111
			WP_CLI::error( __( 'Please select a user to authorize via the --user global argument.', 'jetpack' ) );
1112
		}
1113
1114
		if ( empty( $named_args['token'] ) ) {
1115
			WP_CLI::error( __( 'A non-empty token argument must be passed.', 'jetpack' ) );
1116
		}
1117
1118
		$is_master_user  = ! Jetpack::is_active();
1119
		$current_user_id = get_current_user_id();
1120
1121
		Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_master_user );
1122
1123
		WP_CLI::log( wp_json_encode( $named_args ) );
1124
1125
		if ( $is_master_user ) {
1126
			/**
1127
			 * Auto-enable SSO module for new Jetpack Start connections
1128
			*
1129
			* @since 5.0.0
1130
			*
1131
			* @param bool $enable_sso Whether to enable the SSO module. Default to true.
1132
			*/
1133
			$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1134
			Jetpack::handle_post_authorization_actions( $enable_sso, false );
1135
1136
			/* translators: %d is a user ID */
1137
			WP_CLI::success( sprintf( __( 'Authorized %d and activated default modules.', 'jetpack' ), $current_user_id ) );
1138
		} else {
1139
			/* translators: %d is a user ID */
1140
			WP_CLI::success( sprintf( __( 'Authorized %d.', 'jetpack' ), $current_user_id ) );
1141
		}
1142
	}
1143
1144
	/**
1145
	 * Allows calling a WordPress.com API endpoint using the current blog's token.
1146
	 *
1147
	 * ## OPTIONS
1148
	 * --resource=<resource>
1149
	 * : The resource to call with the current blog's token, where `%d` represents the current blog's ID.
1150
	 *
1151
	 * [--api_version=<api_version>]
1152
	 * : The API version to query against.
1153
	 *
1154
	 * [--base_api_path=<base_api_path>]
1155
	 * : The base API path to query.
1156
	 * ---
1157
	 * default: rest
1158
	 * ---
1159
	 *
1160
	 * [--body=<body>]
1161
	 * : A JSON encoded string representing arguments to send in the body.
1162
	 *
1163
	 * [--field=<value>]
1164
	 * : Any number of arguments that should be passed to the resource.
1165
	 *
1166
	 * [--pretty]
1167
	 * : Will pretty print the results of a successful API call.
1168
	 *
1169
	 * [--strip-success]
1170
	 * : Will remove the green success label from successful API calls.
1171
	 *
1172
	 * ## EXAMPLES
1173
	 *
1174
	 * wp jetpack call_api --resource='/sites/%d'
1175
	 */
1176
	public function call_api( $args, $named_args ) {
1177
		if ( ! Jetpack::is_active() ) {
1178
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1179
		}
1180
1181
		$consumed_args = array(
1182
			'resource',
1183
			'api_version',
1184
			'base_api_path',
1185
			'body',
1186
			'pretty',
1187
		);
1188
1189
		// Get args that should be passed to resource.
1190
		$other_args = array_diff_key( $named_args, array_flip( $consumed_args ) );
1191
1192
		$decoded_body = ! empty( $named_args['body'] )
1193
			? json_decode( $named_args['body'] )
1194
			: false;
1195
1196
		$resource_url = ( false === strpos( $named_args['resource'], '%d' ) )
1197
			? $named_args['resource']
1198
			: sprintf( $named_args['resource'], Jetpack_Options::get_option( 'id' ) );
1199
1200
		$response = Jetpack_Client::wpcom_json_api_request_as_blog(
1201
			$resource_url,
1202
			empty( $named_args['api_version'] ) ? Jetpack_Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
1203
			$other_args,
1204
			empty( $decoded_body ) ? null : $decoded_body,
1205
			$named_args['base_api_path']
1206
		);
1207
1208 View Code Duplication
		if ( is_wp_error( $response ) ) {
1209
			WP_CLI::error( sprintf(
1210
				/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
1211
				__( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
1212
				$resource_url,
1213
				$response->get_error_code(),
1214
				$response->get_error_message()
1215
			) );
1216
		}
1217
1218
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1219
			WP_CLI::error( sprintf(
1220
				/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
1221
				__( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
1222
				$resource_url,
1223
				wp_remote_retrieve_response_code( $response )
1224
			) );
1225
		}
1226
1227
		$output = wp_remote_retrieve_body( $response );
1228
		if ( isset( $named_args['pretty'] ) ) {
1229
			$decoded_output = json_decode( $output );
1230
			if ( $decoded_output ) {
1231
				$output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
1232
			}
1233
		}
1234
1235
		if ( isset( $named_args['strip-success'] ) ) {
1236
			WP_CLI::log( $output );
1237
			WP_CLI::halt( 0 );
1238
		}
1239
1240
		WP_CLI::success( $output );
1241
	}
1242
1243
	/**
1244
	 * API wrapper for getting stats from the WordPress.com API for the current site.
1245
	 *
1246
	 * ## OPTIONS
1247
	 *
1248
	 * [--quantity=<quantity>]
1249
	 * : The number of units to include.
1250
	 * ---
1251
	 * default: 30
1252
	 * ---
1253
	 *
1254
	 * [--period=<period>]
1255
	 * : The unit of time to query stats for.
1256
	 * ---
1257
	 * default: day
1258
	 * options:
1259
	 *  - day
1260
	 *  - week
1261
	 *  - month
1262
	 *  - year
1263
	 * ---
1264
	 *
1265
	 * [--date=<date>]
1266
	 * : The latest date to return stats for. Ex. - 2018-01-01.
1267
	 *
1268
	 * [--pretty]
1269
	 * : Will pretty print the results of a successful API call.
1270
	 *
1271
	 * [--strip-success]
1272
	 * : Will remove the green success label from successful API calls.
1273
	 *
1274
	 * ## EXAMPLES
1275
	 *
1276
	 * wp jetpack get_stats
1277
	 */
1278
	public function get_stats( $args, $named_args ) {
1279
		$selected_args = array_intersect_key(
1280
			$named_args,
1281
			array_flip( array(
1282
				'quantity',
1283
				'date',
1284
			) )
1285
		);
1286
1287
		// The API expects unit, but period seems to be more correct.
1288
		$selected_args['unit'] = $named_args['period'];
1289
1290
		$command = sprintf(
1291
			'jetpack call_api --resource=/sites/%d/stats/%s',
1292
			Jetpack_Options::get_option( 'id' ),
1293
			add_query_arg( $selected_args, 'visits' )
1294
		);
1295
1296
		if ( isset( $named_args['pretty'] ) ) {
1297
			$command .= ' --pretty';
1298
		}
1299
1300
		if ( isset( $named_args['strip-success'] ) ) {
1301
			$command .= ' --strip-success';
1302
		}
1303
1304
		WP_CLI::runcommand(
1305
			$command,
1306
			array(
1307
				'launch' => false, // Use the current process.
1308
			)
1309
		);
1310
	}
1311
1312
	/**
1313
	 * Allows management of publicize connections.
1314
	 *
1315
	 * ## OPTIONS
1316
	 *
1317
	 * <list|disconnect>
1318
	 * : The action to perform.
1319
	 * ---
1320
	 * options:
1321
	 *   - list
1322
	 *   - disconnect
1323
	 * ---
1324
	 *
1325
	 * [<identifier>]
1326
	 * : The connection ID or service to perform an action on.
1327
	 *
1328
	 * [--format=<format>]
1329
	 * : Allows overriding the output of the command when listing connections.
1330
	 * ---
1331
	 * default: table
1332
	 * options:
1333
	 *   - table
1334
	 *   - json
1335
	 *   - csv
1336
	 *   - yaml
1337
	 *   - ids
1338
	 *   - count
1339
	 * ---
1340
	 *
1341
	 * ## EXAMPLES
1342
	 *
1343
	 *     # List all publicize connections.
1344
	 *     $ wp jetpack publicize list
1345
	 *
1346
	 *     # List publicize connections for a given service.
1347
	 *     $ wp jetpack publicize list twitter
1348
	 *
1349
	 *     # List all publicize connections for a given user.
1350
	 *     $ wp --user=1 jetpack publicize list
1351
	 *
1352
	 *     # List all publicize connections for a given user and service.
1353
	 *     $ wp --user=1 jetpack publicize list twitter
1354
	 *
1355
	 *     # Display details for a given connection.
1356
	 *     $ wp jetpack publicize list 123456
1357
	 *
1358
	 *     # Diconnection a given connection.
1359
	 *     $ wp jetpack publicize disconnect 123456
1360
	 *
1361
	 *     # Disconnect all connections.
1362
	 *     $ wp jetpack publicize disconnect all
1363
	 *
1364
	 *     # Disconnect all connections for a given service.
1365
	 *     $ wp jetpack publicize disconnect twitter
1366
	 */
1367
	public function publicize( $args, $named_args ) {
1368
		if ( ! Jetpack::is_active() ) {
1369
			WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
1370
		}
1371
1372
		if ( ! Jetpack::is_module_active( 'publicize' ) ) {
1373
			WP_CLI::error( __( 'The publicize module is not active.', 'jetpack' ) );
1374
		}
1375
1376
		if ( Jetpack::is_development_mode() ) {
1377
			if (
1378
				! defined( 'JETPACK_DEV_DEBUG' ) &&
1379
				! has_filter( 'jetpack_development_mode' ) &&
1380
				false === strpos( site_url(), '.' )
1381
			) {
1382
				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' ) );
1383
			}
1384
1385
			WP_CLI::error( __( 'Jetpack is currently in development mode, so the publicize module will not load.', 'jetpack' ) );
1386
		}
1387
1388
		if ( ! class_exists( 'Publicize' ) ) {
1389
			WP_CLI::error( __( 'The publicize module is not loaded.', 'jetpack' ) );
1390
		}
1391
1392
		$action        = $args[0];
1393
		$publicize     = new Publicize();
1394
		$identifier    = ! empty( $args[1] ) ? $args[1] : false;
1395
		$services      = array_keys( $publicize->get_services() );
1396
		$id_is_service = in_array( $identifier, $services, true );
1397
1398
		switch ( $action ) {
1399
			case 'list':
1400
				$connections_to_return = array();
1401
1402
				// For the CLI command, let's return all connections when a user isn't specified. This
1403
				// differs from the logic in the Publicize class.
1404
				$option_connections = is_user_logged_in()
1405
					? (array) $publicize->get_all_connections_for_user()
1406
					: (array) $publicize->get_all_connections();
1407
1408
				foreach ( $option_connections as $service_name => $connections ) {
1409
					foreach ( (array) $connections as $id => $connection ) {
1410
						$connection['id']        = $id;
1411
						$connection['service']   = $service_name;
1412
						$connections_to_return[] = $connection;
1413
					}
1414
				}
1415
1416
				if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
1417
					$temp_connections      = $connections_to_return;
1418
					$connections_to_return = array();
1419
1420
					foreach ( $temp_connections as $connection ) {
1421
						if ( $identifier === $connection['service'] ) {
1422
							$connections_to_return[] = $connection;
1423
						}
1424
					}
1425
				}
1426
1427
				if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
1428
					$connections_to_return = wp_list_filter( $connections_to_return, array( 'id' => $identifier ) );
1429
				}
1430
1431
				$expected_keys = array(
1432
					'id',
1433
					'service',
1434
					'user_id',
1435
					'provider',
1436
					'issued',
1437
					'expires',
1438
					'external_id',
1439
					'external_name',
1440
					'external_display',
1441
					'type',
1442
					'connection_data',
1443
				);
1444
1445
				// Somehow, a test site ended up in a state where $connections_to_return looked like:
1446
				// array( array( array( 'id' => 0, 'service' => 0 ) ) ) // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1447
				// This caused the CLI command to error when running WP_CLI\Utils\format_items() below. So
1448
				// to minimize future issues, this nested loop will remove any connections that don't contain
1449
				// any keys that we expect.
1450
				foreach ( (array) $connections_to_return as $connection_key => $connection ) {
1451
					foreach ( $expected_keys as $expected_key ) {
1452
						if ( ! isset( $connection[ $expected_key ] ) ) {
1453
							unset( $connections_to_return[ $connection_key ] );
1454
							continue;
1455
						}
1456
					}
1457
				}
1458
1459
				if ( empty( $connections_to_return ) ) {
1460
					return false;
1461
				}
1462
1463
				WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
1464
				break; // list.
1465
			case 'disconnect':
1466
				if ( ! $identifier ) {
1467
					WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
1468
				}
1469
1470
				// If the connection ID is 'all' then delete all connections. If the connection ID
1471
				// matches a service, delete all connections for that service.
1472
				if ( 'all' === $identifier || $id_is_service ) {
1473
					if ( 'all' === $identifier ) {
1474
						WP_CLI::log( __( "You're about to delete all publicize connections.", 'jetpack' ) );
1475
					} else {
1476
						/* translators: %s is a lowercase string for a social network. */
1477
						WP_CLI::log( sprintf( __( "You're about to delete all publicize connections to %s.", 'jetpack' ), $identifier ) );
1478
					}
1479
1480
					jetpack_cli_are_you_sure();
1481
1482
					$connections = array();
1483
					$service     = $identifier;
1484
1485
					$option_connections = is_user_logged_in()
1486
						? (array) $publicize->get_all_connections_for_user()
1487
						: (array) $publicize->get_all_connections();
1488
1489
					if ( 'all' === $service ) {
1490
						foreach ( (array) $option_connections as $service_name => $service_connections ) {
1491
							foreach ( $service_connections as $id => $connection ) {
1492
								$connections[ $id ] = $connection;
1493
							}
1494
						}
1495
					} elseif ( ! empty( $option_connections[ $service ] ) ) {
1496
						$connections = $option_connections[ $service ];
1497
					}
1498
1499
					if ( ! empty( $connections ) ) {
1500
						$count    = count( $connections );
1501
						$progress = \WP_CLI\Utils\make_progress_bar(
1502
							/* translators: %s is a lowercase string for a social network. */
1503
							sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
1504
							$count
1505
						);
1506
1507
						foreach ( $connections as $id => $connection ) {
1508
							if ( false === $publicize->disconnect( false, $id ) ) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1509
								WP_CLI::error( sprintf(
1510
									/* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
1511
									__( 'Publicize connection %d could not be disconnected', 'jetpack' ),
1512
									$id
1513
								) );
1514
							}
1515
1516
							$progress->tick();
1517
						}
1518
1519
						$progress->finish();
1520
1521
						if ( 'all' === $service ) {
1522
							WP_CLI::success( __( 'All publicize connections were successfully disconnected.', 'jetpack' ) );
1523
						} else {
1524
							/* translators: %s is a lowercase string for a social network. */
1525
							WP_CLI::success( __( 'All publicize connections to %s were successfully disconnected.', 'jetpack' ), $service );
1526
						}
1527
					}
1528
				} else {
1529
					if ( false !== $publicize->disconnect( false, $identifier ) ) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1530
						/* translators: %d is a numeric ID. Example: 1234. */
1531
						WP_CLI::success( sprintf( __( 'Publicize connection %d has been disconnected.', 'jetpack' ), $identifier ) );
1532
					} else {
1533
						/* translators: %d is a numeric ID. Example: 1234. */
1534
						WP_CLI::error( sprintf( __( 'Publicize connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
1535
					}
1536
				}
1537
				break; // disconnect.
1538
		}
1539
	}
1540
1541
	private function get_api_host() {
1542
		$env_api_host = getenv( 'JETPACK_START_API_HOST', true );
1543
		return $env_api_host ? $env_api_host : JETPACK__WPCOM_JSON_API_HOST;
1544
	}
1545
1546
	private function partner_provision_error( $error ) {
1547
		WP_CLI::log( json_encode( array(
1548
			'success'       => false,
1549
			'error_code'    => $error->get_error_code(),
1550
			'error_message' => $error->get_error_message()
1551
		) ) );
1552
		exit( 1 );
1553
	}
1554
1555
	/**
1556
	 * Creates the essential files in Jetpack to start building a Gutenberg block or plugin.
1557
	 *
1558
	 * ## TYPES
1559
	 *
1560
	 * 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.
1561
	 *
1562
	 * ## BLOCK TYPE OPTIONS
1563
	 *
1564
	 * The first parameter is the block title and it's not associative. Add it wrapped in quotes.
1565
	 * 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.
1566
	 * --slug: Specific slug to identify the block that overrides the one generated based on the title.
1567
	 * --description: Allows to provide a text description of the block.
1568
	 * --keywords: Provide up to three keywords separated by comma so users can find this block when they search in Gutenberg's inserter.
1569
	 *
1570
	 * ## BLOCK TYPE EXAMPLES
1571
	 *
1572
	 * wp jetpack scaffold block "Cool Block"
1573
	 * wp jetpack scaffold block "Amazing Rock" --slug="good-music" --description="Rock the best music on your site"
1574
	 * wp jetpack scaffold block "Jukebox" --keywords="music, audio, media"
1575
	 *
1576
	 * @subcommand scaffold block
1577
	 * @synopsis <type> <title> [--slug] [--description] [--keywords]
1578
	 *
1579
	 * @param array $args       Positional parameters, when strings are passed, wrap them in quotes.
1580
	 * @param array $assoc_args Associative parameters like --slug="nice-block".
1581
	 */
1582
	public function scaffold( $args, $assoc_args ) {
1583
		// It's ok not to check if it's set, because otherwise WPCLI exits earlier.
1584
		switch ( $args[0] ) {
1585
			case 'block':
1586
				$this->block( $args, $assoc_args );
1587
				break;
1588
			default:
1589
				WP_CLI::error( sprintf( esc_html__( 'Invalid subcommand %s.', 'jetpack' ), $args[0] ) . ' 👻' );
1590
				exit( 1 );
1591
		}
1592
	}
1593
1594
	/**
1595
	 * Creates the essential files in Jetpack to build a Gutenberg block.
1596
	 *
1597
	 * @param array $args       Positional parameters. Only one is used, that corresponds to the block title.
1598
	 * @param array $assoc_args Associative parameters defined in the scaffold() method.
1599
	 */
1600
	public function block( $args, $assoc_args ) {
1601 View Code Duplication
		if ( isset( $args[1] ) ) {
1602
			$title = ucwords( $args[1] );
1603
		} else {
1604
			WP_CLI::error( esc_html__( 'The title parameter is required.', 'jetpack' ) . ' 👻' );
1605
			exit( 1 );
1606
		}
1607
1608
		$slug = isset( $assoc_args['slug'] )
1609
			? $assoc_args['slug']
1610
			: sanitize_title( $title );
1611
1612
		if ( preg_match( '#^jetpack/#', $slug ) ) {
1613
			$slug = preg_replace( '#^jetpack/#', '', $slug );
1614
		}
1615
1616
		if ( ! preg_match( '/^[a-z][a-z0-9\-]*$/', $slug ) ) {
1617
			WP_CLI::error( esc_html__( 'Invalid block slug. They can contain only lowercase alphanumeric characters or dashes, and start with a letter', 'jetpack' ) . ' 👻' );
1618
		}
1619
1620
		global $wp_filesystem;
1621
		if ( ! WP_Filesystem() ) {
1622
			WP_CLI::error( esc_html__( "Can't write files", 'jetpack' ) . ' 😱' );
1623
		}
1624
1625
		$path = JETPACK__PLUGIN_DIR . "extensions/blocks/$slug";
1626
1627
		if ( $wp_filesystem->exists( $path ) && $wp_filesystem->is_dir( $path ) ) {
1628
			WP_CLI::error( sprintf( esc_html__( 'Name conflicts with the existing block %s', 'jetpack' ), $path ) . ' ⛔️' );
1629
			exit( 1 );
1630
		}
1631
1632
		$wp_filesystem->mkdir( $path );
1633
1634
		$hasKeywords = isset( $assoc_args['keywords'] );
1635
1636
		$files = array(
1637
			"$path/$slug.php" => $this->render_block_file( 'block-register-php', array(
1638
				'slug' => $slug,
1639
				'title' => $title,
1640
				'underscoredSlug' => str_replace( '-', '_', $slug ),
1641
			) ),
1642
			"$path/index.js" => $this->render_block_file( 'block-index-js', array(
1643
				'slug' => $slug,
1644
				'title' => $title,
1645
				'description' => isset( $assoc_args['description'] )
1646
					? $assoc_args['description']
1647
					: $title,
1648
				'keywords' => $hasKeywords
1649
					? array_map( function( $keyword ) {
1650
						// Construction necessary for Mustache lists
1651
						return array( 'keyword' => trim( $keyword ) );
1652
					}, explode( ',', $assoc_args['keywords'], 3 ) )
1653
					: '',
1654
				'hasKeywords' => $hasKeywords
1655
			) ),
1656
			"$path/editor.js" => $this->render_block_file( 'block-editor-js' ),
1657
			"$path/editor.scss" => $this->render_block_file( 'block-editor-scss', array(
1658
				'slug' => $slug,
1659
				'title' => $title,
1660
			) ),
1661
			"$path/edit.js" => $this->render_block_file( 'block-edit-js', array(
1662
				'title' => $title,
1663
				'className' => str_replace( ' ', '', ucwords( str_replace( '-', ' ', $slug ) ) ),
1664
			) )
1665
		);
1666
1667
		$files_written = array();
1668
1669
		foreach ( $files as $filename => $contents ) {
1670
			if ( $wp_filesystem->put_contents( $filename, $contents ) ) {
1671
				$files_written[] = $filename;
1672
			} else {
1673
				WP_CLI::error( sprintf( esc_html__( 'Error creating %s', 'jetpack' ), $filename ) );
1674
			}
1675
		}
1676
1677
		if ( empty( $files_written ) ) {
1678
			WP_CLI::log( esc_html__( 'No files were created', 'jetpack' ) );
1679
		} else {
1680
			// Load index.json and insert the slug of the new block in the production array
1681
			$block_list_path = JETPACK__PLUGIN_DIR . 'extensions/index.json';
1682
			$block_list = $wp_filesystem->get_contents( $block_list_path );
1683
			if ( empty( $block_list ) ) {
1684
				WP_CLI::error( sprintf( esc_html__( 'Error fetching contents of %s', 'jetpack' ), $block_list_path ) );
1685
			} else if ( false === stripos( $block_list, $slug ) ) {
1686
				$new_block_list = json_decode( $block_list );
1687
				$new_block_list->beta[] = $slug;
1688
				if ( ! $wp_filesystem->put_contents( $block_list_path, wp_json_encode( $new_block_list ) ) ) {
1689
					WP_CLI::error( sprintf( esc_html__( 'Error writing new %s', 'jetpack' ), $block_list_path ) );
1690
				}
1691
			}
1692
1693
			WP_CLI::success( sprintf(
1694
				/* translators: the placeholders are a human readable title, and a series of words separated by dashes */
1695
				esc_html__( 'Successfully created block %s with slug %s', 'jetpack' ) . ' 🎉' . "\n" .
1696
				"--------------------------------------------------------------------------------------------------------------------\n" .
1697
				/* translators: the placeholder is a directory path */
1698
				esc_html__( 'The files were created at %s', 'jetpack' ) . "\n" .
1699
				esc_html__( 'To start using the block, build the blocks with yarn run build-extensions', 'jetpack' ) . "\n" .
1700
				/* translators: the placeholder is a file path */
1701
				esc_html__( 'The block slug has been added to the beta list at %s', 'jetpack' ) . "\n" .
1702
				esc_html__( 'To load the block, add the constant JETPACK_BETA_BLOCKS as true to your wp-config.php file', 'jetpack' ) . "\n" .
1703
				/* translators: the placeholder is a URL */
1704
				"\n" . esc_html__( 'Read more at %s', 'jetpack' ) . "\n",
1705
				$title,
1706
				$slug,
1707
				$path,
1708
				$block_list_path,
1709
				'https://github.com/Automattic/jetpack/blob/master/extensions/README.md#develop-new-blocks'
1710
			) . '--------------------------------------------------------------------------------------------------------------------' );
1711
		}
1712
	}
1713
1714
	/**
1715
	 * Built the file replacing the placeholders in the template with the data supplied.
1716
	 *
1717
	 * @param string $template
1718
	 * @param array $data
1719
	 *
1720
	 * @return string mixed
1721
	 */
1722
	private static function render_block_file( $template, $data = array() ) {
1723
		return \WP_CLI\Utils\mustache_render( JETPACK__PLUGIN_DIR . "wp-cli-templates/$template.mustache", $data );
1724
	}
1725
}
1726
1727
/*
1728
 * Standard "ask for permission to continue" function.
1729
 * If action cancelled, ask if they need help.
1730
 *
1731
 * Written outside of the class so it's not listed as an executable command w/ 'wp jetpack'
1732
 *
1733
 * @param $flagged   bool   false = normal option | true = flagged by get_jetpack_options_for_reset()
1734
 * @param $error_msg string (optional)
1735
 */
1736
function jetpack_cli_are_you_sure( $flagged = false, $error_msg = false ) {
1737
	$cli = new Jetpack_CLI();
1738
1739
	// Default cancellation message
1740
	if ( ! $error_msg ) {
1741
		$error_msg =
1742
			__( 'Action cancelled. Have a question?', 'jetpack' )
1743
			. ' '
1744
			. $cli->green_open
1745
			. 'jetpack.com/support'
1746
			.  $cli->color_close;
1747
	}
1748
1749
	if ( ! $flagged ) {
1750
		$prompt_message = _x( 'Are you sure? This cannot be undone. Type "yes" to continue:', '"yes" is a command - do not translate.', 'jetpack' );
1751
	} else {
1752
		$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' );
1753
	}
1754
1755
	WP_CLI::line( $prompt_message );
1756
	$handle = fopen( "php://stdin", "r" );
1757
	$line = fgets( $handle );
1758
	if ( 'yes' != trim( $line ) ){
1759
		WP_CLI::error( $error_msg );
1760
	}
1761
}
1762