Completed
Push — branch-7.4-built ( 178bf1...6cbd9f )
by
unknown
17:33 queued 10:33
created

_inc/lib/class.core-rest-api-endpoints.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Register WP REST API endpoints for Jetpack.
4
 *
5
 * @author Automattic
6
 */
7
8
/**
9
 * Disable direct access.
10
 */
11
if ( ! defined( 'ABSPATH' ) ) {
12
	exit;
13
}
14
15
// Load WP_Error for error messages.
16
require_once ABSPATH . '/wp-includes/class-wp-error.php';
17
18
// Register endpoints when WP REST API is initialized.
19
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
20
// Load API endpoints that are synced with WP.com
21
// Each of these is a class that will register its own routes on 'rest_api_init'.
22
require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
23
24
/**
25
 * Class Jetpack_Core_Json_Api_Endpoints
26
 *
27
 * @since 4.3.0
28
 */
29
class Jetpack_Core_Json_Api_Endpoints {
30
31
	/**
32
	 * @var string Generic error message when user is not allowed to perform an action.
33
	 */
34
	public static $user_permissions_error_msg;
35
36
	/**
37
	 * @var array Roles that can access Stats once they're granted access.
38
	 */
39
	public static $stats_roles;
40
41
	/**
42
	 * Declare the Jetpack REST API endpoints.
43
	 *
44
	 * @since 4.3.0
45
	 */
46
	public static function register_endpoints() {
47
48
		// Load API endpoint base classes
49
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
50
51
		// Load API endpoints
52
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
53
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
54
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
55
56
		self::$user_permissions_error_msg = esc_html__(
57
			'You do not have the correct user permissions to perform this action.
58
			Please contact your site admin if you think this is a mistake.',
59
			'jetpack'
60
		);
61
62
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
63
64
		Jetpack::load_xml_rpc_client();
65
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
66
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
67
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
68
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
69
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
70
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
71
		$widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
72
73
		register_rest_route( 'jetpack/v4', 'plans', array(
74
			'methods'             => WP_REST_Server::READABLE,
75
			'callback'            => __CLASS__ . '::get_plans',
76
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
77
78
		) );
79
80
		register_rest_route( 'jetpack/v4', '/jitm', array(
81
			'methods'  => WP_REST_Server::READABLE,
82
			'callback' => __CLASS__ . '::get_jitm_message',
83
		) );
84
85
		register_rest_route( 'jetpack/v4', '/jitm', array(
86
			'methods'  => WP_REST_Server::CREATABLE,
87
			'callback' => __CLASS__ . '::delete_jitm_message'
88
		) );
89
90
		// Register a site
91
		register_rest_route( 'jetpack/v4', '/verify_registration', array(
92
			'methods' => WP_REST_Server::EDITABLE,
93
			'callback' => __CLASS__ . '::verify_registration',
94
		) );
95
96
		// Authorize a remote user
97
		register_rest_route( 'jetpack/v4', '/remote_authorize', array(
98
			'methods' => WP_REST_Server::EDITABLE,
99
			'callback' => __CLASS__ . '::remote_authorize',
100
		) );
101
102
		// Get current connection status of Jetpack
103
		register_rest_route( 'jetpack/v4', '/connection', array(
104
			'methods' => WP_REST_Server::READABLE,
105
			'callback' => __CLASS__ . '::jetpack_connection_status',
106
		) );
107
108
		// Test current connection status of Jetpack
109
		register_rest_route( 'jetpack/v4', '/connection/test', array(
110
			'methods' => WP_REST_Server::READABLE,
111
			'callback' => __CLASS__ . '::jetpack_connection_test',
112
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
113
		) );
114
115
		// Endpoint specific for privileged servers to request detailed debug information.
116
		register_rest_route( 'jetpack/v4', '/connection/test-wpcom/', array(
117
			'methods' => WP_REST_Server::READABLE,
118
			'callback' => __CLASS__ . '::jetpack_connection_test_for_external',
119
			'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check',
120
		) );
121
122
		register_rest_route( 'jetpack/v4', '/rewind', array(
123
			'methods' => WP_REST_Server::READABLE,
124
			'callback' => __CLASS__ . '::get_rewind_data',
125
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
126
		) );
127
128
		// Fetches a fresh connect URL
129
		register_rest_route( 'jetpack/v4', '/connection/url', array(
130
			'methods' => WP_REST_Server::READABLE,
131
			'callback' => __CLASS__ . '::build_connect_url',
132
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
133
		) );
134
135
		// Get current user connection data
136
		register_rest_route( 'jetpack/v4', '/connection/data', array(
137
			'methods' => WP_REST_Server::READABLE,
138
			'callback' => __CLASS__ . '::get_user_connection_data',
139
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
140
		) );
141
142
		// Set the connection owner
143
		register_rest_route( 'jetpack/v4', '/connection/owner', array(
144
			'methods' => WP_REST_Server::EDITABLE,
145
			'callback' => __CLASS__ . '::set_connection_owner',
146
			'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback',
147
		) );
148
149
		// Current user: get or set tracking settings.
150
		register_rest_route( 'jetpack/v4', '/tracking/settings', array(
151
			array(
152
				'methods'             => WP_REST_Server::READABLE,
153
				'callback'            => __CLASS__ . '::get_user_tracking_settings',
154
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
155
			),
156
			array(
157
				'methods'             => WP_REST_Server::EDITABLE,
158
				'callback'            => __CLASS__ . '::update_user_tracking_settings',
159
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
160
				'args'                => array(
161
					'tracks_opt_out' => array( 'type' => 'boolean' ),
162
				),
163
			),
164
		) );
165
166
		// Disconnect site from WordPress.com servers
167
		register_rest_route( 'jetpack/v4', '/connection', array(
168
			'methods' => WP_REST_Server::EDITABLE,
169
			'callback' => __CLASS__ . '::disconnect_site',
170
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
171
		) );
172
173
		// Disconnect/unlink user from WordPress.com servers
174
		register_rest_route( 'jetpack/v4', '/connection/user', array(
175
			'methods' => WP_REST_Server::EDITABLE,
176
			'callback' => __CLASS__ . '::unlink_user',
177
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
178
		) );
179
180
		// Get current site data
181
		register_rest_route( 'jetpack/v4', '/site', array(
182
			'methods' => WP_REST_Server::READABLE,
183
			'callback' => __CLASS__ . '::get_site_data',
184
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
185
		) );
186
187
		// Get current site data
188
		register_rest_route( 'jetpack/v4', '/site/features', array(
189
			'methods' => WP_REST_Server::READABLE,
190
			'callback' => array( $site_endpoint, 'get_features' ),
191
			'permission_callback' => array( $site_endpoint , 'can_request' ),
192
		) );
193
194
		// Get Activity Log data for this site.
195
		register_rest_route( 'jetpack/v4', '/site/activity', array(
196
			'methods' => WP_REST_Server::READABLE,
197
			'callback' => __CLASS__ . '::get_site_activity',
198
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
199
		) );
200
201
		// Confirm that a site in identity crisis should be in staging mode
202
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
203
			'methods' => WP_REST_Server::EDITABLE,
204
			'callback' => __CLASS__ . '::confirm_safe_mode',
205
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
206
		) );
207
208
		// IDC resolve: create an entirely new shadow site for this URL.
209
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
210
			'methods' => WP_REST_Server::EDITABLE,
211
			'callback' => __CLASS__ . '::start_fresh_connection',
212
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
213
		) );
214
215
		// Handles the request to migrate stats and subscribers during an identity crisis.
216
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
217
			'methods' => WP_REST_Server::EDITABLE,
218
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
219
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
220
		) );
221
222
		// Return all modules
223
		register_rest_route( 'jetpack/v4', '/module/all', array(
224
			'methods' => WP_REST_Server::READABLE,
225
			'callback' => array( $module_list_endpoint, 'process' ),
226
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
227
		) );
228
229
		// Activate many modules
230
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
231
			'methods' => WP_REST_Server::EDITABLE,
232
			'callback' => array( $module_list_endpoint, 'process' ),
233
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
234
			'args' => array(
235
				'modules' => array(
236
					'default'           => '',
237
					'type'              => 'array',
238
					'items'             => array(
239
						'type'          => 'string',
240
					),
241
					'required'          => true,
242
					'validate_callback' => __CLASS__ . '::validate_module_list',
243
				),
244
				'active' => array(
245
					'default'           => true,
246
					'type'              => 'boolean',
247
					'required'          => false,
248
					'validate_callback' => __CLASS__ . '::validate_boolean',
249
				),
250
			)
251
		) );
252
253
		// Return a single module and update it when needed
254
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
255
			'methods' => WP_REST_Server::READABLE,
256
			'callback' => array( $core_api_endpoint, 'process' ),
257
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
258
		) );
259
260
		// Activate and deactivate a module
261
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
262
			'methods' => WP_REST_Server::EDITABLE,
263
			'callback' => array( $module_toggle_endpoint, 'process' ),
264
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
265
			'args' => array(
266
				'active' => array(
267
					'default'           => true,
268
					'type'              => 'boolean',
269
					'required'          => true,
270
					'validate_callback' => __CLASS__ . '::validate_boolean',
271
				),
272
			)
273
		) );
274
275
		// Update a module
276
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
277
			'methods' => WP_REST_Server::EDITABLE,
278
			'callback' => array( $core_api_endpoint, 'process' ),
279
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
280
			'args' => self::get_updateable_parameters( 'any' )
281
		) );
282
283
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
284
		// Akismet spam count, etc.
285
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
286
			'methods' => WP_REST_Server::READABLE,
287
			'callback' => array( $module_data_endpoint, 'process' ),
288
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
289
			'args' => array(
290
				'range' => array(
291
					'default'           => 'day',
292
					'type'              => 'string',
293
					'required'          => false,
294
					'validate_callback' => __CLASS__ . '::validate_string',
295
				),
296
			)
297
		) );
298
299
		// Check if the API key for a specific service is valid or not
300
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
301
			'methods' => WP_REST_Server::READABLE,
302
			'callback' => array( $module_data_endpoint, 'key_check' ),
303
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
304
			'sanitize_callback' => 'sanitize_text_field',
305
		) );
306
307
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
308
			'methods' => WP_REST_Server::EDITABLE,
309
			'callback' => array( $module_data_endpoint, 'key_check' ),
310
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
311
			'sanitize_callback' => 'sanitize_text_field',
312
			'args' => array(
313
				'api_key' => array(
314
					'default'           => '',
315
					'type'              => 'string',
316
					'validate_callback' => __CLASS__ . '::validate_alphanum',
317
				),
318
			)
319
		) );
320
321
		// Update any Jetpack module option or setting
322
		register_rest_route( 'jetpack/v4', '/settings', array(
323
			'methods' => WP_REST_Server::EDITABLE,
324
			'callback' => array( $core_api_endpoint, 'process' ),
325
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
326
			'args' => self::get_updateable_parameters( 'any' )
327
		) );
328
329
		// Update a module
330
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
331
			'methods' => WP_REST_Server::EDITABLE,
332
			'callback' => array( $core_api_endpoint, 'process' ),
333
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
334
			'args' => self::get_updateable_parameters()
335
		) );
336
337
		// Return all module settings
338
		register_rest_route( 'jetpack/v4', '/settings/', array(
339
			'methods' => WP_REST_Server::READABLE,
340
			'callback' => array( $core_api_endpoint, 'process' ),
341
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
342
		) );
343
344
		// Reset all Jetpack options
345
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
346
			'methods' => WP_REST_Server::EDITABLE,
347
			'callback' => __CLASS__ . '::reset_jetpack_options',
348
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
349
		) );
350
351
		// Updates: get number of plugin updates available
352
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
353
			'methods' => WP_REST_Server::READABLE,
354
			'callback' => __CLASS__ . '::get_plugin_update_count',
355
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
356
		) );
357
358
		// Dismiss Jetpack Notices
359
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
360
			'methods' => WP_REST_Server::EDITABLE,
361
			'callback' => __CLASS__ . '::dismiss_notice',
362
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
363
		) );
364
365
		// Plugins: get list of all plugins.
366
		register_rest_route( 'jetpack/v4', '/plugins', array(
367
			'methods' => WP_REST_Server::READABLE,
368
			'callback' => __CLASS__ . '::get_plugins',
369
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
370
		) );
371
372
		// Plugins: check if the plugin is active.
373
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
374
			'methods' => WP_REST_Server::READABLE,
375
			'callback' => __CLASS__ . '::get_plugin',
376
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
377
		) );
378
379
		// Widgets: get information about a widget that supports it.
380
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
381
			'methods' => WP_REST_Server::READABLE,
382
			'callback' => array( $widget_endpoint, 'process' ),
383
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
384
		) );
385
386
		// Site Verify: check if the site is verified, and a get verification token if not
387
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
388
			'methods' => WP_REST_Server::READABLE,
389
			'callback' => __CLASS__ . '::is_site_verified_and_token',
390
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
391
		) );
392
393
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
394
			'methods' => WP_REST_Server::READABLE,
395
			'callback' => __CLASS__ . '::is_site_verified_and_token',
396
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
397
		) );
398
399
		// Site Verify: tell a service to verify the site
400
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
401
			'methods' => WP_REST_Server::EDITABLE,
402
			'callback' => __CLASS__ . '::verify_site',
403
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
404
			'args' => array(
405
				'keyring_id' => array(
406
					'required'          => true,
407
					'type'              => 'integer',
408
					'validate_callback' => __CLASS__  . '::validate_posint',
409
				),
410
			)
411
		) );
412
413
		// Get and set API keys.
414
		// Note: permission_callback intentionally omitted from the GET method.
415
		// Map block requires open access to API keys on the front end.
416
		register_rest_route(
417
			'jetpack/v4',
418
			'/service-api-keys/(?P<service>[a-z\-_]+)',
419
			array(
420
				array(
421
					'methods'             => WP_REST_Server::READABLE,
422
					'callback'            => __CLASS__ . '::get_service_api_key',
423
				),
424
				array(
425
					'methods'             => WP_REST_Server::EDITABLE,
426
					'callback'            => __CLASS__ . '::update_service_api_key',
427
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
428
					'args'                => array(
429
						'service_api_key' => array(
430
							'required' => true,
431
							'type'     => 'text',
432
						),
433
					),
434
				),
435
				array(
436
					'methods'             => WP_REST_Server::DELETABLE,
437
					'callback'            => __CLASS__ . '::delete_service_api_key',
438
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
439
				),
440
			)
441
		);
442
443
		register_rest_route(
444
			'jetpack/v4',
445
			'/mobile/send-login-email',
446
			array(
447
				'methods'             => WP_REST_Server::EDITABLE,
448
				'callback'            => __CLASS__ . '::send_mobile_magic_link',
449
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
450
			)
451
		);
452
	}
453
454
	public static function get_plans( $request ) {
455
		$request = Jetpack_Client::wpcom_json_api_request_as_user(
456
			'/plans?_locale=' . get_user_locale(),
457
			'2',
458
			array(
459
				'method'  => 'GET',
460
				'headers' => array(
461
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
462
				),
463
			)
464
		);
465
466
		$body = wp_remote_retrieve_body( $request );
467
		if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
468
			$data = $body;
469
		} else {
470
			// something went wrong so we'll just return the response without caching
471
			return $body;
472
		}
473
474
		return $data;
475
	}
476
477
	/**
478
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
479
	 *
480
	 * @param $request WP_REST_Request
481
	 *
482
	 * @return array An array of jitms
483
	 */
484
	public static function get_jitm_message( $request ) {
485
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
486
487
		$jitm = Jetpack_JITM::init();
488
489
		if ( ! $jitm ) {
490
			return array();
491
		}
492
493
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) );
494
	}
495
496
	/**
497
	 * Dismisses a jitm
498
	 * @param $request WP_REST_Request The request
499
	 *
500
	 * @return bool Always True
501
	 */
502
	public static function delete_jitm_message( $request ) {
503
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
504
505
		$jitm = Jetpack_JITM::init();
506
507
		if ( ! $jitm ) {
508
			return true;
509
		}
510
511
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
512
	}
513
514
	/**
515
	 * Handles verification that a site is registered
516
	 *
517
	 * @since 5.4.0
518
	 *
519
	 * @param WP_REST_Request $request The request sent to the WP REST API.
520
	 *
521
	 * @return array|wp-error
522
	 */
523
	public static function verify_registration( $request ) {
524
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
525
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
526
		$result = $xmlrpc_server->verify_registration( array( $request['secret_1'], $request['state'] ) );
527
528
		if ( is_a( $result, 'IXR_Error' ) ) {
529
			$result = new WP_Error( $result->code, $result->message );
530
		}
531
532
		return $result;
533
	}
534
535
536
	/**
537
	 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
538
	 *  keyring to use to get the token if it is not
539
	 *
540
	 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
541
	 *
542
	 * @since 6.6.0
543
	 *
544
	 * @param WP_REST_Request $request The request sent to the WP REST API.
545
	 *
546
	 * @return array|wp-error
547
	 */
548
	public static function is_site_verified_and_token( $request ) {
549
		/**
550
		 * Return an error if the site uses a Maintenance / Coming Soon plugin
551
		 * and if the plugin is configured to make the site private.
552
		 *
553
		 * We currently handle the following plugins:
554
		 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
555
		 * - https://wordpress.org/plugins/mojo-under-construction
556
		 * - https://wordpress.org/plugins/under-construction-page
557
		 * - https://wordpress.org/plugins/ultimate-under-construction
558
		 * - https://wordpress.org/plugins/coming-soon
559
		 *
560
		 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
561
		 * If the filter returns true, we will consider the site as under construction.
562
		 */
563
		$mm_coming_soon                       = get_option( 'mm_coming_soon', null );
564
		$under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
565
		$ucp_options                          = get_option( 'ucp_options', array() );
566
		$uuc_settings                         = get_option( 'uuc_settings', array() );
567
		$csp4                                 = get_option( 'seed_csp4_settings_content', array() );
568
		if (
569
			( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
570
			|| Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
571
			|| ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
572
			|| ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
573
			|| ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) &&  isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
574
			/**
575
			 * Allow plugins to mark a site as "under construction".
576
			 *
577
			 * @since 6.7.0
578
			 *
579
			 * @param false bool Is the site under construction? Default to false.
580
			 */
581
			|| true === apply_filters( 'jetpack_is_under_construction_plugin', false )
582
		) {
583
			return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
584
		}
585
586
		Jetpack::load_xml_rpc_client();
587
 		$xml = new Jetpack_IXR_Client( array(
588
 			'user_id' => get_current_user_id(),
589
		) );
590
591
		$args = array(
592
			'user_id' => get_current_user_id(),
593
			'service' => $request[ 'service' ],
594
		);
595
596
		if ( isset( $request[ 'keyring_id' ] ) ) {
597
			$args[ 'keyring_id' ] = $request[ 'keyring_id' ];
598
		}
599
600
		$xml->query( 'jetpack.isSiteVerified', $args );
601
602
		if ( $xml->isError() ) {
603
			return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
604
		} else {
605
			return $xml->getResponse();
606
		}
607
	}
608
609
610
611
	public static function verify_site( $request ) {
612
		Jetpack::load_xml_rpc_client();
613
		$xml = new Jetpack_IXR_Client( array(
614
			'user_id' => get_current_user_id(),
615
		) );
616
617
		$params = $request->get_json_params();
618
619
		$xml->query( 'jetpack.verifySite', array(
620
				'user_id' => get_current_user_id(),
621
				'service' => $request[ 'service' ],
622
				'keyring_id' => $params[ 'keyring_id' ],
623
			)
624
		);
625
626
		if ( $xml->isError() ) {
627
			return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
628
		} else {
629
			$response = $xml->getResponse();
630
631
			if ( ! empty( $response['errors'] ) ) {
632
				$error = new WP_Error;
633
				$error->errors = $response['errors'];
634
				return $error;
635
			}
636
637
			return $response;
638
		}
639
	}
640
641
	/**
642
	 * Handles verification that a site is registered
643
	 *
644
	 * @since 5.4.0
645
	 *
646
	 * @param WP_REST_Request $request The request sent to the WP REST API.
647
	 *
648
	 * @return array|wp-error
649
	 */
650
	 public static function remote_authorize( $request ) {
651
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
652
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
653
		$result = $xmlrpc_server->remote_authorize( $request );
654
655
		if ( is_a( $result, 'IXR_Error' ) ) {
656
			$result = new WP_Error( $result->code, $result->message );
657
		}
658
659
		return $result;
660
	 }
661
662
	/**
663
	 * Handles dismissing of Jetpack Notices
664
	 *
665
	 * @since 4.3.0
666
	 *
667
	 * @param WP_REST_Request $request The request sent to the WP REST API.
668
	 *
669
	 * @return array|wp-error
670
	 */
671
	public static function dismiss_notice( $request ) {
672
		$notice = $request['notice'];
673
674
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
675
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
676
		}
677
678
		if ( isset( $notice ) && ! empty( $notice ) ) {
679
			switch( $notice ) {
680
				case 'feedback_dash_request':
681
				case 'welcome':
682
					$notices = get_option( 'jetpack_dismissed_notices', array() );
683
					$notices[ $notice ] = true;
684
					update_option( 'jetpack_dismissed_notices', $notices );
685
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
686
687
				default:
688
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
689
			}
690
		}
691
692
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
693
	}
694
695
	/**
696
	 * Verify that the user can disconnect the site.
697
	 *
698
	 * @since 4.3.0
699
	 *
700
	 * @return bool|WP_Error True if user is able to disconnect the site.
701
	 */
702
	public static function disconnect_site_permission_callback() {
703
		if ( current_user_can( 'jetpack_disconnect' ) ) {
704
			return true;
705
		}
706
707
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
708
709
	}
710
711
	/**
712
	 * Verify that the user can get a connect/link URL
713
	 *
714
	 * @since 4.3.0
715
	 *
716
	 * @return bool|WP_Error True if user is able to disconnect the site.
717
	 */
718 View Code Duplication
	public static function connect_url_permission_callback() {
719
		if ( current_user_can( 'jetpack_connect_user' ) ) {
720
			return true;
721
		}
722
723
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
724
725
	}
726
727
	/**
728
	 * Verify that a user can get the data about the current user.
729
	 * Only those who can connect.
730
	 *
731
	 * @since 4.3.0
732
	 *
733
	 * @uses Jetpack::is_user_connected();
734
	 *
735
	 * @return bool|WP_Error True if user is able to unlink.
736
	 */
737 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
738
		if ( current_user_can( 'jetpack_connect_user' ) ) {
739
			return true;
740
		}
741
742
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
743
	}
744
745
	/**
746
	 * Check that user has permission to change the master user.
747
	 *
748
	 * @since 6.2.0
749
	 *
750
	 * @return bool|WP_Error True if user is able to change master user.
751
	 */
752 View Code Duplication
	public static function set_connection_owner_permission_callback() {
753
		if ( get_current_user_id() === Jetpack_Options::get_option( 'master_user' ) ) {
754
			return true;
755
		}
756
757
		return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
758
	}
759
760
	/**
761
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
762
	 *
763
	 * @since 4.3.0
764
	 *
765
	 * @uses Jetpack::is_user_connected();
766
	 *
767
	 * @return bool|WP_Error True if user is able to unlink.
768
	 */
769 View Code Duplication
	public static function unlink_user_permission_callback() {
770
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
771
			return true;
772
		}
773
774
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
775
	}
776
777
	/**
778
	 * Verify that user can manage Jetpack modules.
779
	 *
780
	 * @since 4.3.0
781
	 *
782
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
783
	 */
784
	public static function manage_modules_permission_check() {
785
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
786
			return true;
787
		}
788
789
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
790
	}
791
792
	/**
793
	 * Verify that user can update Jetpack modules.
794
	 *
795
	 * @since 4.3.0
796
	 *
797
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
798
	 */
799 View Code Duplication
	public static function configure_modules_permission_check() {
800
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
801
			return true;
802
		}
803
804
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
805
	}
806
807
	/**
808
	 * Verify that user can view Jetpack admin page.
809
	 *
810
	 * @since 4.3.0
811
	 *
812
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
813
	 */
814 View Code Duplication
	public static function view_admin_page_permission_check() {
815
		if ( current_user_can( 'jetpack_admin_page' ) ) {
816
			return true;
817
		}
818
819
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
820
	}
821
822
	/**
823
	 * Verify that user can mitigate an identity crisis.
824
	 *
825
	 * @since 4.4.0
826
	 *
827
	 * @return bool Whether user has capability 'jetpack_disconnect'.
828
	 */
829
	public static function identity_crisis_mitigation_permission_check() {
830
		if ( current_user_can( 'jetpack_disconnect' ) ) {
831
			return true;
832
		}
833
834
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
835
	}
836
837
	/**
838
	 * Verify that user can update Jetpack general settings.
839
	 *
840
	 * @since 4.3.0
841
	 *
842
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
843
	 */
844 View Code Duplication
	public static function update_settings_permission_check() {
845
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
846
			return true;
847
		}
848
849
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
850
	}
851
852
	/**
853
	 * Verify that user can view Jetpack admin page and can activate plugins.
854
	 *
855
	 * @since 4.3.0
856
	 *
857
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
858
	 */
859 View Code Duplication
	public static function activate_plugins_permission_check() {
860
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
861
			return true;
862
		}
863
864
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
865
	}
866
867
	/**
868
	 * Verify that user can edit other's posts (Editors and Administrators).
869
	 *
870
	 * @return bool Whether user has the capability 'edit_others_posts'.
871
	 */
872
	public static function edit_others_posts_check() {
873
		if ( current_user_can( 'edit_others_posts' ) ) {
874
			return true;
875
		}
876
877
		return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
878
	}
879
880
	/**
881
	 * Contextual HTTP error code for authorization failure.
882
	 *
883
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
884
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
885
	 *
886
	 * @since 4.3.0
887
	 *
888
	 * @return int
889
	 */
890
	public static function rest_authorization_required_code() {
891
		return is_user_logged_in() ? 403 : 401;
892
	}
893
894
	/**
895
	 * Get connection status for this Jetpack site.
896
	 *
897
	 * @since 4.3.0
898
	 *
899
	 * @return bool True if site is connected
900
	 */
901
	public static function jetpack_connection_status() {
902
		return rest_ensure_response( array(
903
				'isActive'  => Jetpack::is_active(),
904
				'isStaging' => Jetpack::is_staging_site(),
905
				'devMode'   => array(
906
					'isActive' => Jetpack::is_development_mode(),
907
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
908
					'url'      => site_url() && false === strpos( site_url(), '.' ),
909
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
910
				),
911
			)
912
		);
913
	}
914
915
	/**
916
	 * Test connection status for this Jetpack site.
917
	 *
918
	 * @since 6.8.0
919
	 *
920
	 * @return array|WP_Error WP_Error returned if connection test does not succeed.
921
	 */
922
	public static function jetpack_connection_test() {
923
		jetpack_require_lib( 'debugger' );
924
		$cxntests = new Jetpack_Cxn_Tests();
925
926
		if ( $cxntests->pass() ) {
927
			return rest_ensure_response(
928
				array(
929
					'code'    => 'success',
930
					'message' => __( 'All connection tests passed.', 'jetpack' ),
931
				)
932
			);
933
		} else {
934
			return $cxntests->output_fails_as_wp_error();
935
		}
936
	}
937
938
	/**
939
	 * Test connection permission check method.
940
	 *
941
	 * @since 7.1.0
942
	 *
943
	 * @return bool
944
	 */
945
	public static function view_jetpack_connection_test_check() {
946
		if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'] ) ) {
947
			return false;
948
		}
949
		$signature = base64_decode( $_GET['signature'] );
950
951
		$signature_data = wp_json_encode(
952
			array(
953
				'rest_route' => $_GET['rest_route'],
954
				'timestamp' => intval( $_GET['timestamp'] ),
955
				'url' => wp_unslash( $_GET['url'] ),
956
			)
957
		);
958
959
		if (
960
			! function_exists( 'openssl_verify' )
961
			|| ! openssl_verify(
962
				$signature_data,
963
				$signature,
964
				JETPACK__DEBUGGER_PUBLIC_KEY
965
			)
966
		) {
967
			return false;
968
		}
969
970
		// signature timestamp must be within 5min of current time
971
		if ( abs( time() - intval( $_GET['timestamp'] ) ) > 300 ) {
972
			return false;
973
		}
974
975
		return true;
976
	}
977
978
	/**
979
	 * Test connection status for this Jetpack site, encrypt the results for decryption by a third-party.
980
	 *
981
	 * @since 7.1.0
982
	 *
983
	 * @return array|mixed|object|WP_Error
984
	 */
985
	public static function jetpack_connection_test_for_external() {
986
		// Since we are running this test for inclusion in the WP.com testing suite, let's not try to run them as part of these results.
987
		add_filter( 'jetpack_debugger_run_self_test', '__return_false' );
988
		jetpack_require_lib( 'debugger' );
989
		$cxntests = new Jetpack_Cxn_Tests();
990
991
		if ( $cxntests->pass() ) {
992
			$result = array(
993
				'code'    => 'success',
994
				'message' => __( 'All connection tests passed.', 'jetpack' ),
995
			);
996
		} else {
997
			$error  = $cxntests->output_fails_as_wp_error(); // Using this so the output is similar both ways.
998
			$errors = array();
999
1000
			// Borrowed from WP_REST_Server::error_to_response().
1001
			foreach ( (array) $error->errors as $code => $messages ) {
1002
				foreach ( (array) $messages as $message ) {
1003
					$errors[] = array(
1004
						'code'    => $code,
1005
						'message' => $message,
1006
						'data'    => $error->get_error_data( $code ),
1007
					);
1008
				}
1009
			}
1010
1011
			$result = $errors[0];
1012
			if ( count( $errors ) > 1 ) {
1013
				// Remove the primary error.
1014
				array_shift( $errors );
1015
				$result['additional_errors'] = $errors;
1016
			}
1017
		}
1018
1019
		$result = wp_json_encode( $result );
1020
1021
		$encrypted = $cxntests->encrypt_string_for_wpcom( $result );
1022
1023
		if ( ! $encrypted || ! is_array( $encrypted ) ) {
1024
			return rest_ensure_response(
1025
				array(
1026
					'code'    => 'action_required',
1027
					'message' => 'Please request results from the in-plugin debugger',
1028
				)
1029
			);
1030
		}
1031
1032
		return rest_ensure_response(
1033
			array(
1034
				'code'  => 'response',
1035
				'debug' => array(
1036
					'data' => $encrypted['data'],
1037
					'key'  => $encrypted['key'],
1038
				),
1039
			)
1040
		);
1041
	}
1042
1043
	public static function rewind_data() {
1044
		$site_id = Jetpack_Options::get_option( 'id' );
1045
1046
		if ( ! $site_id ) {
1047
			return new WP_Error( 'site_id_missing' );
1048
		}
1049
1050
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' );
1051
1052
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1053
			return new WP_Error( 'rewind_data_fetch_failed' );
1054
		}
1055
1056
		$body = wp_remote_retrieve_body( $response );
1057
1058
		return json_decode( $body );
1059
	}
1060
1061
	/**
1062
	 * Get rewind data
1063
	 *
1064
	 * @since 5.7.0
1065
	 *
1066
	 * @return array Array of rewind properties.
1067
	 */
1068
	public static function get_rewind_data() {
1069
		$rewind_data = self::rewind_data();
1070
1071 View Code Duplication
		if ( ! is_wp_error( $rewind_data ) ) {
1072
			return rest_ensure_response( array(
1073
					'code' => 'success',
1074
					'message' => esc_html__( 'Backup & Scan data correctly received.', 'jetpack' ),
1075
					'data' => wp_json_encode( $rewind_data ),
1076
				)
1077
			);
1078
		}
1079
1080 View Code Duplication
		if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) {
1081
			return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1082
		}
1083
1084 View Code Duplication
		if ( $rewind_data->get_error_code() === 'site_id_missing' ) {
1085
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1086
		}
1087
1088
		return new WP_Error(
1089
			'error_get_rewind_data',
1090
			esc_html__( 'Could not retrieve Backup & Scan data.', 'jetpack' ),
1091
			array( 'status' => 500 )
1092
		);
1093
	}
1094
1095
	/**
1096
	 * Disconnects Jetpack from the WordPress.com Servers
1097
	 *
1098
	 * @uses Jetpack::disconnect();
1099
	 * @since 4.3.0
1100
	 *
1101
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1102
	 *
1103
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
1104
	 */
1105 View Code Duplication
	public static function disconnect_site( $request ) {
1106
1107
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
1108
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1109
		}
1110
1111
		if ( Jetpack::is_active() ) {
1112
			Jetpack::disconnect();
1113
			return rest_ensure_response( array( 'code' => 'success' ) );
1114
		}
1115
1116
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1117
	}
1118
1119
	/**
1120
	 * Gets a new connect raw URL with fresh nonce.
1121
	 *
1122
	 * @uses Jetpack::disconnect();
1123
	 * @since 4.3.0
1124
	 *
1125
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1126
	 *
1127
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
1128
	 */
1129
	public static function build_connect_url() {
1130
		$url = Jetpack::init()->build_connect_url( true, false, false );
1131
		if ( $url ) {
1132
			return rest_ensure_response( $url );
1133
		}
1134
1135
		return new WP_Error( 'build_connect_url_failed', esc_html__( 'Unable to build the connect URL.  Please reload the page and try again.', 'jetpack' ), array( 'status' => 400 ) );
1136
	}
1137
1138
	/**
1139
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
1140
	 * Information about the master/primary user.
1141
	 * Information about the current user.
1142
	 *
1143
	 * @since 4.3.0
1144
	 *
1145
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1146
	 *
1147
	 * @return object
1148
	 */
1149
	public static function get_user_connection_data() {
1150
		require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' );
1151
1152
		$response = array(
1153
//			'othersLinked' => Jetpack::get_other_linked_admins(),
1154
			'currentUser'  => jetpack_current_user_data(),
1155
		);
1156
		return rest_ensure_response( $response );
1157
	}
1158
1159
	/**
1160
	 * Change the master user.
1161
	 *
1162
	 * @since 6.2.0
1163
	 *
1164
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1165
	 *
1166
	 * @return bool|WP_Error True if owner successfully changed.
1167
	 */
1168
	public static function set_connection_owner( $request ) {
1169
		if ( ! isset( $request['owner'] ) ) {
1170
			return new WP_Error(
1171
				'invalid_param',
1172
				esc_html__( 'Invalid Parameter', 'jetpack' ),
1173
				array( 'status' => 400 )
1174
			);
1175
		}
1176
1177
		$new_owner_id = $request['owner'];
1178
		if ( ! user_can( $new_owner_id, 'administrator' ) ) {
1179
			return new WP_Error(
1180
				'new_owner_not_admin',
1181
				esc_html__( 'New owner is not admin', 'jetpack' ),
1182
				array( 'status' => 400 )
1183
			);
1184
		}
1185
1186
		if ( $new_owner_id === get_current_user_id() ) {
1187
			return new WP_Error(
1188
				'new_owner_is_current_user',
1189
				esc_html__( 'New owner is same as current user', 'jetpack' ),
1190
				array( 'status' => 400 )
1191
			);
1192
		}
1193
1194
		if ( ! Jetpack::is_user_connected( $new_owner_id ) ) {
1195
			return new WP_Error(
1196
				'new_owner_not_connected',
1197
				esc_html__( 'New owner is not connected', 'jetpack' ),
1198
				array( 'status' => 400 )
1199
			);
1200
		}
1201
1202
		// Update the master user in Jetpack
1203
		$updated = Jetpack_Options::update_option( 'master_user', $new_owner_id );
1204
1205
		// Notify WPCOM about the master user change
1206
		Jetpack::load_xml_rpc_client();
1207
		$xml = new Jetpack_IXR_Client( array(
1208
			'user_id' => get_current_user_id(),
1209
		) );
1210
		$xml->query( 'jetpack.switchBlogOwner', array(
1211
			'new_blog_owner' => $new_owner_id,
1212
		) );
1213
1214
		if ( $updated && ! $xml->isError() ) {
1215
			return rest_ensure_response(
1216
				array(
1217
					'code' => 'success',
1218
				)
1219
			);
1220
		}
1221
		return new WP_Error(
1222
			'error_setting_new_owner',
1223
			esc_html__( 'Could not confirm new owner.', 'jetpack' ),
1224
			array( 'status' => 500 )
1225
		);
1226
	}
1227
1228
	/**
1229
	 * Unlinks current user from the WordPress.com Servers.
1230
	 *
1231
	 * @since 4.3.0
1232
	 * @uses  Jetpack::unlink_user
1233
	 *
1234
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1235
	 *
1236
	 * @return bool|WP_Error True if user successfully unlinked.
1237
	 */
1238 View Code Duplication
	public static function unlink_user( $request ) {
1239
1240
		if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) {
1241
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1242
		}
1243
1244
		if ( Jetpack::unlink_user() ) {
1245
			return rest_ensure_response(
1246
				array(
1247
					'code' => 'success'
1248
				)
1249
			);
1250
		}
1251
1252
		return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1253
	}
1254
1255
	/**
1256
	 * Gets current user's tracking settings.
1257
	 *
1258
	 * @since 6.0.0
1259
	 *
1260
	 * @param  WP_REST_Request $request The request sent to the WP REST API.
1261
	 *
1262
	 * @return WP_REST_Response|WP_Error Response, else error.
1263
	 */
1264 View Code Duplication
	public static function get_user_tracking_settings( $request ) {
1265
		if ( ! Jetpack::is_user_connected() ) {
1266
			$response = array(
1267
				'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1268
			);
1269
		} else {
1270
			$response = Jetpack_Client::wpcom_json_api_request_as_user(
1271
				'/jetpack-user-tracking',
1272
				'v2',
1273
				array(
1274
					'method'  => 'GET',
1275
					'headers' => array(
1276
						'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1277
					),
1278
				)
1279
			);
1280
			if ( ! is_wp_error( $response ) ) {
1281
				$response = json_decode( wp_remote_retrieve_body( $response ), true );
1282
			}
1283
		}
1284
1285
		return rest_ensure_response( $response );
1286
	}
1287
1288
	/**
1289
	 * Updates current user's tracking settings.
1290
	 *
1291
	 * @since 6.0.0
1292
	 *
1293
	 * @param  WP_REST_Request $request The request sent to the WP REST API.
1294
	 *
1295
	 * @return WP_REST_Response|WP_Error Response, else error.
1296
	 */
1297 View Code Duplication
	public static function update_user_tracking_settings( $request ) {
1298
		if ( ! Jetpack::is_user_connected() ) {
1299
			$response = array(
1300
				'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1301
			);
1302
		} else {
1303
			$response = Jetpack_Client::wpcom_json_api_request_as_user(
1304
				'/jetpack-user-tracking',
1305
				'v2',
1306
				array(
1307
					'method'  => 'PUT',
1308
					'headers' => array(
1309
						'Content-Type'    => 'application/json',
1310
						'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1311
					),
1312
				),
1313
				wp_json_encode( $request->get_params() )
1314
			);
1315
			if ( ! is_wp_error( $response ) ) {
1316
				$response = json_decode( wp_remote_retrieve_body( $response ), true );
1317
			}
1318
		}
1319
1320
		return rest_ensure_response( $response );
1321
	}
1322
1323
	/**
1324
	 * Fetch site data from .com including the site's current plan.
1325
	 *
1326
	 * @since 5.5.0
1327
	 *
1328
	 * @return array Array of site properties.
1329
	 */
1330
	public static function site_data() {
1331
		$site_id = Jetpack_Options::get_option( 'id' );
1332
1333
		if ( ! $site_id ) {
1334
			new WP_Error( 'site_id_missing' );
1335
		}
1336
1337
		$args = array( 'headers' => array() );
1338
1339
		// Allow use a store sandbox. Internal ref: PCYsg-IA-p2.
1340
		if ( isset( $_COOKIE ) && isset( $_COOKIE['store_sandbox'] ) ) {
1341
			$secret                    = $_COOKIE['store_sandbox'];
1342
			$args['headers']['Cookie'] = "store_sandbox=$secret;";
1343
		}
1344
1345
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1', $args );
1346
1347
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1348
			return new WP_Error( 'site_data_fetch_failed' );
1349
		}
1350
1351
		Jetpack_Plan::update_from_sites_response( $response );
1352
1353
		$body = wp_remote_retrieve_body( $response );
1354
1355
		return json_decode( $body );
1356
	}
1357
	/**
1358
	 * Get site data, including for example, the site's current plan.
1359
	 *
1360
	 * @since 4.3.0
1361
	 *
1362
	 * @return array Array of site properties.
1363
	 */
1364
	public static function get_site_data() {
1365
		$site_data = self::site_data();
1366
1367 View Code Duplication
		if ( ! is_wp_error( $site_data ) ) {
1368
			return rest_ensure_response( array(
1369
					'code' => 'success',
1370
					'message' => esc_html__( 'Site data correctly received.', 'jetpack' ),
1371
					'data' => json_encode( $site_data ),
1372
				)
1373
			);
1374
		}
1375 View Code Duplication
		if ( $site_data->get_error_code() === 'site_data_fetch_failed' ) {
1376
			return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1377
		}
1378
1379 View Code Duplication
		if ( $site_data->get_error_code() === 'site_id_missing' ) {
1380
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1381
		}
1382
	}
1383
1384
	/**
1385
	 * Fetch AL data for this site and return it.
1386
	 *
1387
	 * @since 7.4
1388
	 *
1389
	 * @return array|WP_Error
1390
	 */
1391
	public static function get_site_activity() {
1392
		$site_id = Jetpack_Options::get_option( 'id' );
1393
1394
		if ( ! $site_id ) {
1395
			return new WP_Error(
1396
				'site_id_missing',
1397
				esc_html__( 'Site ID is missing.', 'jetpack' ),
1398
				array( 'status' => 400 )
1399
			);
1400
		}
1401
1402
		$response = Jetpack_Client::wpcom_json_api_request_as_user( "/sites/$site_id/activity", '2', array(
1403
			'method'  => 'GET',
1404
			'headers' => array(
1405
				'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1406
			),
1407
		), null, 'wpcom' );
1408
		$response_code = wp_remote_retrieve_response_code( $response );
1409
1410 View Code Duplication
		if ( 200 !== $response_code ) {
1411
			return new WP_Error(
1412
				'activity_fetch_failed',
1413
				esc_html__( 'Could not retrieve site activity.', 'jetpack' ),
1414
				array( 'status' => $response_code )
1415
			);
1416
		}
1417
1418
		$data = json_decode( wp_remote_retrieve_body( $response ) );
1419
1420
		if ( ! isset( $data->current->orderedItems ) ) {
1421
			return new WP_Error(
1422
				'activity_not_found',
1423
				esc_html__( 'No activity found', 'jetpack' ),
1424
				array( 'status' => 204 ) // no content
1425
			);
1426
		}
1427
1428
		return rest_ensure_response( array(
1429
				'code' => 'success',
1430
				'data' => $data->current->orderedItems,
1431
			)
1432
		);
1433
	}
1434
1435
	/**
1436
	 * Handles identity crisis mitigation, confirming safe mode for this site.
1437
	 *
1438
	 * @since 4.4.0
1439
	 *
1440
	 * @return bool | WP_Error True if option is properly set.
1441
	 */
1442
	public static function confirm_safe_mode() {
1443
		$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
1444
		if ( $updated ) {
1445
			return rest_ensure_response(
1446
				array(
1447
					'code' => 'success'
1448
				)
1449
			);
1450
		}
1451
		return new WP_Error(
1452
			'error_setting_jetpack_safe_mode',
1453
			esc_html__( 'Could not confirm safe mode.', 'jetpack' ),
1454
			array( 'status' => 500 )
1455
		);
1456
	}
1457
1458
	/**
1459
	 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
1460
	 *
1461
	 * @since 4.4.0
1462
	 *
1463
	 * @return bool | WP_Error True if option is properly set.
1464
	 */
1465
	public static function migrate_stats_and_subscribers() {
1466
		if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
1467
			return new WP_Error(
1468
				'error_deleting_sync_error_idc',
1469
				esc_html__( 'Could not delete sync error option.', 'jetpack' ),
1470
				array( 'status' => 500 )
1471
			);
1472
		}
1473
1474
		if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
1475
			return rest_ensure_response(
1476
				array(
1477
					'code' => 'success'
1478
				)
1479
			);
1480
		}
1481
		return new WP_Error(
1482
			'error_setting_jetpack_migrate',
1483
			esc_html__( 'Could not confirm migration.', 'jetpack' ),
1484
			array( 'status' => 500 )
1485
		);
1486
	}
1487
1488
	/**
1489
	 * This IDC resolution will disconnect the site and re-connect to a completely new
1490
	 * and separate shadow site than the original.
1491
	 *
1492
	 * It will first will disconnect the site without phoning home as to not disturb the production site.
1493
	 * It then builds a fresh connection URL and sends it back along with the response.
1494
	 *
1495
	 * @since 4.4.0
1496
	 * @return bool|WP_Error
1497
	 */
1498
	public static function start_fresh_connection() {
1499
		// First clear the options / disconnect.
1500
		Jetpack::disconnect();
1501
		return self::build_connect_url();
1502
	}
1503
1504
	/**
1505
	 * Reset Jetpack options
1506
	 *
1507
	 * @since 4.3.0
1508
	 *
1509
	 * @param WP_REST_Request $request {
1510
	 *     Array of parameters received by request.
1511
	 *
1512
	 *     @type string $options Available options to reset are options|modules
1513
	 * }
1514
	 *
1515
	 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
1516
	 */
1517
	public static function reset_jetpack_options( $request ) {
1518
1519
		if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) {
1520
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1521
		}
1522
1523
		if ( isset( $request['options'] ) ) {
1524
			$data = $request['options'];
1525
1526
			switch( $data ) {
1527
				case ( 'options' ) :
1528
					$options_to_reset = Jetpack::get_jetpack_options_for_reset();
1529
1530
					// Reset the Jetpack options
1531
					foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
1532
						Jetpack_Options::delete_option( $option_to_reset );
1533
					}
1534
1535
					foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
1536
						delete_option( $option_to_reset );
1537
					}
1538
1539
					// Reset to default modules
1540
					$default_modules = Jetpack::get_default_modules();
1541
					Jetpack::update_active_modules( $default_modules );
1542
1543
					return rest_ensure_response( array(
1544
						'code' 	  => 'success',
1545
						'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ),
1546
					) );
1547
					break;
1548
1549
				case 'modules':
1550
					$default_modules = Jetpack::get_default_modules();
1551
					Jetpack::update_active_modules( $default_modules );
1552
					return rest_ensure_response( array(
1553
						'code' 	  => 'success',
1554
						'message' => esc_html__( 'Modules reset to default.', 'jetpack' ),
1555
					) );
1556
					break;
1557
1558
				default:
1559
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1560
			}
1561
		}
1562
1563
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
1564
	}
1565
1566
	/**
1567
	 * Get the query parameters to update module options or general settings.
1568
	 *
1569
	 * @since 4.3.0
1570
	 * @since 4.4.0 Accepts a $selector parameter.
1571
	 *
1572
	 * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'.
1573
	 *
1574
	 * @return array
1575
	 */
1576
	public static function get_updateable_parameters( $selector = '' ) {
1577
		$parameters = array(
1578
			'context'     => array(
1579
				'default' => 'edit',
1580
			),
1581
		);
1582
1583
		return array_merge( $parameters, self::get_updateable_data_list( $selector ) );
1584
	}
1585
1586
	/**
1587
	 * Returns a list of module options or general settings that can be updated.
1588
	 *
1589
	 * @since 4.3.0
1590
	 * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list.
1591
	 *
1592
	 * @param string|array $selector Module slug, 'any', or an array of parameters.
1593
	 *                               If empty, it's assumed we're updating a module and we'll try to get its slug.
1594
	 *                               If 'any' the full list is returned.
1595
	 *                               If it's an array of parameters, includes the elements by matching keys.
1596
	 *
1597
	 * @return array
1598
	 */
1599
	public static function get_updateable_data_list( $selector = '' ) {
1600
1601
		$options = array(
1602
1603
			// Carousel
1604
			'carousel_background_color' => array(
1605
				'description'       => esc_html__( 'Color scheme.', 'jetpack' ),
1606
				'type'              => 'string',
1607
				'default'           => 'black',
1608
				'enum'              => array(
1609
					'black',
1610
					'white',
1611
				),
1612
				'enum_labels' => array(
1613
					'black' => esc_html__( 'Black', 'jetpack' ),
1614
					'white' => esc_html__( 'White', 'jetpack' ),
1615
				),
1616
				'validate_callback' => __CLASS__ . '::validate_list_item',
1617
				'jp_group'          => 'carousel',
1618
			),
1619
			'carousel_display_exif' => array(
1620
				'description'       => wp_kses( sprintf( __( 'Show photo metadata (<a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ), array( 'a' => array( 'href' => true, 'target' => true ) ) ),
1621
				'type'              => 'boolean',
1622
				'default'           => 0,
1623
				'validate_callback' => __CLASS__ . '::validate_boolean',
1624
				'jp_group'          => 'carousel',
1625
			),
1626
1627
			// Comments
1628
			'highlander_comment_form_prompt' => array(
1629
				'description'       => esc_html__( 'Greeting Text', 'jetpack' ),
1630
				'type'              => 'string',
1631
				'default'           => esc_html__( 'Leave a Reply', 'jetpack' ),
1632
				'sanitize_callback' => 'sanitize_text_field',
1633
				'jp_group'          => 'comments',
1634
			),
1635
			'jetpack_comment_form_color_scheme' => array(
1636
				'description'       => esc_html__( "Color scheme", 'jetpack' ),
1637
				'type'              => 'string',
1638
				'default'           => 'light',
1639
				'enum'              => array(
1640
					'light',
1641
					'dark',
1642
					'transparent',
1643
				),
1644
				'enum_labels' => array(
1645
					'light'       => esc_html__( 'Light', 'jetpack' ),
1646
					'dark'        => esc_html__( 'Dark', 'jetpack' ),
1647
					'transparent' => esc_html__( 'Transparent', 'jetpack' ),
1648
				),
1649
				'validate_callback' => __CLASS__ . '::validate_list_item',
1650
				'jp_group'          => 'comments',
1651
			),
1652
1653
			// Custom Content Types
1654
			'jetpack_portfolio' => array(
1655
				'description'       => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1656
				'type'              => 'boolean',
1657
				'default'           => 0,
1658
				'validate_callback' => __CLASS__ . '::validate_boolean',
1659
				'jp_group'          => 'custom-content-types',
1660
			),
1661
			'jetpack_portfolio_posts_per_page' => array(
1662
				'description'       => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1663
				'type'              => 'integer',
1664
				'default'           => 10,
1665
				'validate_callback' => __CLASS__ . '::validate_posint',
1666
				'jp_group'          => 'custom-content-types',
1667
			),
1668
			'jetpack_testimonial' => array(
1669
				'description'       => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1670
				'type'              => 'boolean',
1671
				'default'           => 0,
1672
				'validate_callback' => __CLASS__ . '::validate_boolean',
1673
				'jp_group'          => 'custom-content-types',
1674
			),
1675
			'jetpack_testimonial_posts_per_page' => array(
1676
				'description'       => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1677
				'type'              => 'integer',
1678
				'default'           => 10,
1679
				'validate_callback' => __CLASS__ . '::validate_posint',
1680
				'jp_group'          => 'custom-content-types',
1681
			),
1682
1683
			// Galleries
1684
			'tiled_galleries' => array(
1685
				'description'       => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1686
				'type'              => 'boolean',
1687
				'default'           => 0,
1688
				'validate_callback' => __CLASS__ . '::validate_boolean',
1689
				'jp_group'          => 'tiled-gallery',
1690
			),
1691
1692
			'gravatar_disable_hovercards' => array(
1693
				'description'       => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1694
				'type'              => 'string',
1695
				'default'           => 'enabled',
1696
				// Not visible. This is used as the checkbox value.
1697
				'enum'              => array(
1698
					'enabled',
1699
					'disabled',
1700
				),
1701
				'enum_labels' => array(
1702
					'enabled'  => esc_html__( 'Enabled', 'jetpack' ),
1703
					'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1704
				),
1705
				'validate_callback' => __CLASS__ . '::validate_list_item',
1706
				'jp_group'          => 'gravatar-hovercards',
1707
			),
1708
1709
			// Infinite Scroll
1710
			'infinite_scroll' => array(
1711
				'description'       => esc_html__( 'To infinity and beyond', 'jetpack' ),
1712
				'type'              => 'boolean',
1713
				'default'           => 1,
1714
				'validate_callback' => __CLASS__ . '::validate_boolean',
1715
				'jp_group'          => 'infinite-scroll',
1716
			),
1717
			'infinite_scroll_google_analytics' => array(
1718
				'description'       => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1719
				'type'              => 'boolean',
1720
				'default'           => 0,
1721
				'validate_callback' => __CLASS__ . '::validate_boolean',
1722
				'jp_group'          => 'infinite-scroll',
1723
			),
1724
1725
			// Likes
1726
			'wpl_default' => array(
1727
				'description'       => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1728
				'type'              => 'string',
1729
				'default'           => 'on',
1730
				'enum'              => array(
1731
					'on',
1732
					'off',
1733
				),
1734
				'enum_labels' => array(
1735
					'on'  => esc_html__( 'On for all posts', 'jetpack' ),
1736
					'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1737
				),
1738
				'validate_callback' => __CLASS__ . '::validate_list_item',
1739
				'jp_group'          => 'likes',
1740
			),
1741
			'social_notifications_like' => array(
1742
				'description'       => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1743
				'type'              => 'boolean',
1744
				'default'           => 1,
1745
				'validate_callback' => __CLASS__ . '::validate_boolean',
1746
				'jp_group'          => 'likes',
1747
			),
1748
1749
			// Markdown
1750
			'wpcom_publish_comments_with_markdown' => array(
1751
				'description'       => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1752
				'type'              => 'boolean',
1753
				'default'           => 0,
1754
				'validate_callback' => __CLASS__ . '::validate_boolean',
1755
				'jp_group'          => 'markdown',
1756
			),
1757
			'wpcom_publish_posts_with_markdown' => array(
1758
				'description'       => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1759
				'type'              => 'boolean',
1760
				'default'           => 0,
1761
				'validate_callback' => __CLASS__ . '::validate_boolean',
1762
				'jp_group'          => 'markdown',
1763
			),
1764
1765
			// Mobile Theme
1766
			'wp_mobile_excerpt' => array(
1767
				'description'       => esc_html__( 'Excerpts', 'jetpack' ),
1768
				'type'              => 'boolean',
1769
				'default'           => 0,
1770
				'validate_callback' => __CLASS__ . '::validate_boolean',
1771
				'jp_group'          => 'minileven',
1772
			),
1773
			'wp_mobile_featured_images' => array(
1774
				'description'       => esc_html__( 'Featured Images', 'jetpack' ),
1775
				'type'              => 'boolean',
1776
				'default'           => 0,
1777
				'validate_callback' => __CLASS__ . '::validate_boolean',
1778
				'jp_group'          => 'minileven',
1779
			),
1780
			'wp_mobile_app_promos' => array(
1781
				'description'       => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ),
1782
				'type'              => 'boolean',
1783
				'default'           => 0,
1784
				'validate_callback' => __CLASS__ . '::validate_boolean',
1785
				'jp_group'          => 'minileven',
1786
			),
1787
1788
			// Monitor
1789
			'monitor_receive_notifications' => array(
1790
				'description'       => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1791
				'type'              => 'boolean',
1792
				'default'           => 0,
1793
				'validate_callback' => __CLASS__ . '::validate_boolean',
1794
				'jp_group'          => 'monitor',
1795
			),
1796
1797
			// Post by Email
1798
			'post_by_email_address' => array(
1799
				'description'       => esc_html__( 'Email Address', 'jetpack' ),
1800
				'type'              => 'string',
1801
				'default'           => 'noop',
1802
				'enum'              => array(
1803
					'noop',
1804
					'create',
1805
					'regenerate',
1806
					'delete',
1807
				),
1808
				'enum_labels' => array(
1809
					'noop'       => '',
1810
					'create'     => esc_html__( 'Create Post by Email address', 'jetpack' ),
1811
					'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1812
					'delete'     => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1813
				),
1814
				'validate_callback' => __CLASS__ . '::validate_list_item',
1815
				'jp_group'          => 'post-by-email',
1816
			),
1817
1818
			// Protect
1819
			'jetpack_protect_key' => array(
1820
				'description'       => esc_html__( 'Protect API key', 'jetpack' ),
1821
				'type'              => 'string',
1822
				'default'           => '',
1823
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1824
				'jp_group'          => 'protect',
1825
			),
1826
			'jetpack_protect_global_whitelist' => array(
1827
				'description'       => esc_html__( 'Protect global whitelist', 'jetpack' ),
1828
				'type'              => 'string',
1829
				'default'           => '',
1830
				'validate_callback' => __CLASS__ . '::validate_string',
1831
				'sanitize_callback' => 'esc_textarea',
1832
				'jp_group'          => 'protect',
1833
			),
1834
1835
			// Sharing
1836
			'sharing_services' => array(
1837
				'description'       => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1838
				'type'              => 'object',
1839
				'default'           => array(
1840
					'visible' => array( 'twitter', 'facebook', 'google-plus-1' ),
1841
					'hidden'  => array(),
1842
				),
1843
				'validate_callback' => __CLASS__ . '::validate_services',
1844
				'jp_group'          => 'sharedaddy',
1845
			),
1846
			'button_style' => array(
1847
				'description'       => esc_html__( 'Button Style', 'jetpack' ),
1848
				'type'              => 'string',
1849
				'default'           => 'icon',
1850
				'enum'              => array(
1851
					'icon-text',
1852
					'icon',
1853
					'text',
1854
					'official',
1855
				),
1856
				'enum_labels' => array(
1857
					'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1858
					'icon'      => esc_html__( 'Icon only', 'jetpack' ),
1859
					'text'      => esc_html__( 'Text only', 'jetpack' ),
1860
					'official'  => esc_html__( 'Official buttons', 'jetpack' ),
1861
				),
1862
				'validate_callback' => __CLASS__ . '::validate_list_item',
1863
				'jp_group'          => 'sharedaddy',
1864
			),
1865
			'sharing_label' => array(
1866
				'description'       => esc_html__( 'Sharing Label', 'jetpack' ),
1867
				'type'              => 'string',
1868
				'default'           => '',
1869
				'validate_callback' => __CLASS__ . '::validate_string',
1870
				'sanitize_callback' => 'esc_html',
1871
				'jp_group'          => 'sharedaddy',
1872
			),
1873
			'show' => array(
1874
				'description'       => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1875
				'type'              => 'array',
1876
				'items'             => array(
1877
					'type' => 'string'
1878
				),
1879
				'default'           => array( 'post' ),
1880
				'validate_callback' => __CLASS__ . '::validate_sharing_show',
1881
				'jp_group'          => 'sharedaddy',
1882
			),
1883
			'jetpack-twitter-cards-site-tag' => array(
1884
				'description'       => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
1885
				'type'              => 'string',
1886
				'default'           => '',
1887
				'validate_callback' => __CLASS__ . '::validate_twitter_username',
1888
				'sanitize_callback' => 'esc_html',
1889
				'jp_group'          => 'sharedaddy',
1890
			),
1891
			'sharedaddy_disable_resources' => array(
1892
				'description'       => esc_html__( 'Disable CSS and JS', 'jetpack' ),
1893
				'type'              => 'boolean',
1894
				'default'           => 0,
1895
				'validate_callback' => __CLASS__ . '::validate_boolean',
1896
				'jp_group'          => 'sharedaddy',
1897
			),
1898
			'custom' => array(
1899
				'description'       => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
1900
				'type'              => 'object',
1901
				'default'           => array(
1902
					'sharing_name' => '',
1903
					'sharing_url'  => '',
1904
					'sharing_icon' => '',
1905
				),
1906
				'validate_callback' => __CLASS__ . '::validate_custom_service',
1907
				'jp_group'          => 'sharedaddy',
1908
			),
1909
			// Not an option, but an action that can be perfomed on the list of custom services passing the service ID.
1910
			'sharing_delete_service' => array(
1911
				'description'       => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
1912
				'type'              => 'string',
1913
				'default'           => '',
1914
				'validate_callback' => __CLASS__ . '::validate_custom_service_id',
1915
				'jp_group'          => 'sharedaddy',
1916
			),
1917
1918
			// SSO
1919
			'jetpack_sso_require_two_step' => array(
1920
				'description'       => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
1921
				'type'              => 'boolean',
1922
				'default'           => 0,
1923
				'validate_callback' => __CLASS__ . '::validate_boolean',
1924
				'jp_group'          => 'sso',
1925
			),
1926
			'jetpack_sso_match_by_email' => array(
1927
				'description'       => esc_html__( 'Match by Email', 'jetpack' ),
1928
				'type'              => 'boolean',
1929
				'default'           => 0,
1930
				'validate_callback' => __CLASS__ . '::validate_boolean',
1931
				'jp_group'          => 'sso',
1932
			),
1933
1934
			// Subscriptions
1935
			'stb_enabled' => array(
1936
				'description'       => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ),
1937
				'type'              => 'boolean',
1938
				'default'           => 1,
1939
				'validate_callback' => __CLASS__ . '::validate_boolean',
1940
				'jp_group'          => 'subscriptions',
1941
			),
1942
			'stc_enabled' => array(
1943
				'description'       => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
1944
				'type'              => 'boolean',
1945
				'default'           => 1,
1946
				'validate_callback' => __CLASS__ . '::validate_boolean',
1947
				'jp_group'          => 'subscriptions',
1948
			),
1949
1950
			// Related Posts
1951
			'show_headline' => array(
1952
				'description'       => esc_html__( 'Highlight related content with a heading', 'jetpack' ),
1953
				'type'              => 'boolean',
1954
				'default'           => 1,
1955
				'validate_callback' => __CLASS__ . '::validate_boolean',
1956
				'jp_group'          => 'related-posts',
1957
			),
1958
			'show_thumbnails' => array(
1959
				'description'       => esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
1960
				'type'              => 'boolean',
1961
				'default'           => 0,
1962
				'validate_callback' => __CLASS__ . '::validate_boolean',
1963
				'jp_group'          => 'related-posts',
1964
			),
1965
1966
			// Verification Tools
1967
			'google' => array(
1968
				'description'       => esc_html__( 'Google Search Console', 'jetpack' ),
1969
				'type'              => 'string',
1970
				'default'           => '',
1971
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1972
				'jp_group'          => 'verification-tools',
1973
			),
1974
			'bing' => array(
1975
				'description'       => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
1976
				'type'              => 'string',
1977
				'default'           => '',
1978
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1979
				'jp_group'          => 'verification-tools',
1980
			),
1981
			'pinterest' => array(
1982
				'description'       => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
1983
				'type'              => 'string',
1984
				'default'           => '',
1985
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1986
				'jp_group'          => 'verification-tools',
1987
			),
1988
			'yandex' => array(
1989
				'description'       => esc_html__( 'Yandex Site Verification', 'jetpack' ),
1990
				'type'              => 'string',
1991
				'default'           => '',
1992
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1993
				'jp_group'          => 'verification-tools',
1994
			),
1995
			'enable_header_ad' => array(
1996
				'description'        => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
1997
				'type'               => 'boolean',
1998
				'default'            => 1,
1999
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2000
				'jp_group'           => 'wordads',
2001
			),
2002
			'wordads_approved' => array(
2003
				'description'        => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
2004
				'type'               => 'boolean',
2005
				'default'            => 0,
2006
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2007
				'jp_group'           => 'wordads',
2008
			),
2009
			'wordads_second_belowpost' => array(
2010
				'description'        => esc_html__( 'Display second ad below post?', 'jetpack' ),
2011
				'type'               => 'boolean',
2012
				'default'            => 1,
2013
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2014
				'jp_group'           => 'wordads',
2015
			),
2016
			'wordads_display_front_page' => array(
2017
				'description'        => esc_html__( 'Display ads on the front page?', 'jetpack' ),
2018
				'type'               => 'boolean',
2019
				'default'            => 1,
2020
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2021
				'jp_group'           => 'wordads',
2022
			),
2023
			'wordads_display_post' => array(
2024
				'description'        => esc_html__( 'Display ads on posts?', 'jetpack' ),
2025
				'type'               => 'boolean',
2026
				'default'            => 1,
2027
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2028
				'jp_group'           => 'wordads',
2029
			),
2030
			'wordads_display_page' => array(
2031
				'description'        => esc_html__( 'Display ads on pages?', 'jetpack' ),
2032
				'type'               => 'boolean',
2033
				'default'            => 1,
2034
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2035
				'jp_group'           => 'wordads',
2036
			),
2037
			'wordads_display_archive' => array(
2038
				'description'        => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
2039
				'type'               => 'boolean',
2040
				'default'            => 1,
2041
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2042
				'jp_group'           => 'wordads',
2043
			),
2044
			'wordads_custom_adstxt' => array(
2045
				'description'        => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
2046
				'type'               => 'string',
2047
				'default'            => '',
2048
				'validate_callback'  => __CLASS__ . '::validate_string',
2049
				'sanitize_callback'  => 'sanitize_textarea_field',
2050
				'jp_group'           => 'wordads',
2051
			),
2052
2053
			// Google Analytics
2054
			'google_analytics_tracking_id' => array(
2055
				'description'        => esc_html__( 'Google Analytics', 'jetpack' ),
2056
				'type'               => 'string',
2057
				'default'            => '',
2058
				'validate_callback'  => __CLASS__ . '::validate_alphanum',
2059
				'jp_group'           => 'google-analytics',
2060
			),
2061
2062
			// Stats
2063
			'admin_bar' => array(
2064
				'description'       => esc_html__( 'Include a small chart in your admin bar with a 48-hour traffic snapshot.', 'jetpack' ),
2065
				'type'              => 'boolean',
2066
				'default'           => 1,
2067
				'validate_callback' => __CLASS__ . '::validate_boolean',
2068
				'jp_group'          => 'stats',
2069
			),
2070
			'roles' => array(
2071
				'description'       => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
2072
				'type'              => 'array',
2073
				'items'             => array(
2074
					'type' => 'string'
2075
				),
2076
				'default'           => array( 'administrator' ),
2077
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
2078
				'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
2079
				'jp_group'          => 'stats',
2080
			),
2081
			'count_roles' => array(
2082
				'description'       => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
2083
				'type'              => 'array',
2084
				'items'             => array(
2085
					'type' => 'string'
2086
				),
2087
				'default'           => array( 'administrator' ),
2088
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
2089
				'jp_group'          => 'stats',
2090
			),
2091
			'blog_id' => array(
2092
				'description'       => esc_html__( 'Blog ID.', 'jetpack' ),
2093
				'type'              => 'boolean',
2094
				'default'           => 0,
2095
				'validate_callback' => __CLASS__ . '::validate_boolean',
2096
				'jp_group'          => 'stats',
2097
			),
2098
			'do_not_track' => array(
2099
				'description'       => esc_html__( 'Do not track.', 'jetpack' ),
2100
				'type'              => 'boolean',
2101
				'default'           => 1,
2102
				'validate_callback' => __CLASS__ . '::validate_boolean',
2103
				'jp_group'          => 'stats',
2104
			),
2105
			'hide_smile' => array(
2106
				'description'       => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ),
2107
				'type'              => 'boolean',
2108
				'default'           => 1,
2109
				'validate_callback' => __CLASS__ . '::validate_boolean',
2110
				'jp_group'          => 'stats',
2111
			),
2112
			'version' => array(
2113
				'description'       => esc_html__( 'Version.', 'jetpack' ),
2114
				'type'              => 'integer',
2115
				'default'           => 9,
2116
				'validate_callback' => __CLASS__ . '::validate_posint',
2117
				'jp_group'          => 'stats',
2118
			),
2119
2120
			// Akismet - Not a module, but a plugin. The options can be passed and handled differently.
2121
			'akismet_show_user_comments_approved' => array(
2122
				'description'       => '',
2123
				'type'              => 'boolean',
2124
				'default'           => 0,
2125
				'validate_callback' => __CLASS__ . '::validate_boolean',
2126
				'jp_group'          => 'settings',
2127
			),
2128
2129
			'wordpress_api_key' => array(
2130
				'description'       => '',
2131
				'type'              => 'string',
2132
				'default'           => '',
2133
				'validate_callback' => __CLASS__ . '::validate_alphanum',
2134
				'jp_group'          => 'settings',
2135
			),
2136
2137
			// Apps card on dashboard
2138
			'dismiss_dash_app_card' => array(
2139
				'description'       => '',
2140
				'type'              => 'boolean',
2141
				'default'           => 0,
2142
				'validate_callback' => __CLASS__ . '::validate_boolean',
2143
				'jp_group'          => 'settings',
2144
			),
2145
2146
			// Empty stats card dismiss
2147
			'dismiss_empty_stats_card' => array(
2148
				'description'       => '',
2149
				'type'              => 'boolean',
2150
				'default'           => 0,
2151
				'validate_callback' => __CLASS__ . '::validate_boolean',
2152
				'jp_group'          => 'settings',
2153
			),
2154
2155
			'lang_id' => array(
2156
				'description' => esc_html__( 'Primary language for the site.', 'jetpack' ),
2157
				'type' => 'string',
2158
				'default' => 'en_US',
2159
				'jp_group' => 'settings',
2160
			),
2161
2162
			'onboarding' => array(
2163
				'description'       => '',
2164
				'type'              => 'object',
2165
				'default'           => array(
2166
					'siteTitle'          => '',
2167
					'siteDescription'    => '',
2168
					'siteType'           => 'personal',
2169
					'homepageFormat'     => 'posts',
2170
					'addContactForm'     => 0,
2171
					'businessAddress'    => array(
2172
						'name'   => '',
2173
						'street' => '',
2174
						'city'   => '',
2175
						'state'  => '',
2176
						'zip'    => '',
2177
					),
2178
					'installWooCommerce' => false,
2179
				),
2180
				'validate_callback' => __CLASS__ . '::validate_onboarding',
2181
				'jp_group'          => 'settings',
2182
			),
2183
2184
		);
2185
2186
		// Add modules to list so they can be toggled
2187
		$modules = Jetpack::get_available_modules();
2188
		if ( is_array( $modules ) && ! empty( $modules ) ) {
2189
			$module_args = array(
2190
				'description'       => '',
2191
				'type'              => 'boolean',
2192
				'default'           => 0,
2193
				'validate_callback' => __CLASS__ . '::validate_boolean',
2194
				'jp_group'          => 'modules',
2195
			);
2196
			foreach( $modules as $module ) {
2197
				$options[ $module ] = $module_args;
2198
			}
2199
		}
2200
2201
		if ( is_array( $selector ) ) {
2202
2203
			// Return only those options whose keys match $selector keys
2204
			return array_intersect_key( $options, $selector );
2205
		}
2206
2207
		if ( 'any' === $selector ) {
2208
2209
			// Toggle module or update any module option or any general setting
2210
			return $options;
2211
		}
2212
2213
		// We're updating the options for a single module.
2214
		if ( empty( $selector ) ) {
2215
			$selector = self::get_module_requested();
2216
		}
2217
		$selected = array();
2218
		foreach ( $options as $option => $attributes ) {
2219
2220
			// Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
2221
			if ( $selector === $attributes['jp_group'] ) {
2222
				$selected[ $option ] = $attributes;
2223
			}
2224
		}
2225
		return $selected;
2226
	}
2227
2228
	/**
2229
	 * Validates that the parameters are proper values that can be set during Jetpack onboarding.
2230
	 *
2231
	 * @since 5.4.0
2232
	 *
2233
	 * @param array           $onboarding_data Values to check.
2234
	 * @param WP_REST_Request $request         The request sent to the WP REST API.
2235
	 * @param string          $param           Name of the parameter passed to endpoint holding $value.
2236
	 *
2237
	 * @return bool|WP_Error
2238
	 */
2239
	public static function validate_onboarding( $onboarding_data, $request, $param ) {
2240
		if ( ! is_array( $onboarding_data ) ) {
2241
			return new WP_Error( 'invalid_param', esc_html__( 'Not valid onboarding data.', 'jetpack' ) );
2242
		}
2243
		foreach ( $onboarding_data as $value ) {
2244
			if ( is_string( $value ) ) {
2245
				$onboarding_choice = self::validate_string( $value, $request, $param );
2246
			} elseif ( is_array( $value ) ) {
2247
				$onboarding_choice = self::validate_onboarding( $value, $request, $param );
2248
			} else {
2249
				$onboarding_choice = self::validate_boolean( $value, $request, $param );
2250
			}
2251
			if ( is_wp_error( $onboarding_choice ) ) {
2252
				return $onboarding_choice;
2253
			}
2254
		}
2255
		return true;
2256
	}
2257
2258
	/**
2259
	 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
2260
	 *
2261
	 * @since 4.3.0
2262
	 *
2263
	 * @param string|bool $value Value to check.
2264
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2265
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2266
	 *
2267
	 * @return bool|WP_Error
2268
	 */
2269
	public static function validate_boolean( $value, $request, $param ) {
2270
		if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) {
2271
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) );
2272
		}
2273
		return true;
2274
	}
2275
2276
	/**
2277
	 * Validates that the parameter is a positive integer.
2278
	 *
2279
	 * @since 4.3.0
2280
	 *
2281
	 * @param int $value Value to check.
2282
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2283
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2284
	 *
2285
	 * @return bool|WP_Error
2286
	 */
2287
	public static function validate_posint( $value = 0, $request, $param ) {
2288
		if ( ! is_numeric( $value ) || $value <= 0 ) {
2289
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a positive integer.', 'jetpack' ), $param ) );
2290
		}
2291
		return true;
2292
	}
2293
2294
	/**
2295
	 * Validates that the parameter belongs to a list of admitted values.
2296
	 *
2297
	 * @since 4.3.0
2298
	 *
2299
	 * @param string $value Value to check.
2300
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2301
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2302
	 *
2303
	 * @return bool|WP_Error
2304
	 */
2305
	public static function validate_list_item( $value = '', $request, $param ) {
2306
		$attributes = $request->get_attributes();
2307
		if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
2308
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s not recognized', 'jetpack' ), $param ) );
2309
		}
2310
		$args = $attributes['args'][ $param ];
2311
		if ( ! empty( $args['enum'] ) ) {
2312
2313
			// If it's an associative array, use the keys to check that the value is among those admitted.
2314
			$enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) ? array_keys( $args['enum'] ) : $args['enum'];
2315 View Code Duplication
			if ( ! in_array( $value, $enum ) ) {
2316
				return new WP_Error( 'invalid_param_value', sprintf(
2317
					/* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
2318
					esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum )
2319
				) );
2320
			}
2321
		}
2322
		return true;
2323
	}
2324
2325
	/**
2326
	 * Validates that the parameter belongs to a list of admitted values.
2327
	 *
2328
	 * @since 4.3.0
2329
	 *
2330
	 * @param string $value Value to check.
2331
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2332
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2333
	 *
2334
	 * @return bool|WP_Error
2335
	 */
2336
	public static function validate_module_list( $value = '', $request, $param ) {
2337 View Code Duplication
		if ( ! is_array( $value ) ) {
2338
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be an array', 'jetpack' ), $param ) );
2339
		}
2340
2341
		$modules = Jetpack::get_available_modules();
2342
2343 View Code Duplication
		if ( count( array_intersect( $value, $modules ) ) != count( $value ) ) {
2344
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be a list of valid modules', 'jetpack' ), $param ) );
2345
		}
2346
2347
		return true;
2348
	}
2349
2350
	/**
2351
	 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
2352
	 *
2353
	 * @since 4.3.0
2354
	 *
2355
	 * @param string $value Value to check.
2356
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2357
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2358
	 *
2359
	 * @return bool|WP_Error
2360
	 */
2361
	public static function validate_alphanum( $value = '', $request, $param ) {
2362 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
2363
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) );
2364
		}
2365
		return true;
2366
	}
2367
2368
	/**
2369
	 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
2370
	 *
2371
	 * @since 4.6.0
2372
	 *
2373
	 * @param string $value Value to check.
2374
	 * @param WP_REST_Request $request
2375
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2376
	 *
2377
	 * @return bool|WP_Error
2378
	 */
2379
	public static function validate_verification_service( $value = '', $request, $param ) {
2380
		if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) {
2381
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
2382
		}
2383
		return true;
2384
	}
2385
2386
	/**
2387
	 * Validates that the parameter is among the roles allowed for Stats.
2388
	 *
2389
	 * @since 4.3.0
2390
	 *
2391
	 * @param string|bool $value Value to check.
2392
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2393
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2394
	 *
2395
	 * @return bool|WP_Error
2396
	 */
2397
	public static function validate_stats_roles( $value, $request, $param ) {
2398
		if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) {
2399
			return new WP_Error( 'invalid_param', sprintf(
2400
				/* Translators: first variable is the name of a parameter passed to endpoint holding the role that will be checked, the second is a list of roles allowed to see stats. The parameter is checked against this list. */
2401
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', self::$stats_roles )
2402
			) );
2403
		}
2404
		return true;
2405
	}
2406
2407
	/**
2408
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2409
	 *
2410
	 * @since 4.3.0
2411
	 *
2412
	 * @param string|bool $value Value to check.
2413
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2414
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2415
	 *
2416
	 * @return bool|WP_Error
2417
	 */
2418
	public static function validate_sharing_show( $value, $request, $param ) {
2419
		$views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
2420 View Code Duplication
		if ( ! is_array( $value ) ) {
2421
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) );
2422
		}
2423
		if ( ! array_intersect( $views, $value ) ) {
2424
			return new WP_Error( 'invalid_param', sprintf(
2425
				/* Translators: first variable is the name of a parameter passed to endpoint holding the post type where Sharing will be displayed, the second is a list of post types where Sharing can be displayed */
2426
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views )
2427
			) );
2428
		}
2429
		return true;
2430
	}
2431
2432
	/**
2433
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2434
	 *
2435
	 * @since 4.3.0
2436
	 *
2437
	 * @param string|bool $value {
2438
	 *     Value to check received by request.
2439
	 *
2440
	 *     @type array $visible List of slug of services to share to that are displayed directly in the page.
2441
	 *     @type array $hidden  List of slug of services to share to that are concealed in a folding menu.
2442
	 * }
2443
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2444
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2445
	 *
2446
	 * @return bool|WP_Error
2447
	 */
2448
	public static function validate_services( $value, $request, $param ) {
2449 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
2450
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), $param ) );
2451
		}
2452
2453
		// Allow to clear everything.
2454
		if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
2455
			return true;
2456
		}
2457
2458 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2459
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2460
		}
2461
		$sharer = new Sharing_Service();
2462
		$services = array_keys( $sharer->get_all_services() );
2463
2464
		if (
2465
			( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
2466
			||
2467
			( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) )
2468
		{
2469
			return new WP_Error( 'invalid_param', sprintf(
2470
				/* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
2471
				esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services )
2472
			) );
2473
		}
2474
		return true;
2475
	}
2476
2477
	/**
2478
	 * Validates that the parameter has enough information to build a custom sharing button.
2479
	 *
2480
	 * @since 4.3.0
2481
	 *
2482
	 * @param string|bool $value Value to check.
2483
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2484
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2485
	 *
2486
	 * @return bool|WP_Error
2487
	 */
2488
	public static function validate_custom_service( $value, $request, $param ) {
2489 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
2490
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), $param ) );
2491
		}
2492
2493
		// Allow to clear everything.
2494
		if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
2495
			return true;
2496
		}
2497
2498 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2499
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2500
		}
2501
2502
		if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
2503
		|| ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
2504
		|| ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
2505
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), $param ) );
2506
		}
2507
		return true;
2508
	}
2509
2510
	/**
2511
	 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
2512
	 *
2513
	 * @since 4.3.0
2514
	 *
2515
	 * @param string $value Value to check.
2516
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2517
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2518
	 *
2519
	 * @return bool|WP_Error
2520
	 */
2521
	public static function validate_custom_service_id( $value = '', $request, $param ) {
2522 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
2523
			return new WP_Error( 'invalid_param', sprintf( esc_html__( "%s must be a string prefixed with 'custom-' and followed by a numeric ID.", 'jetpack' ), $param ) );
2524
		}
2525
2526 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2527
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2528
		}
2529
		$sharer = new Sharing_Service();
2530
		$services = array_keys( $sharer->get_all_services() );
2531
2532 View Code Duplication
		if ( ! empty( $value ) && ! in_array( $value, $services ) ) {
2533
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), $param ) );
2534
		}
2535
2536
		return true;
2537
	}
2538
2539
	/**
2540
	 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
2541
	 *
2542
	 * @since 4.3.0
2543
	 *
2544
	 * @param string $value Value to check.
2545
	 * @param WP_REST_Request $request
2546
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2547
	 *
2548
	 * @return bool|WP_Error
2549
	 */
2550
	public static function validate_twitter_username( $value = '', $request, $param ) {
2551 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
2552
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a Twitter username.', 'jetpack' ), $param ) );
2553
		}
2554
		return true;
2555
	}
2556
2557
	/**
2558
	 * Validates that the parameter is a string.
2559
	 *
2560
	 * @since 4.3.0
2561
	 *
2562
	 * @param string $value Value to check.
2563
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2564
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2565
	 *
2566
	 * @return bool|WP_Error
2567
	 */
2568
	public static function validate_string( $value = '', $request, $param ) {
2569
		if ( ! is_string( $value ) ) {
2570
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack' ), $param ) );
2571
		}
2572
		return true;
2573
	}
2574
2575
	/**
2576
	 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
2577
	 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
2578
	 *
2579
	 * @since 4.3.0
2580
	 *
2581
	 * @param string|bool $value Value to check.
2582
	 *
2583
	 * @return bool|array
2584
	 */
2585
	public static function sanitize_stats_allowed_roles( $value ) {
2586
		if ( empty( $value ) ) {
2587
			return array( 'administrator' );
2588
		}
2589
		return $value;
2590
	}
2591
2592
	/**
2593
	 * Get the currently accessed route and return the module slug in it.
2594
	 *
2595
	 * @since 4.3.0
2596
	 *
2597
	 * @param string $route Regular expression for the endpoint with the module slug to return.
2598
	 *
2599
	 * @return array|string
2600
	 */
2601
	public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
2602
2603
		if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
2604
			return '';
2605
		}
2606
2607
		preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
2608
2609
		if ( empty( $module['slug'] ) ) {
2610
			return '';
2611
		}
2612
2613
		return $module['slug'];
2614
	}
2615
2616
	/**
2617
	 * Adds extra information for modules.
2618
	 *
2619
	 * @since 4.3.0
2620
	 *
2621
	 * @param string|array $modules Can be a single module or a list of modules.
2622
	 * @param null|string  $slug    Slug of the module in the first parameter.
2623
	 *
2624
	 * @return array|string
2625
	 */
2626
	public static function prepare_modules_for_response( $modules = '', $slug = null ) {
2627
		global $wp_rewrite;
2628
2629
		/** This filter is documented in modules/sitemaps/sitemaps.php */
2630
		$location = apply_filters( 'jetpack_sitemap_location', '' );
2631
2632
		if ( $wp_rewrite->using_index_permalinks() ) {
2633
			$sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
2634
			$news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
2635
		} else if ( $wp_rewrite->using_permalinks() ) {
2636
			$sitemap_url = home_url( $location . '/sitemap.xml' );
2637
			$news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
2638
		} else {
2639
			$sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
2640
			$news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
2641
		}
2642
2643
		if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) {
2644
			// Is a list of modules
2645
			$modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
2646
			$modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
2647
		} elseif ( 'sitemaps' == $slug ) {
2648
			// It's a single module
2649
			$modules['extra']['sitemap_url'] = $sitemap_url;
2650
			$modules['extra']['news_sitemap_url'] = $news_sitemap_url;
2651
		}
2652
		return $modules;
2653
	}
2654
2655
	/**
2656
	 * Remove 'validate_callback' item from options available for module.
2657
	 * Fetch current option value and add to array of module options.
2658
	 * Prepare values of module options that need special handling, like those saved in wpcom.
2659
	 *
2660
	 * @since 4.3.0
2661
	 *
2662
	 * @param string $module Module slug.
2663
	 * @return array
2664
	 */
2665
	public static function prepare_options_for_response( $module = '' ) {
2666
		$options = self::get_updateable_data_list( $module );
2667
2668
		if ( ! is_array( $options ) || empty( $options ) ) {
2669
			return $options;
2670
		}
2671
2672
		// Some modules need special treatment.
2673
		switch ( $module ) {
2674
2675
			case 'monitor':
2676
				// Status of user notifications
2677
				$options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
2678
				break;
2679
2680
			case 'post-by-email':
2681
				// Email address
2682
				$options['post_by_email_address']['current_value'] = self::cast_value( self::get_remote_value( 'post-by-email', 'post_by_email_address' ), $options['post_by_email_address'] );
2683
				break;
2684
2685
			case 'protect':
2686
				// Protect
2687
				$options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
2688
				if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) {
2689
					include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' );
2690
				}
2691
				$options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist();
2692
				break;
2693
2694
			case 'related-posts':
2695
				// It's local, but it must be broken apart since it's saved as an array.
2696
				$options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
2697
				break;
2698
2699
			case 'verification-tools':
2700
				// It's local, but it must be broken apart since it's saved as an array.
2701
				$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
2702
				break;
2703
2704
			case 'google-analytics':
2705
				$wga = get_option( 'jetpack_wga' );
2706
				$code = '';
2707
				if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
2708
					 $code = $wga[ 'code' ];
2709
				}
2710
				$options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
2711
				break;
2712
2713
			case 'sharedaddy':
2714
				// It's local, but it must be broken apart since it's saved as an array.
2715
				if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2716
					break;
2717
				}
2718
				$sharer = new Sharing_Service();
2719
				$options = self::split_options( $options, $sharer->get_global_options() );
2720
				$options['sharing_services']['current_value'] = $sharer->get_blog_services();
2721
				$other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
2722 View Code Duplication
				foreach ( $other_sharedaddy_options as $key ) {
2723
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2724
					$current_value = get_option( $key, $default_value );
2725
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2726
				}
2727
				break;
2728
2729
			case 'stats':
2730
				// It's local, but it must be broken apart since it's saved as an array.
2731
				if ( ! function_exists( 'stats_get_options' ) ) {
2732
					include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
2733
				}
2734
				$options = self::split_options( $options, stats_get_options() );
2735
				break;
2736
			default:
2737
				// These option are just stored as plain WordPress options.
2738 View Code Duplication
				foreach ( $options as $key => $value ) {
2739
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2740
					$current_value = get_option( $key, $default_value );
2741
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2742
				}
2743
		}
2744
		// At this point some options have current_value not set because they're options
2745
		// that only get written on update, so we set current_value to the default one.
2746
		foreach ( $options as $key => $value ) {
2747
			// We don't need validate_callback in the response
2748
			if ( isset( $options[ $key ]['validate_callback'] ) ) {
2749
				unset( $options[ $key ]['validate_callback'] );
2750
			}
2751
			$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2752
			if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
2753
				$options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
2754
			}
2755
		}
2756
		return $options;
2757
	}
2758
2759
	/**
2760
	 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
2761
	 *
2762
	 * @since 4.3.0
2763
	 *
2764
	 * @param array  $separate_options Array of options admitted by the module.
2765
	 * @param array  $grouped_options Option saved as array to be splitted.
2766
	 * @param string $prefix Optional prefix for the separate option keys.
2767
	 *
2768
	 * @return array
2769
	 */
2770
	public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
2771
		if ( is_array( $grouped_options ) ) {
2772
			foreach ( $grouped_options as $key => $value ) {
2773
				$option_key = $prefix . $key;
2774
				if ( isset( $separate_options[ $option_key ] ) ) {
2775
					$separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
2776
				}
2777
			}
2778
		}
2779
		return $separate_options;
2780
	}
2781
2782
	/**
2783
	 * Perform a casting to the value specified in the option definition.
2784
	 *
2785
	 * @since 4.3.0
2786
	 *
2787
	 * @param mixed $value Value to cast to the proper type.
2788
	 * @param array $definition Type to cast the value to.
2789
	 *
2790
	 * @return bool|float|int|string
2791
	 */
2792
	public static function cast_value( $value, $definition ) {
2793
		if ( $value === 'NULL' ) {
2794
			return null;
2795
		}
2796
2797
		if ( isset( $definition['type'] ) ) {
2798
			switch ( $definition['type'] ) {
2799
				case 'boolean':
2800
					if ( 'true' === $value ) {
2801
						return true;
2802
					} elseif ( 'false' === $value ) {
2803
						return false;
2804
					}
2805
					return (bool) $value;
2806
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2807
2808
				case 'integer':
2809
					return (int) $value;
2810
					break;
2811
2812
				case 'float':
2813
					return (float) $value;
2814
					break;
2815
2816
				case 'string':
2817
					return (string) $value;
2818
					break;
2819
			}
2820
		}
2821
		return $value;
2822
	}
2823
2824
	/**
2825
	 * Get a value not saved locally.
2826
	 *
2827
	 * @since 4.3.0
2828
	 *
2829
	 * @param string $module Module slug.
2830
	 * @param string $option Option name.
2831
	 *
2832
	 * @return bool Whether user is receiving notifications or not.
2833
	 */
2834
	public static function get_remote_value( $module, $option ) {
2835
2836
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
2837
			$option .= get_current_user_id();
2838
		}
2839
2840
		// If option doesn't exist, 'does_not_exist' will be returned.
2841
		$value = get_option( $option, 'does_not_exist' );
2842
2843
		// If option exists, just return it.
2844
		if ( 'does_not_exist' !== $value ) {
2845
			return $value;
2846
		}
2847
2848
		// Only check a remote option if Jetpack is connected.
2849
		if ( ! Jetpack::is_active() ) {
2850
			return false;
2851
		}
2852
2853
		// Do what is necessary for each module.
2854
		switch ( $module ) {
2855
			case 'monitor':
2856
				// Load the class to use the method. If class can't be found, do nothing.
2857
				if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2858
					return false;
2859
				}
2860
				$value = Jetpack_Monitor::user_receives_notifications( false );
2861
				break;
2862
2863
			case 'post-by-email':
2864
				// Load the class to use the method. If class can't be found, do nothing.
2865
				if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2866
					return false;
2867
				}
2868
				$post_by_email = new Jetpack_Post_By_Email();
2869
				$value = $post_by_email->get_post_by_email_address();
2870
				if ( $value === null ) {
2871
					$value = 'NULL'; // sentinel value so it actually gets set
2872
				}
2873
				break;
2874
		}
2875
2876
		// Normalize value to boolean.
2877
		if ( is_wp_error( $value ) || is_null( $value ) ) {
2878
			$value = false;
2879
		}
2880
2881
		// Save option to use it next time.
2882
		update_option( $option, $value );
2883
2884
		return $value;
2885
	}
2886
2887
	/**
2888
	 * Get number of plugin updates available.
2889
	 *
2890
	 * @since 4.3.0
2891
	 *
2892
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
2893
	 */
2894
	public static function get_plugin_update_count() {
2895
		$updates = wp_get_update_data();
2896
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
2897
			$count = $updates['counts']['plugins'];
2898
			if ( 0 == $count ) {
2899
				$response = array(
2900
					'code'    => 'success',
2901
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
2902
					'count'   => 0,
2903
				);
2904
			} else {
2905
				$response = array(
2906
					'code'    => 'updates-available',
2907
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
2908
					'count'   => $count,
2909
				);
2910
			}
2911
			return rest_ensure_response( $response );
2912
		}
2913
2914
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
2915
	}
2916
2917
2918
	/**
2919
	 * Returns a list of all plugins in the site.
2920
	 *
2921
	 * @since 4.2.0
2922
	 * @uses get_plugins()
2923
	 *
2924
	 * @return array
2925
	 */
2926
	private static function core_get_plugins() {
2927
		if ( ! function_exists( 'get_plugins' ) ) {
2928
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2929
		}
2930
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2931
		$plugins = apply_filters( 'all_plugins', get_plugins() );
2932
2933
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
2934
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
2935
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
2936
			}
2937
			return $plugins;
2938
		}
2939
2940
		return array();
2941
	}
2942
2943
	/**
2944
	 * Deprecated - Get third party plugin API keys.
2945
	 * @deprecated
2946
	 *
2947
	 * @param WP_REST_Request $request {
2948
	 *     Array of parameters received by request.
2949
	 *
2950
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2951
	 * }
2952
	 */
2953
	public static function get_service_api_key( $request ) {
2954
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key' );
2955
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key( $request );
2956
	}
2957
2958
	/**
2959
	 * Deprecated - Update third party plugin API keys.
2960
	 * @deprecated
2961
	 *
2962
	 * @param WP_REST_Request $request {
2963
	 *     Array of parameters received by request.
2964
	 *
2965
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2966
	 * }
2967
	 */
2968
	public static function update_service_api_key( $request ) {
2969
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key' );
2970
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key( $request ) ;
2971
	}
2972
2973
	/**
2974
	 * Deprecated - Delete a third party plugin API key.
2975
	 * @deprecated
2976
	 *
2977
	 * @param WP_REST_Request $request {
2978
	 *     Array of parameters received by request.
2979
	 *
2980
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2981
	 * }
2982
	 */
2983
	public static function delete_service_api_key( $request ) {
2984
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key' );
2985
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key( $request );
2986
	}
2987
2988
	/**
2989
	 * Deprecated - Validate the service provided in /service-api-keys/ endpoints.
2990
	 * To add a service to these endpoints, add the service name to $valid_services
2991
	 * and add '{service name}_api_key' to the non-compact return array in get_option_names(),
2992
	 * in class-jetpack-options.php
2993
	 * @deprecated
2994
	 *
2995
	 * @param string $service The service the API key is for.
2996
	 * @return string Returns the service name if valid, null if invalid.
2997
	 */
2998
	public static function validate_service_api_service( $service = null ) {
2999
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service' );
3000
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service( $service );
3001
	}
3002
3003
	/**
3004
	 * Error response for invalid service API key requests with an invalid service.
3005
	 */
3006
	public static function service_api_invalid_service_response() {
3007
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response' );
3008
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response();
3009
	}
3010
3011
	/**
3012
	 * Deprecated - Validate API Key
3013
	 * @deprecated
3014
	 *
3015
	 * @param string $key The API key to be validated.
3016
	 * @param string $service The service the API key is for.
3017
	 *
3018
	 */
3019
	public static function validate_service_api_key( $key = null, $service = null ) {
3020
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' );
3021
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key( $key , $service  );
3022
	}
3023
3024
	/**
3025
	 * Deprecated - Validate Mapbox API key
3026
	 * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php
3027
	 * @deprecated
3028
	 *
3029
	 * @param string $key The API key to be validated.
3030
	 */
3031
	public static function validate_service_api_key_mapbox( $key ) {
3032
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' );
3033
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key_mapbox( $key );
3034
3035
	}
3036
3037
	/**
3038
	 * Checks if the queried plugin is active.
3039
	 *
3040
	 * @since 4.2.0
3041
	 * @uses is_plugin_active()
3042
	 *
3043
	 * @return bool
3044
	 */
3045
	private static function core_is_plugin_active( $plugin ) {
3046
		if ( ! function_exists( 'is_plugin_active' ) ) {
3047
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
3048
		}
3049
3050
		return is_plugin_active( $plugin );
3051
	}
3052
3053
	/**
3054
	 * Get plugins data in site.
3055
	 *
3056
	 * @since 4.2.0
3057
	 *
3058
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
3059
	 */
3060
	public static function get_plugins() {
3061
		$plugins = self::core_get_plugins();
3062
3063
		if ( ! empty( $plugins ) ) {
3064
			return rest_ensure_response( $plugins );
3065
		}
3066
3067
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
3068
	}
3069
3070
	/**
3071
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
3072
	 *
3073
	 * @since 4.2.0
3074
	 *
3075
	 * @param WP_REST_Request $request {
3076
	 *     Array of parameters received by request.
3077
	 *
3078
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3079
	 * }
3080
	 *
3081
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
3082
	 */
3083
	public static function get_plugin( $request ) {
3084
3085
		$plugins = self::core_get_plugins();
3086
3087
		if ( empty( $plugins ) ) {
3088
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3089
		}
3090
3091
		$plugin = stripslashes( $request['plugin'] );
3092
3093
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
3094
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
3095
		}
3096
3097
		$plugin_data = $plugins[ $plugin ];
3098
3099
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
3100
3101
		return rest_ensure_response( array(
3102
			'code'    => 'success',
3103
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
3104
			'data'    => $plugin_data
3105
		) );
3106
	}
3107
3108
	/**
3109
	 * Proxies a request to WordPress.com to request that a magic link be sent to the current user
3110
	 * to log this user in to the mobile app via email.
3111
	 *
3112
	 * @param WP_REST_REQUEST $request The request parameters.
3113
	 * @return bool|WP_Error
3114
	 */
3115
	public static function send_mobile_magic_link( $request ) {
3116
		Jetpack::load_xml_rpc_client();
3117
		$xml = new Jetpack_IXR_Client(
3118
			array(
3119
				'user_id' => get_current_user_id(),
3120
			)
3121
		);
3122
3123
		$xml->query( 'jetpack.sendMobileMagicLink', array() );
3124
		if ( $xml->isError() ) {
3125
			return new WP_Error(
3126
				'error_sending_mobile_magic_link',
3127
				sprintf(
3128
					'%s: %s',
3129
					$xml->getErrorCode(),
3130
					$xml->getErrorMessage()
3131
				)
3132
			);
3133
		}
3134
3135
		$response = $xml->getResponse();
0 ignored issues
show
$response 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...
3136
3137
		return rest_ensure_response(
3138
			array(
3139
				'code' => 'success',
3140
			)
3141
		);
3142
	}
3143
} // class end
3144