Completed
Push — add/plugin-search-hints ( d3403f...c10220 )
by
unknown
22:54 queued 15:37
created

Jetpack_CLI::sync_queue()   B

Complexity

Conditions 8
Paths 48

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 48
nop 2
dl 0
loc 51
rs 7.8246
c 0
b 0
f 0

How to fix   Long Method   

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