Completed
Push — dna-constants-module ( 98001d...70f07d )
by
unknown
116:29 queued 105:00
created

_inc/lib/class.core-rest-api-endpoints.php (1 issue)

Labels
Severity

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
use Automattic\Jetpack\Connection\Client;
4
use Automattic\Jetpack\JITM;
5
6
/**
7
 * Register WP REST API endpoints for Jetpack.
8
 *
9
 * @author Automattic
10
 */
11
12
/**
13
 * Disable direct access.
14
 */
15
if ( ! defined( 'ABSPATH' ) ) {
16
	exit;
17
}
18
19
// Load WP_Error for error messages.
20
require_once ABSPATH . '/wp-includes/class-wp-error.php';
21
22
// Register endpoints when WP REST API is initialized.
23
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
24
// Load API endpoints that are synced with WP.com
25
// Each of these is a class that will register its own routes on 'rest_api_init'.
26
require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
27
28
/**
29
 * Class Jetpack_Core_Json_Api_Endpoints
30
 *
31
 * @since 4.3.0
32
 */
33
class Jetpack_Core_Json_Api_Endpoints {
34
35
	/**
36
	 * @var string Generic error message when user is not allowed to perform an action.
37
	 */
38
	public static $user_permissions_error_msg;
39
40
	/**
41
	 * @var array Roles that can access Stats once they're granted access.
42
	 */
43
	public static $stats_roles;
44
45
	/**
46
	 * Declare the Jetpack REST API endpoints.
47
	 *
48
	 * @since 4.3.0
49
	 */
50
	public static function register_endpoints() {
51
52
		// Load API endpoint base classes
53
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
54
55
		// Load API endpoints
56
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
57
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
58
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
59
60
		self::$user_permissions_error_msg = esc_html__(
61
			'You do not have the correct user permissions to perform this action.
62
			Please contact your site admin if you think this is a mistake.',
63
			'jetpack'
64
		);
65
66
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
67
68
		Jetpack::load_xml_rpc_client();
69
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
70
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
71
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
72
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
73
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
74
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
75
		$widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
76
77
		register_rest_route( 'jetpack/v4', 'plans', array(
78
			'methods'             => WP_REST_Server::READABLE,
79
			'callback'            => __CLASS__ . '::get_plans',
80
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
81
82
		) );
83
84
		register_rest_route( 'jetpack/v4', '/jitm', array(
85
			'methods'  => WP_REST_Server::READABLE,
86
			'callback' => __CLASS__ . '::get_jitm_message',
87
		) );
88
89
		register_rest_route( 'jetpack/v4', '/jitm', array(
90
			'methods'  => WP_REST_Server::CREATABLE,
91
			'callback' => __CLASS__ . '::delete_jitm_message'
92
		) );
93
94
		// Authorize a remote user
95
		register_rest_route( 'jetpack/v4', '/remote_authorize', array(
96
			'methods' => WP_REST_Server::EDITABLE,
97
			'callback' => __CLASS__ . '::remote_authorize',
98
		) );
99
100
		// Get current connection status of Jetpack
101
		register_rest_route( 'jetpack/v4', '/connection', array(
102
			'methods' => WP_REST_Server::READABLE,
103
			'callback' => __CLASS__ . '::jetpack_connection_status',
104
		) );
105
106
		// Test current connection status of Jetpack
107
		register_rest_route( 'jetpack/v4', '/connection/test', array(
108
			'methods' => WP_REST_Server::READABLE,
109
			'callback' => __CLASS__ . '::jetpack_connection_test',
110
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
111
		) );
112
113
		// Endpoint specific for privileged servers to request detailed debug information.
114
		register_rest_route( 'jetpack/v4', '/connection/test-wpcom/', array(
115
			'methods' => WP_REST_Server::READABLE,
116
			'callback' => __CLASS__ . '::jetpack_connection_test_for_external',
117
			'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check',
118
		) );
119
120
		register_rest_route( 'jetpack/v4', '/rewind', array(
121
			'methods' => WP_REST_Server::READABLE,
122
			'callback' => __CLASS__ . '::get_rewind_data',
123
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
124
		) );
125
126
		// Fetches a fresh connect URL
127
		register_rest_route( 'jetpack/v4', '/connection/url', array(
128
			'methods' => WP_REST_Server::READABLE,
129
			'callback' => __CLASS__ . '::build_connect_url',
130
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
131
		) );
132
133
		// Get current user connection data
134
		register_rest_route( 'jetpack/v4', '/connection/data', array(
135
			'methods' => WP_REST_Server::READABLE,
136
			'callback' => __CLASS__ . '::get_user_connection_data',
137
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
138
		) );
139
140
		// Set the connection owner
141
		register_rest_route( 'jetpack/v4', '/connection/owner', array(
142
			'methods' => WP_REST_Server::EDITABLE,
143
			'callback' => __CLASS__ . '::set_connection_owner',
144
			'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback',
145
		) );
146
147
		// Current user: get or set tracking settings.
148
		register_rest_route( 'jetpack/v4', '/tracking/settings', array(
149
			array(
150
				'methods'             => WP_REST_Server::READABLE,
151
				'callback'            => __CLASS__ . '::get_user_tracking_settings',
152
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
153
			),
154
			array(
155
				'methods'             => WP_REST_Server::EDITABLE,
156
				'callback'            => __CLASS__ . '::update_user_tracking_settings',
157
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
158
				'args'                => array(
159
					'tracks_opt_out' => array( 'type' => 'boolean' ),
160
				),
161
			),
162
		) );
163
164
		// Disconnect site from WordPress.com servers
165
		register_rest_route( 'jetpack/v4', '/connection', array(
166
			'methods' => WP_REST_Server::EDITABLE,
167
			'callback' => __CLASS__ . '::disconnect_site',
168
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
169
		) );
170
171
		// Disconnect/unlink user from WordPress.com servers
172
		register_rest_route( 'jetpack/v4', '/connection/user', array(
173
			'methods' => WP_REST_Server::EDITABLE,
174
			'callback' => __CLASS__ . '::unlink_user',
175
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
176
		) );
177
178
		// Get current site data
179
		register_rest_route( 'jetpack/v4', '/site', array(
180
			'methods' => WP_REST_Server::READABLE,
181
			'callback' => __CLASS__ . '::get_site_data',
182
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
183
		) );
184
185
		// Get current site data
186
		register_rest_route( 'jetpack/v4', '/site/features', array(
187
			'methods' => WP_REST_Server::READABLE,
188
			'callback' => array( $site_endpoint, 'get_features' ),
189
			'permission_callback' => array( $site_endpoint , 'can_request' ),
190
		) );
191
192
		// Get Activity Log data for this site.
193
		register_rest_route( 'jetpack/v4', '/site/activity', array(
194
			'methods' => WP_REST_Server::READABLE,
195
			'callback' => __CLASS__ . '::get_site_activity',
196
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
197
		) );
198
199
		// Confirm that a site in identity crisis should be in staging mode
200
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
201
			'methods' => WP_REST_Server::EDITABLE,
202
			'callback' => __CLASS__ . '::confirm_safe_mode',
203
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
204
		) );
205
206
		// IDC resolve: create an entirely new shadow site for this URL.
207
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
208
			'methods' => WP_REST_Server::EDITABLE,
209
			'callback' => __CLASS__ . '::start_fresh_connection',
210
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
211
		) );
212
213
		// Handles the request to migrate stats and subscribers during an identity crisis.
214
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
215
			'methods' => WP_REST_Server::EDITABLE,
216
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
217
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
218
		) );
219
220
		// Return all modules
221
		register_rest_route( 'jetpack/v4', '/module/all', array(
222
			'methods' => WP_REST_Server::READABLE,
223
			'callback' => array( $module_list_endpoint, 'process' ),
224
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
225
		) );
226
227
		// Activate many modules
228
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
229
			'methods' => WP_REST_Server::EDITABLE,
230
			'callback' => array( $module_list_endpoint, 'process' ),
231
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
232
			'args' => array(
233
				'modules' => array(
234
					'default'           => '',
235
					'type'              => 'array',
236
					'items'             => array(
237
						'type'          => 'string',
238
					),
239
					'required'          => true,
240
					'validate_callback' => __CLASS__ . '::validate_module_list',
241
				),
242
				'active' => array(
243
					'default'           => true,
244
					'type'              => 'boolean',
245
					'required'          => false,
246
					'validate_callback' => __CLASS__ . '::validate_boolean',
247
				),
248
			)
249
		) );
250
251
		// Return a single module and update it when needed
252
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
253
			'methods' => WP_REST_Server::READABLE,
254
			'callback' => array( $core_api_endpoint, 'process' ),
255
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
256
		) );
257
258
		// Activate and deactivate a module
259
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
260
			'methods' => WP_REST_Server::EDITABLE,
261
			'callback' => array( $module_toggle_endpoint, 'process' ),
262
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
263
			'args' => array(
264
				'active' => array(
265
					'default'           => true,
266
					'type'              => 'boolean',
267
					'required'          => true,
268
					'validate_callback' => __CLASS__ . '::validate_boolean',
269
				),
270
			)
271
		) );
272
273
		// Update a module
274
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
275
			'methods' => WP_REST_Server::EDITABLE,
276
			'callback' => array( $core_api_endpoint, 'process' ),
277
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
278
			'args' => self::get_updateable_parameters( 'any' )
279
		) );
280
281
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
282
		// Akismet spam count, etc.
283
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
284
			'methods' => WP_REST_Server::READABLE,
285
			'callback' => array( $module_data_endpoint, 'process' ),
286
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
287
			'args' => array(
288
				'range' => array(
289
					'default'           => 'day',
290
					'type'              => 'string',
291
					'required'          => false,
292
					'validate_callback' => __CLASS__ . '::validate_string',
293
				),
294
			)
295
		) );
296
297
		// Check if the API key for a specific service is valid or not
298
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
299
			'methods' => WP_REST_Server::READABLE,
300
			'callback' => array( $module_data_endpoint, 'key_check' ),
301
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
302
			'sanitize_callback' => 'sanitize_text_field',
303
		) );
304
305
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
306
			'methods' => WP_REST_Server::EDITABLE,
307
			'callback' => array( $module_data_endpoint, 'key_check' ),
308
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
309
			'sanitize_callback' => 'sanitize_text_field',
310
			'args' => array(
311
				'api_key' => array(
312
					'default'           => '',
313
					'type'              => 'string',
314
					'validate_callback' => __CLASS__ . '::validate_alphanum',
315
				),
316
			)
317
		) );
318
319
		// Update any Jetpack module option or setting
320
		register_rest_route( 'jetpack/v4', '/settings', array(
321
			'methods' => WP_REST_Server::EDITABLE,
322
			'callback' => array( $core_api_endpoint, 'process' ),
323
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
324
			'args' => self::get_updateable_parameters( 'any' )
325
		) );
326
327
		// Update a module
328
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
329
			'methods' => WP_REST_Server::EDITABLE,
330
			'callback' => array( $core_api_endpoint, 'process' ),
331
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
332
			'args' => self::get_updateable_parameters()
333
		) );
334
335
		// Return all module settings
336
		register_rest_route( 'jetpack/v4', '/settings/', array(
337
			'methods' => WP_REST_Server::READABLE,
338
			'callback' => array( $core_api_endpoint, 'process' ),
339
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
340
		) );
341
342
		// Reset all Jetpack options
343
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
344
			'methods' => WP_REST_Server::EDITABLE,
345
			'callback' => __CLASS__ . '::reset_jetpack_options',
346
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
347
		) );
348
349
		// Updates: get number of plugin updates available
350
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
351
			'methods' => WP_REST_Server::READABLE,
352
			'callback' => __CLASS__ . '::get_plugin_update_count',
353
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
354
		) );
355
356
		// Dismiss Jetpack Notices
357
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
358
			'methods' => WP_REST_Server::EDITABLE,
359
			'callback' => __CLASS__ . '::dismiss_notice',
360
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
361
		) );
362
363
		// Plugins: get list of all plugins.
364
		register_rest_route( 'jetpack/v4', '/plugins', array(
365
			'methods' => WP_REST_Server::READABLE,
366
			'callback' => __CLASS__ . '::get_plugins',
367
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
368
		) );
369
370
		// Plugins: check if the plugin is active.
371
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
372
			'methods' => WP_REST_Server::READABLE,
373
			'callback' => __CLASS__ . '::get_plugin',
374
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
375
		) );
376
377
		// Widgets: get information about a widget that supports it.
378
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
379
			'methods' => WP_REST_Server::READABLE,
380
			'callback' => array( $widget_endpoint, 'process' ),
381
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
382
		) );
383
384
		// Site Verify: check if the site is verified, and a get verification token if not
385
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
386
			'methods' => WP_REST_Server::READABLE,
387
			'callback' => __CLASS__ . '::is_site_verified_and_token',
388
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
389
		) );
390
391
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
392
			'methods' => WP_REST_Server::READABLE,
393
			'callback' => __CLASS__ . '::is_site_verified_and_token',
394
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
395
		) );
396
397
		// Site Verify: tell a service to verify the site
398
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
399
			'methods' => WP_REST_Server::EDITABLE,
400
			'callback' => __CLASS__ . '::verify_site',
401
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
402
			'args' => array(
403
				'keyring_id' => array(
404
					'required'          => true,
405
					'type'              => 'integer',
406
					'validate_callback' => __CLASS__  . '::validate_posint',
407
				),
408
			)
409
		) );
410
411
		// Get and set API keys.
412
		// Note: permission_callback intentionally omitted from the GET method.
413
		// Map block requires open access to API keys on the front end.
414
		register_rest_route(
415
			'jetpack/v4',
416
			'/service-api-keys/(?P<service>[a-z\-_]+)',
417
			array(
418
				array(
419
					'methods'             => WP_REST_Server::READABLE,
420
					'callback'            => __CLASS__ . '::get_service_api_key',
421
				),
422
				array(
423
					'methods'             => WP_REST_Server::EDITABLE,
424
					'callback'            => __CLASS__ . '::update_service_api_key',
425
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
426
					'args'                => array(
427
						'service_api_key' => array(
428
							'required' => true,
429
							'type'     => 'text',
430
						),
431
					),
432
				),
433
				array(
434
					'methods'             => WP_REST_Server::DELETABLE,
435
					'callback'            => __CLASS__ . '::delete_service_api_key',
436
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
437
				),
438
			)
439
		);
440
441
		register_rest_route(
442
			'jetpack/v4',
443
			'/mobile/send-login-email',
444
			array(
445
				'methods'             => WP_REST_Server::EDITABLE,
446
				'callback'            => __CLASS__ . '::send_mobile_magic_link',
447
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
448
			)
449
		);
450
	}
451
452
	public static function get_plans( $request ) {
453
		$request = Client::wpcom_json_api_request_as_user(
454
			'/plans?_locale=' . get_user_locale(),
455
			'2',
456
			array(
457
				'method'  => 'GET',
458
				'headers' => array(
459
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
460
				),
461
			)
462
		);
463
464
		$body = wp_remote_retrieve_body( $request );
465
		if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
466
			$data = $body;
467
		} else {
468
			// something went wrong so we'll just return the response without caching
469
			return $body;
470
		}
471
472
		return $data;
473
	}
474
475
	/**
476
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
477
	 *
478
	 * @param $request WP_REST_Request
479
	 *
480
	 * @return array An array of jitms
481
	 */
482
	public static function get_jitm_message( $request ) {
483
		$jitm = new JITM();
484
485
		if ( ! $jitm->register() ) {
486
			return array();
487
		}
488
489
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) );
490
	}
491
492
	/**
493
	 * Dismisses a jitm
494
	 * @param $request WP_REST_Request The request
495
	 *
496
	 * @return bool Always True
497
	 */
498
	public static function delete_jitm_message( $request ) {
499
		$jitm = new JITM();
500
501
		if ( ! $jitm->register() ) {
502
			return true;
503
		}
504
505
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
506
	}
507
508
	/**
509
	 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
510
	 *  keyring to use to get the token if it is not
511
	 *
512
	 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
513
	 *
514
	 * @since 6.6.0
515
	 *
516
	 * @param WP_REST_Request $request The request sent to the WP REST API.
517
	 *
518
	 * @return array|wp-error
519
	 */
520
	public static function is_site_verified_and_token( $request ) {
521
		/**
522
		 * Return an error if the site uses a Maintenance / Coming Soon plugin
523
		 * and if the plugin is configured to make the site private.
524
		 *
525
		 * We currently handle the following plugins:
526
		 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
527
		 * - https://wordpress.org/plugins/mojo-under-construction
528
		 * - https://wordpress.org/plugins/under-construction-page
529
		 * - https://wordpress.org/plugins/ultimate-under-construction
530
		 * - https://wordpress.org/plugins/coming-soon
531
		 *
532
		 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
533
		 * If the filter returns true, we will consider the site as under construction.
534
		 */
535
		$mm_coming_soon                       = get_option( 'mm_coming_soon', null );
536
		$under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
537
		$ucp_options                          = get_option( 'ucp_options', array() );
538
		$uuc_settings                         = get_option( 'uuc_settings', array() );
539
		$csp4                                 = get_option( 'seed_csp4_settings_content', array() );
540
		if (
541
			( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
542
			|| Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
543
			|| ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
544
			|| ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
545
			|| ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) &&  isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
546
			/**
547
			 * Allow plugins to mark a site as "under construction".
548
			 *
549
			 * @since 6.7.0
550
			 *
551
			 * @param false bool Is the site under construction? Default to false.
552
			 */
553
			|| true === apply_filters( 'jetpack_is_under_construction_plugin', false )
554
		) {
555
			return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
556
		}
557
558
		Jetpack::load_xml_rpc_client();
559
 		$xml = new Jetpack_IXR_Client( array(
560
 			'user_id' => get_current_user_id(),
561
		) );
562
563
		$args = array(
564
			'user_id' => get_current_user_id(),
565
			'service' => $request[ 'service' ],
566
		);
567
568
		if ( isset( $request[ 'keyring_id' ] ) ) {
569
			$args[ 'keyring_id' ] = $request[ 'keyring_id' ];
570
		}
571
572
		$xml->query( 'jetpack.isSiteVerified', $args );
573
574
		if ( $xml->isError() ) {
575
			return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
576
		} else {
577
			return $xml->getResponse();
578
		}
579
	}
580
581
582
583
	public static function verify_site( $request ) {
584
		Jetpack::load_xml_rpc_client();
585
		$xml = new Jetpack_IXR_Client( array(
586
			'user_id' => get_current_user_id(),
587
		) );
588
589
		$params = $request->get_json_params();
590
591
		$xml->query( 'jetpack.verifySite', array(
592
				'user_id' => get_current_user_id(),
593
				'service' => $request[ 'service' ],
594
				'keyring_id' => $params[ 'keyring_id' ],
595
			)
596
		);
597
598
		if ( $xml->isError() ) {
599
			return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
600
		} else {
601
			$response = $xml->getResponse();
602
603
			if ( ! empty( $response['errors'] ) ) {
604
				$error = new WP_Error;
605
				$error->errors = $response['errors'];
606
				return $error;
607
			}
608
609
			return $response;
610
		}
611
	}
612
613
	/**
614
	 * Handles verification that a site is registered
615
	 *
616
	 * @since 5.4.0
617
	 *
618
	 * @param WP_REST_Request $request The request sent to the WP REST API.
619
	 *
620
	 * @return array|wp-error
621
	 */
622
	 public static function remote_authorize( $request ) {
623
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
624
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
625
		$result = $xmlrpc_server->remote_authorize( $request );
626
627
		if ( is_a( $result, 'IXR_Error' ) ) {
628
			$result = new WP_Error( $result->code, $result->message );
629
		}
630
631
		return $result;
632
	 }
633
634
	/**
635
	 * Handles dismissing of Jetpack Notices
636
	 *
637
	 * @since 4.3.0
638
	 *
639
	 * @param WP_REST_Request $request The request sent to the WP REST API.
640
	 *
641
	 * @return array|wp-error
642
	 */
643
	public static function dismiss_notice( $request ) {
644
		$notice = $request['notice'];
645
646
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
647
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
648
		}
649
650
		if ( isset( $notice ) && ! empty( $notice ) ) {
651
			switch( $notice ) {
652
				case 'feedback_dash_request':
653
				case 'welcome':
654
					$notices = get_option( 'jetpack_dismissed_notices', array() );
655
					$notices[ $notice ] = true;
656
					update_option( 'jetpack_dismissed_notices', $notices );
657
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
658
659
				default:
660
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
661
			}
662
		}
663
664
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
665
	}
666
667
	/**
668
	 * Verify that the user can disconnect the site.
669
	 *
670
	 * @since 4.3.0
671
	 *
672
	 * @return bool|WP_Error True if user is able to disconnect the site.
673
	 */
674
	public static function disconnect_site_permission_callback() {
675
		if ( current_user_can( 'jetpack_disconnect' ) ) {
676
			return true;
677
		}
678
679
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
680
681
	}
682
683
	/**
684
	 * Verify that the user can get a connect/link URL
685
	 *
686
	 * @since 4.3.0
687
	 *
688
	 * @return bool|WP_Error True if user is able to disconnect the site.
689
	 */
690 View Code Duplication
	public static function connect_url_permission_callback() {
691
		if ( current_user_can( 'jetpack_connect_user' ) ) {
692
			return true;
693
		}
694
695
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
696
697
	}
698
699
	/**
700
	 * Verify that a user can get the data about the current user.
701
	 * Only those who can connect.
702
	 *
703
	 * @since 4.3.0
704
	 *
705
	 * @uses Jetpack::is_user_connected();
706
	 *
707
	 * @return bool|WP_Error True if user is able to unlink.
708
	 */
709 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
710
		if ( current_user_can( 'jetpack_connect_user' ) ) {
711
			return true;
712
		}
713
714
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
715
	}
716
717
	/**
718
	 * Check that user has permission to change the master user.
719
	 *
720
	 * @since 6.2.0
721
	 *
722
	 * @return bool|WP_Error True if user is able to change master user.
723
	 */
724 View Code Duplication
	public static function set_connection_owner_permission_callback() {
725
		if ( get_current_user_id() === Jetpack_Options::get_option( 'master_user' ) ) {
726
			return true;
727
		}
728
729
		return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
730
	}
731
732
	/**
733
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
734
	 *
735
	 * @since 4.3.0
736
	 *
737
	 * @uses Jetpack::is_user_connected();
738
	 *
739
	 * @return bool|WP_Error True if user is able to unlink.
740
	 */
741 View Code Duplication
	public static function unlink_user_permission_callback() {
742
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
743
			return true;
744
		}
745
746
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
747
	}
748
749
	/**
750
	 * Verify that user can manage Jetpack modules.
751
	 *
752
	 * @since 4.3.0
753
	 *
754
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
755
	 */
756
	public static function manage_modules_permission_check() {
757
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
758
			return true;
759
		}
760
761
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
762
	}
763
764
	/**
765
	 * Verify that user can update Jetpack modules.
766
	 *
767
	 * @since 4.3.0
768
	 *
769
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
770
	 */
771 View Code Duplication
	public static function configure_modules_permission_check() {
772
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
773
			return true;
774
		}
775
776
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
777
	}
778
779
	/**
780
	 * Verify that user can view Jetpack admin page.
781
	 *
782
	 * @since 4.3.0
783
	 *
784
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
785
	 */
786 View Code Duplication
	public static function view_admin_page_permission_check() {
787
		if ( current_user_can( 'jetpack_admin_page' ) ) {
788
			return true;
789
		}
790
791
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
792
	}
793
794
	/**
795
	 * Verify that user can mitigate an identity crisis.
796
	 *
797
	 * @since 4.4.0
798
	 *
799
	 * @return bool Whether user has capability 'jetpack_disconnect'.
800
	 */
801
	public static function identity_crisis_mitigation_permission_check() {
802
		if ( current_user_can( 'jetpack_disconnect' ) ) {
803
			return true;
804
		}
805
806
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
807
	}
808
809
	/**
810
	 * Verify that user can update Jetpack general settings.
811
	 *
812
	 * @since 4.3.0
813
	 *
814
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
815
	 */
816 View Code Duplication
	public static function update_settings_permission_check() {
817
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
818
			return true;
819
		}
820
821
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
822
	}
823
824
	/**
825
	 * Verify that user can view Jetpack admin page and can activate plugins.
826
	 *
827
	 * @since 4.3.0
828
	 *
829
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
830
	 */
831 View Code Duplication
	public static function activate_plugins_permission_check() {
832
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
833
			return true;
834
		}
835
836
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
837
	}
838
839
	/**
840
	 * Verify that user can edit other's posts (Editors and Administrators).
841
	 *
842
	 * @return bool Whether user has the capability 'edit_others_posts'.
843
	 */
844
	public static function edit_others_posts_check() {
845
		if ( current_user_can( 'edit_others_posts' ) ) {
846
			return true;
847
		}
848
849
		return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
850
	}
851
852
	/**
853
	 * Contextual HTTP error code for authorization failure.
854
	 *
855
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
856
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
857
	 *
858
	 * @since 4.3.0
859
	 *
860
	 * @return int
861
	 */
862
	public static function rest_authorization_required_code() {
863
		return is_user_logged_in() ? 403 : 401;
864
	}
865
866
	/**
867
	 * Get connection status for this Jetpack site.
868
	 *
869
	 * @since 4.3.0
870
	 *
871
	 * @return bool True if site is connected
872
	 */
873
	public static function jetpack_connection_status() {
874
		return rest_ensure_response( array(
875
				'isActive'  => Jetpack::is_active(),
876
				'isStaging' => Jetpack::is_staging_site(),
877
				'devMode'   => array(
878
					'isActive' => Jetpack::is_development_mode(),
879
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
880
					'url'      => site_url() && false === strpos( site_url(), '.' ),
881
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
882
				),
883
			)
884
		);
885
	}
886
887
	/**
888
	 * Test connection status for this Jetpack site.
889
	 *
890
	 * @since 6.8.0
891
	 *
892
	 * @return array|WP_Error WP_Error returned if connection test does not succeed.
893
	 */
894
	public static function jetpack_connection_test() {
895
		jetpack_require_lib( 'debugger' );
896
		$cxntests = new Jetpack_Cxn_Tests();
897
898
		if ( $cxntests->pass() ) {
899
			return rest_ensure_response(
900
				array(
901
					'code'    => 'success',
902
					'message' => __( 'All connection tests passed.', 'jetpack' ),
903
				)
904
			);
905
		} else {
906
			return $cxntests->output_fails_as_wp_error();
907
		}
908
	}
909
910
	/**
911
	 * Test connection permission check method.
912
	 *
913
	 * @since 7.1.0
914
	 *
915
	 * @return bool
916
	 */
917
	public static function view_jetpack_connection_test_check() {
918
		if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'] ) ) {
919
			return false;
920
		}
921
		$signature = base64_decode( $_GET['signature'] );
922
923
		$signature_data = wp_json_encode(
924
			array(
925
				'rest_route' => $_GET['rest_route'],
926
				'timestamp' => intval( $_GET['timestamp'] ),
927
				'url' => wp_unslash( $_GET['url'] ),
928
			)
929
		);
930
931
		if (
932
			! function_exists( 'openssl_verify' )
933
			|| ! openssl_verify(
934
				$signature_data,
935
				$signature,
936
				JETPACK__DEBUGGER_PUBLIC_KEY
937
			)
938
		) {
939
			return false;
940
		}
941
942
		// signature timestamp must be within 5min of current time
943
		if ( abs( time() - intval( $_GET['timestamp'] ) ) > 300 ) {
944
			return false;
945
		}
946
947
		return true;
948
	}
949
950
	/**
951
	 * Test connection status for this Jetpack site, encrypt the results for decryption by a third-party.
952
	 *
953
	 * @since 7.1.0
954
	 *
955
	 * @return array|mixed|object|WP_Error
956
	 */
957
	public static function jetpack_connection_test_for_external() {
958
		// 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.
959
		add_filter( 'jetpack_debugger_run_self_test', '__return_false' );
960
		jetpack_require_lib( 'debugger' );
961
		$cxntests = new Jetpack_Cxn_Tests();
962
963
		if ( $cxntests->pass() ) {
964
			$result = array(
965
				'code'    => 'success',
966
				'message' => __( 'All connection tests passed.', 'jetpack' ),
967
			);
968
		} else {
969
			$error  = $cxntests->output_fails_as_wp_error(); // Using this so the output is similar both ways.
970
			$errors = array();
971
972
			// Borrowed from WP_REST_Server::error_to_response().
973
			foreach ( (array) $error->errors as $code => $messages ) {
974
				foreach ( (array) $messages as $message ) {
975
					$errors[] = array(
976
						'code'    => $code,
977
						'message' => $message,
978
						'data'    => $error->get_error_data( $code ),
979
					);
980
				}
981
			}
982
983
			$result = $errors[0];
984
			if ( count( $errors ) > 1 ) {
985
				// Remove the primary error.
986
				array_shift( $errors );
987
				$result['additional_errors'] = $errors;
988
			}
989
		}
990
991
		$result = wp_json_encode( $result );
992
993
		$encrypted = $cxntests->encrypt_string_for_wpcom( $result );
994
995
		if ( ! $encrypted || ! is_array( $encrypted ) ) {
996
			return rest_ensure_response(
997
				array(
998
					'code'    => 'action_required',
999
					'message' => 'Please request results from the in-plugin debugger',
1000
				)
1001
			);
1002
		}
1003
1004
		return rest_ensure_response(
1005
			array(
1006
				'code'  => 'response',
1007
				'debug' => array(
1008
					'data' => $encrypted['data'],
1009
					'key'  => $encrypted['key'],
1010
				),
1011
			)
1012
		);
1013
	}
1014
1015
	public static function rewind_data() {
1016
		$site_id = Jetpack_Options::get_option( 'id' );
1017
1018
		if ( ! $site_id ) {
1019
			return new WP_Error( 'site_id_missing' );
1020
		}
1021
1022
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' );
1023
1024
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1025
			return new WP_Error( 'rewind_data_fetch_failed' );
1026
		}
1027
1028
		$body = wp_remote_retrieve_body( $response );
1029
1030
		return json_decode( $body );
1031
	}
1032
1033
	/**
1034
	 * Get rewind data
1035
	 *
1036
	 * @since 5.7.0
1037
	 *
1038
	 * @return array Array of rewind properties.
1039
	 */
1040
	public static function get_rewind_data() {
1041
		$rewind_data = self::rewind_data();
1042
1043 View Code Duplication
		if ( ! is_wp_error( $rewind_data ) ) {
1044
			return rest_ensure_response( array(
1045
					'code' => 'success',
1046
					'message' => esc_html__( 'Backup & Scan data correctly received.', 'jetpack' ),
1047
					'data' => wp_json_encode( $rewind_data ),
1048
				)
1049
			);
1050
		}
1051
1052 View Code Duplication
		if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) {
1053
			return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1054
		}
1055
1056 View Code Duplication
		if ( $rewind_data->get_error_code() === 'site_id_missing' ) {
1057
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1058
		}
1059
1060
		return new WP_Error(
1061
			'error_get_rewind_data',
1062
			esc_html__( 'Could not retrieve Backup & Scan data.', 'jetpack' ),
1063
			array( 'status' => 500 )
1064
		);
1065
	}
1066
1067
	/**
1068
	 * Disconnects Jetpack from the WordPress.com Servers
1069
	 *
1070
	 * @uses Jetpack::disconnect();
1071
	 * @since 4.3.0
1072
	 *
1073
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1074
	 *
1075
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
1076
	 */
1077 View Code Duplication
	public static function disconnect_site( $request ) {
1078
1079
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
1080
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1081
		}
1082
1083
		if ( Jetpack::is_active() ) {
1084
			Jetpack::disconnect();
1085
			return rest_ensure_response( array( 'code' => 'success' ) );
1086
		}
1087
1088
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1089
	}
1090
1091
	/**
1092
	 * Gets a new connect raw URL with fresh nonce.
1093
	 *
1094
	 * @uses Jetpack::disconnect();
1095
	 * @since 4.3.0
1096
	 *
1097
	 * @param WP_REST_Request $request The request sent to the WP REST API.
0 ignored issues
show
There is no parameter named $request. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1098
	 *
1099
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
1100
	 */
1101
	public static function build_connect_url() {
1102
		$url = Jetpack::init()->build_connect_url( true, false, false );
1103
		if ( $url ) {
1104
			return rest_ensure_response( $url );
1105
		}
1106
1107
		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 ) );
1108
	}
1109
1110
	/**
1111
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
1112
	 * Information about the master/primary user.
1113
	 * Information about the current user.
1114
	 *
1115
	 * @since 4.3.0
1116
	 *
1117
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1118
	 *
1119
	 * @return object
1120
	 */
1121
	public static function get_user_connection_data() {
1122
		require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' );
1123
1124
		$response = array(
1125
//			'othersLinked' => Jetpack::get_other_linked_admins(),
1126
			'currentUser'  => jetpack_current_user_data(),
1127
		);
1128
		return rest_ensure_response( $response );
1129
	}
1130
1131
	/**
1132
	 * Change the master user.
1133
	 *
1134
	 * @since 6.2.0
1135
	 *
1136
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1137
	 *
1138
	 * @return bool|WP_Error True if owner successfully changed.
1139
	 */
1140
	public static function set_connection_owner( $request ) {
1141
		if ( ! isset( $request['owner'] ) ) {
1142
			return new WP_Error(
1143
				'invalid_param',
1144
				esc_html__( 'Invalid Parameter', 'jetpack' ),
1145
				array( 'status' => 400 )
1146
			);
1147
		}
1148
1149
		$new_owner_id = $request['owner'];
1150
		if ( ! user_can( $new_owner_id, 'administrator' ) ) {
1151
			return new WP_Error(
1152
				'new_owner_not_admin',
1153
				esc_html__( 'New owner is not admin', 'jetpack' ),
1154
				array( 'status' => 400 )
1155
			);
1156
		}
1157
1158
		if ( $new_owner_id === get_current_user_id() ) {
1159
			return new WP_Error(
1160
				'new_owner_is_current_user',
1161
				esc_html__( 'New owner is same as current user', 'jetpack' ),
1162
				array( 'status' => 400 )
1163
			);
1164
		}
1165
1166
		if ( ! Jetpack::is_user_connected( $new_owner_id ) ) {
1167
			return new WP_Error(
1168
				'new_owner_not_connected',
1169
				esc_html__( 'New owner is not connected', 'jetpack' ),
1170
				array( 'status' => 400 )
1171
			);
1172
		}
1173
1174
		// Update the master user in Jetpack
1175
		$updated = Jetpack_Options::update_option( 'master_user', $new_owner_id );
1176
1177
		// Notify WPCOM about the master user change
1178
		Jetpack::load_xml_rpc_client();
1179
		$xml = new Jetpack_IXR_Client( array(
1180
			'user_id' => get_current_user_id(),
1181
		) );
1182
		$xml->query( 'jetpack.switchBlogOwner', array(
1183
			'new_blog_owner' => $new_owner_id,
1184
		) );
1185
1186
		if ( $updated && ! $xml->isError() ) {
1187
			return rest_ensure_response(
1188
				array(
1189
					'code' => 'success',
1190
				)
1191
			);
1192
		}
1193
		return new WP_Error(
1194
			'error_setting_new_owner',
1195
			esc_html__( 'Could not confirm new owner.', 'jetpack' ),
1196
			array( 'status' => 500 )
1197
		);
1198
	}
1199
1200
	/**
1201
	 * Unlinks current user from the WordPress.com Servers.
1202
	 *
1203
	 * @since 4.3.0
1204
	 * @uses  Jetpack::unlink_user
1205
	 *
1206
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1207
	 *
1208
	 * @return bool|WP_Error True if user successfully unlinked.
1209
	 */
1210 View Code Duplication
	public static function unlink_user( $request ) {
1211
1212
		if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) {
1213
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1214
		}
1215
1216
		if ( Jetpack::unlink_user() ) {
1217
			return rest_ensure_response(
1218
				array(
1219
					'code' => 'success'
1220
				)
1221
			);
1222
		}
1223
1224
		return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1225
	}
1226
1227
	/**
1228
	 * Gets current user's tracking settings.
1229
	 *
1230
	 * @since 6.0.0
1231
	 *
1232
	 * @param  WP_REST_Request $request The request sent to the WP REST API.
1233
	 *
1234
	 * @return WP_REST_Response|WP_Error Response, else error.
1235
	 */
1236 View Code Duplication
	public static function get_user_tracking_settings( $request ) {
1237
		if ( ! Jetpack::is_user_connected() ) {
1238
			$response = array(
1239
				'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1240
			);
1241
		} else {
1242
			$response = Client::wpcom_json_api_request_as_user(
1243
				'/jetpack-user-tracking',
1244
				'v2',
1245
				array(
1246
					'method'  => 'GET',
1247
					'headers' => array(
1248
						'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1249
					),
1250
				)
1251
			);
1252
			if ( ! is_wp_error( $response ) ) {
1253
				$response = json_decode( wp_remote_retrieve_body( $response ), true );
1254
			}
1255
		}
1256
1257
		return rest_ensure_response( $response );
1258
	}
1259
1260
	/**
1261
	 * Updates current user's tracking settings.
1262
	 *
1263
	 * @since 6.0.0
1264
	 *
1265
	 * @param  WP_REST_Request $request The request sent to the WP REST API.
1266
	 *
1267
	 * @return WP_REST_Response|WP_Error Response, else error.
1268
	 */
1269 View Code Duplication
	public static function update_user_tracking_settings( $request ) {
1270
		if ( ! Jetpack::is_user_connected() ) {
1271
			$response = array(
1272
				'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1273
			);
1274
		} else {
1275
			$response = Client::wpcom_json_api_request_as_user(
1276
				'/jetpack-user-tracking',
1277
				'v2',
1278
				array(
1279
					'method'  => 'PUT',
1280
					'headers' => array(
1281
						'Content-Type'    => 'application/json',
1282
						'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1283
					),
1284
				),
1285
				wp_json_encode( $request->get_params() )
1286
			);
1287
			if ( ! is_wp_error( $response ) ) {
1288
				$response = json_decode( wp_remote_retrieve_body( $response ), true );
1289
			}
1290
		}
1291
1292
		return rest_ensure_response( $response );
1293
	}
1294
1295
	/**
1296
	 * Fetch site data from .com including the site's current plan.
1297
	 *
1298
	 * @since 5.5.0
1299
	 *
1300
	 * @return array Array of site properties.
1301
	 */
1302
	public static function site_data() {
1303
		$site_id = Jetpack_Options::get_option( 'id' );
1304
1305
		if ( ! $site_id ) {
1306
			new WP_Error( 'site_id_missing' );
1307
		}
1308
1309
		$args = array( 'headers' => array() );
1310
1311
		// Allow use a store sandbox. Internal ref: PCYsg-IA-p2.
1312
		if ( isset( $_COOKIE ) && isset( $_COOKIE['store_sandbox'] ) ) {
1313
			$secret                    = $_COOKIE['store_sandbox'];
1314
			$args['headers']['Cookie'] = "store_sandbox=$secret;";
1315
		}
1316
1317
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1', $args );
1318
1319
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1320
			return new WP_Error( 'site_data_fetch_failed' );
1321
		}
1322
1323
		Jetpack_Plan::update_from_sites_response( $response );
1324
1325
		$body = wp_remote_retrieve_body( $response );
1326
1327
		return json_decode( $body );
1328
	}
1329
	/**
1330
	 * Get site data, including for example, the site's current plan.
1331
	 *
1332
	 * @since 4.3.0
1333
	 *
1334
	 * @return array Array of site properties.
1335
	 */
1336
	public static function get_site_data() {
1337
		$site_data = self::site_data();
1338
1339 View Code Duplication
		if ( ! is_wp_error( $site_data ) ) {
1340
			return rest_ensure_response( array(
1341
					'code' => 'success',
1342
					'message' => esc_html__( 'Site data correctly received.', 'jetpack' ),
1343
					'data' => json_encode( $site_data ),
1344
				)
1345
			);
1346
		}
1347 View Code Duplication
		if ( $site_data->get_error_code() === 'site_data_fetch_failed' ) {
1348
			return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1349
		}
1350
1351 View Code Duplication
		if ( $site_data->get_error_code() === 'site_id_missing' ) {
1352
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1353
		}
1354
	}
1355
1356
	/**
1357
	 * Fetch AL data for this site and return it.
1358
	 *
1359
	 * @since 7.4
1360
	 *
1361
	 * @return array|WP_Error
1362
	 */
1363
	public static function get_site_activity() {
1364
		$site_id = Jetpack_Options::get_option( 'id' );
1365
1366
		if ( ! $site_id ) {
1367
			return new WP_Error(
1368
				'site_id_missing',
1369
				esc_html__( 'Site ID is missing.', 'jetpack' ),
1370
				array( 'status' => 400 )
1371
			);
1372
		}
1373
1374
		$response = Client::wpcom_json_api_request_as_user( "/sites/$site_id/activity", '2', array(
1375
			'method'  => 'GET',
1376
			'headers' => array(
1377
				'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1378
			),
1379
		), null, 'wpcom' );
1380
		$response_code = wp_remote_retrieve_response_code( $response );
1381
1382 View Code Duplication
		if ( 200 !== $response_code ) {
1383
			return new WP_Error(
1384
				'activity_fetch_failed',
1385
				esc_html__( 'Could not retrieve site activity.', 'jetpack' ),
1386
				array( 'status' => $response_code )
1387
			);
1388
		}
1389
1390
		$data = json_decode( wp_remote_retrieve_body( $response ) );
1391
1392
		if ( ! isset( $data->current->orderedItems ) ) {
1393
			return new WP_Error(
1394
				'activity_not_found',
1395
				esc_html__( 'No activity found', 'jetpack' ),
1396
				array( 'status' => 204 ) // no content
1397
			);
1398
		}
1399
1400
		return rest_ensure_response( array(
1401
				'code' => 'success',
1402
				'data' => $data->current->orderedItems,
1403
			)
1404
		);
1405
	}
1406
1407
	/**
1408
	 * Handles identity crisis mitigation, confirming safe mode for this site.
1409
	 *
1410
	 * @since 4.4.0
1411
	 *
1412
	 * @return bool | WP_Error True if option is properly set.
1413
	 */
1414
	public static function confirm_safe_mode() {
1415
		$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
1416
		if ( $updated ) {
1417
			return rest_ensure_response(
1418
				array(
1419
					'code' => 'success'
1420
				)
1421
			);
1422
		}
1423
		return new WP_Error(
1424
			'error_setting_jetpack_safe_mode',
1425
			esc_html__( 'Could not confirm safe mode.', 'jetpack' ),
1426
			array( 'status' => 500 )
1427
		);
1428
	}
1429
1430
	/**
1431
	 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
1432
	 *
1433
	 * @since 4.4.0
1434
	 *
1435
	 * @return bool | WP_Error True if option is properly set.
1436
	 */
1437
	public static function migrate_stats_and_subscribers() {
1438
		if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
1439
			return new WP_Error(
1440
				'error_deleting_sync_error_idc',
1441
				esc_html__( 'Could not delete sync error option.', 'jetpack' ),
1442
				array( 'status' => 500 )
1443
			);
1444
		}
1445
1446
		if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
1447
			return rest_ensure_response(
1448
				array(
1449
					'code' => 'success'
1450
				)
1451
			);
1452
		}
1453
		return new WP_Error(
1454
			'error_setting_jetpack_migrate',
1455
			esc_html__( 'Could not confirm migration.', 'jetpack' ),
1456
			array( 'status' => 500 )
1457
		);
1458
	}
1459
1460
	/**
1461
	 * This IDC resolution will disconnect the site and re-connect to a completely new
1462
	 * and separate shadow site than the original.
1463
	 *
1464
	 * It will first will disconnect the site without phoning home as to not disturb the production site.
1465
	 * It then builds a fresh connection URL and sends it back along with the response.
1466
	 *
1467
	 * @since 4.4.0
1468
	 * @return bool|WP_Error
1469
	 */
1470
	public static function start_fresh_connection() {
1471
		// First clear the options / disconnect.
1472
		Jetpack::disconnect();
1473
		return self::build_connect_url();
1474
	}
1475
1476
	/**
1477
	 * Reset Jetpack options
1478
	 *
1479
	 * @since 4.3.0
1480
	 *
1481
	 * @param WP_REST_Request $request {
1482
	 *     Array of parameters received by request.
1483
	 *
1484
	 *     @type string $options Available options to reset are options|modules
1485
	 * }
1486
	 *
1487
	 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
1488
	 */
1489
	public static function reset_jetpack_options( $request ) {
1490
1491
		if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) {
1492
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1493
		}
1494
1495
		if ( isset( $request['options'] ) ) {
1496
			$data = $request['options'];
1497
1498
			switch( $data ) {
1499
				case ( 'options' ) :
1500
					$options_to_reset = Jetpack::get_jetpack_options_for_reset();
1501
1502
					// Reset the Jetpack options
1503
					foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
1504
						Jetpack_Options::delete_option( $option_to_reset );
1505
					}
1506
1507
					foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
1508
						delete_option( $option_to_reset );
1509
					}
1510
1511
					// Reset to default modules
1512
					$default_modules = Jetpack::get_default_modules();
1513
					Jetpack::update_active_modules( $default_modules );
1514
1515
					return rest_ensure_response( array(
1516
						'code' 	  => 'success',
1517
						'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ),
1518
					) );
1519
					break;
1520
1521
				case 'modules':
1522
					$default_modules = Jetpack::get_default_modules();
1523
					Jetpack::update_active_modules( $default_modules );
1524
					return rest_ensure_response( array(
1525
						'code' 	  => 'success',
1526
						'message' => esc_html__( 'Modules reset to default.', 'jetpack' ),
1527
					) );
1528
					break;
1529
1530
				default:
1531
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1532
			}
1533
		}
1534
1535
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
1536
	}
1537
1538
	/**
1539
	 * Get the query parameters to update module options or general settings.
1540
	 *
1541
	 * @since 4.3.0
1542
	 * @since 4.4.0 Accepts a $selector parameter.
1543
	 *
1544
	 * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'.
1545
	 *
1546
	 * @return array
1547
	 */
1548
	public static function get_updateable_parameters( $selector = '' ) {
1549
		$parameters = array(
1550
			'context'     => array(
1551
				'default' => 'edit',
1552
			),
1553
		);
1554
1555
		return array_merge( $parameters, self::get_updateable_data_list( $selector ) );
1556
	}
1557
1558
	/**
1559
	 * Returns a list of module options or general settings that can be updated.
1560
	 *
1561
	 * @since 4.3.0
1562
	 * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list.
1563
	 *
1564
	 * @param string|array $selector Module slug, 'any', or an array of parameters.
1565
	 *                               If empty, it's assumed we're updating a module and we'll try to get its slug.
1566
	 *                               If 'any' the full list is returned.
1567
	 *                               If it's an array of parameters, includes the elements by matching keys.
1568
	 *
1569
	 * @return array
1570
	 */
1571
	public static function get_updateable_data_list( $selector = '' ) {
1572
1573
		$options = array(
1574
1575
			// Carousel
1576
			'carousel_background_color' => array(
1577
				'description'       => esc_html__( 'Color scheme.', 'jetpack' ),
1578
				'type'              => 'string',
1579
				'default'           => 'black',
1580
				'enum'              => array(
1581
					'black',
1582
					'white',
1583
				),
1584
				'enum_labels' => array(
1585
					'black' => esc_html__( 'Black', 'jetpack' ),
1586
					'white' => esc_html__( 'White', 'jetpack' ),
1587
				),
1588
				'validate_callback' => __CLASS__ . '::validate_list_item',
1589
				'jp_group'          => 'carousel',
1590
			),
1591
			'carousel_display_exif' => array(
1592
				'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 ) ) ),
1593
				'type'              => 'boolean',
1594
				'default'           => 0,
1595
				'validate_callback' => __CLASS__ . '::validate_boolean',
1596
				'jp_group'          => 'carousel',
1597
			),
1598
1599
			// Comments
1600
			'highlander_comment_form_prompt' => array(
1601
				'description'       => esc_html__( 'Greeting Text', 'jetpack' ),
1602
				'type'              => 'string',
1603
				'default'           => esc_html__( 'Leave a Reply', 'jetpack' ),
1604
				'sanitize_callback' => 'sanitize_text_field',
1605
				'jp_group'          => 'comments',
1606
			),
1607
			'jetpack_comment_form_color_scheme' => array(
1608
				'description'       => esc_html__( "Color scheme", 'jetpack' ),
1609
				'type'              => 'string',
1610
				'default'           => 'light',
1611
				'enum'              => array(
1612
					'light',
1613
					'dark',
1614
					'transparent',
1615
				),
1616
				'enum_labels' => array(
1617
					'light'       => esc_html__( 'Light', 'jetpack' ),
1618
					'dark'        => esc_html__( 'Dark', 'jetpack' ),
1619
					'transparent' => esc_html__( 'Transparent', 'jetpack' ),
1620
				),
1621
				'validate_callback' => __CLASS__ . '::validate_list_item',
1622
				'jp_group'          => 'comments',
1623
			),
1624
1625
			// Custom Content Types
1626
			'jetpack_portfolio' => array(
1627
				'description'       => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1628
				'type'              => 'boolean',
1629
				'default'           => 0,
1630
				'validate_callback' => __CLASS__ . '::validate_boolean',
1631
				'jp_group'          => 'custom-content-types',
1632
			),
1633
			'jetpack_portfolio_posts_per_page' => array(
1634
				'description'       => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1635
				'type'              => 'integer',
1636
				'default'           => 10,
1637
				'validate_callback' => __CLASS__ . '::validate_posint',
1638
				'jp_group'          => 'custom-content-types',
1639
			),
1640
			'jetpack_testimonial' => array(
1641
				'description'       => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1642
				'type'              => 'boolean',
1643
				'default'           => 0,
1644
				'validate_callback' => __CLASS__ . '::validate_boolean',
1645
				'jp_group'          => 'custom-content-types',
1646
			),
1647
			'jetpack_testimonial_posts_per_page' => array(
1648
				'description'       => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1649
				'type'              => 'integer',
1650
				'default'           => 10,
1651
				'validate_callback' => __CLASS__ . '::validate_posint',
1652
				'jp_group'          => 'custom-content-types',
1653
			),
1654
1655
			// Galleries
1656
			'tiled_galleries' => array(
1657
				'description'       => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1658
				'type'              => 'boolean',
1659
				'default'           => 0,
1660
				'validate_callback' => __CLASS__ . '::validate_boolean',
1661
				'jp_group'          => 'tiled-gallery',
1662
			),
1663
1664
			'gravatar_disable_hovercards' => array(
1665
				'description'       => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1666
				'type'              => 'string',
1667
				'default'           => 'enabled',
1668
				// Not visible. This is used as the checkbox value.
1669
				'enum'              => array(
1670
					'enabled',
1671
					'disabled',
1672
				),
1673
				'enum_labels' => array(
1674
					'enabled'  => esc_html__( 'Enabled', 'jetpack' ),
1675
					'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1676
				),
1677
				'validate_callback' => __CLASS__ . '::validate_list_item',
1678
				'jp_group'          => 'gravatar-hovercards',
1679
			),
1680
1681
			// Infinite Scroll
1682
			'infinite_scroll' => array(
1683
				'description'       => esc_html__( 'To infinity and beyond', 'jetpack' ),
1684
				'type'              => 'boolean',
1685
				'default'           => 1,
1686
				'validate_callback' => __CLASS__ . '::validate_boolean',
1687
				'jp_group'          => 'infinite-scroll',
1688
			),
1689
			'infinite_scroll_google_analytics' => array(
1690
				'description'       => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1691
				'type'              => 'boolean',
1692
				'default'           => 0,
1693
				'validate_callback' => __CLASS__ . '::validate_boolean',
1694
				'jp_group'          => 'infinite-scroll',
1695
			),
1696
1697
			// Likes
1698
			'wpl_default' => array(
1699
				'description'       => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1700
				'type'              => 'string',
1701
				'default'           => 'on',
1702
				'enum'              => array(
1703
					'on',
1704
					'off',
1705
				),
1706
				'enum_labels' => array(
1707
					'on'  => esc_html__( 'On for all posts', 'jetpack' ),
1708
					'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1709
				),
1710
				'validate_callback' => __CLASS__ . '::validate_list_item',
1711
				'jp_group'          => 'likes',
1712
			),
1713
			'social_notifications_like' => array(
1714
				'description'       => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1715
				'type'              => 'boolean',
1716
				'default'           => 1,
1717
				'validate_callback' => __CLASS__ . '::validate_boolean',
1718
				'jp_group'          => 'likes',
1719
			),
1720
1721
			// Markdown
1722
			'wpcom_publish_comments_with_markdown' => array(
1723
				'description'       => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1724
				'type'              => 'boolean',
1725
				'default'           => 0,
1726
				'validate_callback' => __CLASS__ . '::validate_boolean',
1727
				'jp_group'          => 'markdown',
1728
			),
1729
			'wpcom_publish_posts_with_markdown' => array(
1730
				'description'       => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1731
				'type'              => 'boolean',
1732
				'default'           => 0,
1733
				'validate_callback' => __CLASS__ . '::validate_boolean',
1734
				'jp_group'          => 'markdown',
1735
			),
1736
1737
			// Mobile Theme
1738
			'wp_mobile_excerpt' => array(
1739
				'description'       => esc_html__( 'Excerpts', 'jetpack' ),
1740
				'type'              => 'boolean',
1741
				'default'           => 0,
1742
				'validate_callback' => __CLASS__ . '::validate_boolean',
1743
				'jp_group'          => 'minileven',
1744
			),
1745
			'wp_mobile_featured_images' => array(
1746
				'description'       => esc_html__( 'Featured Images', 'jetpack' ),
1747
				'type'              => 'boolean',
1748
				'default'           => 0,
1749
				'validate_callback' => __CLASS__ . '::validate_boolean',
1750
				'jp_group'          => 'minileven',
1751
			),
1752
			'wp_mobile_app_promos' => array(
1753
				'description'       => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ),
1754
				'type'              => 'boolean',
1755
				'default'           => 0,
1756
				'validate_callback' => __CLASS__ . '::validate_boolean',
1757
				'jp_group'          => 'minileven',
1758
			),
1759
1760
			// Monitor
1761
			'monitor_receive_notifications' => array(
1762
				'description'       => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1763
				'type'              => 'boolean',
1764
				'default'           => 0,
1765
				'validate_callback' => __CLASS__ . '::validate_boolean',
1766
				'jp_group'          => 'monitor',
1767
			),
1768
1769
			// Post by Email
1770
			'post_by_email_address' => array(
1771
				'description'       => esc_html__( 'Email Address', 'jetpack' ),
1772
				'type'              => 'string',
1773
				'default'           => 'noop',
1774
				'enum'              => array(
1775
					'noop',
1776
					'create',
1777
					'regenerate',
1778
					'delete',
1779
				),
1780
				'enum_labels' => array(
1781
					'noop'       => '',
1782
					'create'     => esc_html__( 'Create Post by Email address', 'jetpack' ),
1783
					'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1784
					'delete'     => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1785
				),
1786
				'validate_callback' => __CLASS__ . '::validate_list_item',
1787
				'jp_group'          => 'post-by-email',
1788
			),
1789
1790
			// Protect
1791
			'jetpack_protect_key' => array(
1792
				'description'       => esc_html__( 'Protect API key', 'jetpack' ),
1793
				'type'              => 'string',
1794
				'default'           => '',
1795
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1796
				'jp_group'          => 'protect',
1797
			),
1798
			'jetpack_protect_global_whitelist' => array(
1799
				'description'       => esc_html__( 'Protect global whitelist', 'jetpack' ),
1800
				'type'              => 'string',
1801
				'default'           => '',
1802
				'validate_callback' => __CLASS__ . '::validate_string',
1803
				'sanitize_callback' => 'esc_textarea',
1804
				'jp_group'          => 'protect',
1805
			),
1806
1807
			// Sharing
1808
			'sharing_services' => array(
1809
				'description'       => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1810
				'type'              => 'object',
1811
				'default'           => array(
1812
					'visible' => array( 'twitter', 'facebook', 'google-plus-1' ),
1813
					'hidden'  => array(),
1814
				),
1815
				'validate_callback' => __CLASS__ . '::validate_services',
1816
				'jp_group'          => 'sharedaddy',
1817
			),
1818
			'button_style' => array(
1819
				'description'       => esc_html__( 'Button Style', 'jetpack' ),
1820
				'type'              => 'string',
1821
				'default'           => 'icon',
1822
				'enum'              => array(
1823
					'icon-text',
1824
					'icon',
1825
					'text',
1826
					'official',
1827
				),
1828
				'enum_labels' => array(
1829
					'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1830
					'icon'      => esc_html__( 'Icon only', 'jetpack' ),
1831
					'text'      => esc_html__( 'Text only', 'jetpack' ),
1832
					'official'  => esc_html__( 'Official buttons', 'jetpack' ),
1833
				),
1834
				'validate_callback' => __CLASS__ . '::validate_list_item',
1835
				'jp_group'          => 'sharedaddy',
1836
			),
1837
			'sharing_label' => array(
1838
				'description'       => esc_html__( 'Sharing Label', 'jetpack' ),
1839
				'type'              => 'string',
1840
				'default'           => '',
1841
				'validate_callback' => __CLASS__ . '::validate_string',
1842
				'sanitize_callback' => 'esc_html',
1843
				'jp_group'          => 'sharedaddy',
1844
			),
1845
			'show' => array(
1846
				'description'       => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1847
				'type'              => 'array',
1848
				'items'             => array(
1849
					'type' => 'string'
1850
				),
1851
				'default'           => array( 'post' ),
1852
				'validate_callback' => __CLASS__ . '::validate_sharing_show',
1853
				'jp_group'          => 'sharedaddy',
1854
			),
1855
			'jetpack-twitter-cards-site-tag' => array(
1856
				'description'       => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
1857
				'type'              => 'string',
1858
				'default'           => '',
1859
				'validate_callback' => __CLASS__ . '::validate_twitter_username',
1860
				'sanitize_callback' => 'esc_html',
1861
				'jp_group'          => 'sharedaddy',
1862
			),
1863
			'sharedaddy_disable_resources' => array(
1864
				'description'       => esc_html__( 'Disable CSS and JS', 'jetpack' ),
1865
				'type'              => 'boolean',
1866
				'default'           => 0,
1867
				'validate_callback' => __CLASS__ . '::validate_boolean',
1868
				'jp_group'          => 'sharedaddy',
1869
			),
1870
			'custom' => array(
1871
				'description'       => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
1872
				'type'              => 'object',
1873
				'default'           => array(
1874
					'sharing_name' => '',
1875
					'sharing_url'  => '',
1876
					'sharing_icon' => '',
1877
				),
1878
				'validate_callback' => __CLASS__ . '::validate_custom_service',
1879
				'jp_group'          => 'sharedaddy',
1880
			),
1881
			// Not an option, but an action that can be perfomed on the list of custom services passing the service ID.
1882
			'sharing_delete_service' => array(
1883
				'description'       => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
1884
				'type'              => 'string',
1885
				'default'           => '',
1886
				'validate_callback' => __CLASS__ . '::validate_custom_service_id',
1887
				'jp_group'          => 'sharedaddy',
1888
			),
1889
1890
			// SSO
1891
			'jetpack_sso_require_two_step' => array(
1892
				'description'       => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
1893
				'type'              => 'boolean',
1894
				'default'           => 0,
1895
				'validate_callback' => __CLASS__ . '::validate_boolean',
1896
				'jp_group'          => 'sso',
1897
			),
1898
			'jetpack_sso_match_by_email' => array(
1899
				'description'       => esc_html__( 'Match by Email', 'jetpack' ),
1900
				'type'              => 'boolean',
1901
				'default'           => 0,
1902
				'validate_callback' => __CLASS__ . '::validate_boolean',
1903
				'jp_group'          => 'sso',
1904
			),
1905
1906
			// Subscriptions
1907
			'stb_enabled' => array(
1908
				'description'       => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ),
1909
				'type'              => 'boolean',
1910
				'default'           => 1,
1911
				'validate_callback' => __CLASS__ . '::validate_boolean',
1912
				'jp_group'          => 'subscriptions',
1913
			),
1914
			'stc_enabled' => array(
1915
				'description'       => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
1916
				'type'              => 'boolean',
1917
				'default'           => 1,
1918
				'validate_callback' => __CLASS__ . '::validate_boolean',
1919
				'jp_group'          => 'subscriptions',
1920
			),
1921
1922
			// Related Posts
1923
			'show_headline' => array(
1924
				'description'       => esc_html__( 'Highlight related content with a heading', 'jetpack' ),
1925
				'type'              => 'boolean',
1926
				'default'           => 1,
1927
				'validate_callback' => __CLASS__ . '::validate_boolean',
1928
				'jp_group'          => 'related-posts',
1929
			),
1930
			'show_thumbnails' => array(
1931
				'description'       => esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
1932
				'type'              => 'boolean',
1933
				'default'           => 0,
1934
				'validate_callback' => __CLASS__ . '::validate_boolean',
1935
				'jp_group'          => 'related-posts',
1936
			),
1937
1938
			// Verification Tools
1939
			'google' => array(
1940
				'description'       => esc_html__( 'Google Search Console', 'jetpack' ),
1941
				'type'              => 'string',
1942
				'default'           => '',
1943
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1944
				'jp_group'          => 'verification-tools',
1945
			),
1946
			'bing' => array(
1947
				'description'       => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
1948
				'type'              => 'string',
1949
				'default'           => '',
1950
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1951
				'jp_group'          => 'verification-tools',
1952
			),
1953
			'pinterest' => array(
1954
				'description'       => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
1955
				'type'              => 'string',
1956
				'default'           => '',
1957
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1958
				'jp_group'          => 'verification-tools',
1959
			),
1960
			'yandex' => array(
1961
				'description'       => esc_html__( 'Yandex Site Verification', 'jetpack' ),
1962
				'type'              => 'string',
1963
				'default'           => '',
1964
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1965
				'jp_group'          => 'verification-tools',
1966
			),
1967
			'enable_header_ad' => array(
1968
				'description'        => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
1969
				'type'               => 'boolean',
1970
				'default'            => 1,
1971
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1972
				'jp_group'           => 'wordads',
1973
			),
1974
			'wordads_approved' => array(
1975
				'description'        => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
1976
				'type'               => 'boolean',
1977
				'default'            => 0,
1978
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1979
				'jp_group'           => 'wordads',
1980
			),
1981
			'wordads_second_belowpost' => array(
1982
				'description'        => esc_html__( 'Display second ad below post?', 'jetpack' ),
1983
				'type'               => 'boolean',
1984
				'default'            => 1,
1985
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1986
				'jp_group'           => 'wordads',
1987
			),
1988
			'wordads_display_front_page' => array(
1989
				'description'        => esc_html__( 'Display ads on the front page?', 'jetpack' ),
1990
				'type'               => 'boolean',
1991
				'default'            => 1,
1992
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1993
				'jp_group'           => 'wordads',
1994
			),
1995
			'wordads_display_post' => array(
1996
				'description'        => esc_html__( 'Display ads on posts?', 'jetpack' ),
1997
				'type'               => 'boolean',
1998
				'default'            => 1,
1999
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2000
				'jp_group'           => 'wordads',
2001
			),
2002
			'wordads_display_page' => array(
2003
				'description'        => esc_html__( 'Display ads on pages?', 'jetpack' ),
2004
				'type'               => 'boolean',
2005
				'default'            => 1,
2006
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2007
				'jp_group'           => 'wordads',
2008
			),
2009
			'wordads_display_archive' => array(
2010
				'description'        => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
2011
				'type'               => 'boolean',
2012
				'default'            => 1,
2013
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2014
				'jp_group'           => 'wordads',
2015
			),
2016
			'wordads_custom_adstxt' => array(
2017
				'description'        => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
2018
				'type'               => 'string',
2019
				'default'            => '',
2020
				'validate_callback'  => __CLASS__ . '::validate_string',
2021
				'sanitize_callback'  => 'sanitize_textarea_field',
2022
				'jp_group'           => 'wordads',
2023
			),
2024
2025
			// Google Analytics
2026
			'google_analytics_tracking_id' => array(
2027
				'description'        => esc_html__( 'Google Analytics', 'jetpack' ),
2028
				'type'               => 'string',
2029
				'default'            => '',
2030
				'validate_callback'  => __CLASS__ . '::validate_alphanum',
2031
				'jp_group'           => 'google-analytics',
2032
			),
2033
2034
			// Stats
2035
			'admin_bar' => array(
2036
				'description'       => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ),
2037
				'type'              => 'boolean',
2038
				'default'           => 1,
2039
				'validate_callback' => __CLASS__ . '::validate_boolean',
2040
				'jp_group'          => 'stats',
2041
			),
2042
			'roles' => array(
2043
				'description'       => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
2044
				'type'              => 'array',
2045
				'items'             => array(
2046
					'type' => 'string'
2047
				),
2048
				'default'           => array( 'administrator' ),
2049
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
2050
				'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
2051
				'jp_group'          => 'stats',
2052
			),
2053
			'count_roles' => array(
2054
				'description'       => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
2055
				'type'              => 'array',
2056
				'items'             => array(
2057
					'type' => 'string'
2058
				),
2059
				'default'           => array( 'administrator' ),
2060
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
2061
				'jp_group'          => 'stats',
2062
			),
2063
			'blog_id' => array(
2064
				'description'       => esc_html__( 'Blog ID.', 'jetpack' ),
2065
				'type'              => 'boolean',
2066
				'default'           => 0,
2067
				'validate_callback' => __CLASS__ . '::validate_boolean',
2068
				'jp_group'          => 'stats',
2069
			),
2070
			'do_not_track' => array(
2071
				'description'       => esc_html__( 'Do not track.', 'jetpack' ),
2072
				'type'              => 'boolean',
2073
				'default'           => 1,
2074
				'validate_callback' => __CLASS__ . '::validate_boolean',
2075
				'jp_group'          => 'stats',
2076
			),
2077
			'hide_smile' => array(
2078
				'description'       => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ),
2079
				'type'              => 'boolean',
2080
				'default'           => 1,
2081
				'validate_callback' => __CLASS__ . '::validate_boolean',
2082
				'jp_group'          => 'stats',
2083
			),
2084
			'version' => array(
2085
				'description'       => esc_html__( 'Version.', 'jetpack' ),
2086
				'type'              => 'integer',
2087
				'default'           => 9,
2088
				'validate_callback' => __CLASS__ . '::validate_posint',
2089
				'jp_group'          => 'stats',
2090
			),
2091
2092
			// Akismet - Not a module, but a plugin. The options can be passed and handled differently.
2093
			'akismet_show_user_comments_approved' => array(
2094
				'description'       => '',
2095
				'type'              => 'boolean',
2096
				'default'           => 0,
2097
				'validate_callback' => __CLASS__ . '::validate_boolean',
2098
				'jp_group'          => 'settings',
2099
			),
2100
2101
			'wordpress_api_key' => array(
2102
				'description'       => '',
2103
				'type'              => 'string',
2104
				'default'           => '',
2105
				'validate_callback' => __CLASS__ . '::validate_alphanum',
2106
				'jp_group'          => 'settings',
2107
			),
2108
2109
			// Apps card on dashboard
2110
			'dismiss_dash_app_card' => array(
2111
				'description'       => '',
2112
				'type'              => 'boolean',
2113
				'default'           => 0,
2114
				'validate_callback' => __CLASS__ . '::validate_boolean',
2115
				'jp_group'          => 'settings',
2116
			),
2117
2118
			// Empty stats card dismiss
2119
			'dismiss_empty_stats_card' => array(
2120
				'description'       => '',
2121
				'type'              => 'boolean',
2122
				'default'           => 0,
2123
				'validate_callback' => __CLASS__ . '::validate_boolean',
2124
				'jp_group'          => 'settings',
2125
			),
2126
2127
			'lang_id' => array(
2128
				'description' => esc_html__( 'Primary language for the site.', 'jetpack' ),
2129
				'type' => 'string',
2130
				'default' => 'en_US',
2131
				'jp_group' => 'settings',
2132
			),
2133
2134
			'onboarding' => array(
2135
				'description'       => '',
2136
				'type'              => 'object',
2137
				'default'           => array(
2138
					'siteTitle'          => '',
2139
					'siteDescription'    => '',
2140
					'siteType'           => 'personal',
2141
					'homepageFormat'     => 'posts',
2142
					'addContactForm'     => 0,
2143
					'businessAddress'    => array(
2144
						'name'   => '',
2145
						'street' => '',
2146
						'city'   => '',
2147
						'state'  => '',
2148
						'zip'    => '',
2149
					),
2150
					'installWooCommerce' => false,
2151
				),
2152
				'validate_callback' => __CLASS__ . '::validate_onboarding',
2153
				'jp_group'          => 'settings',
2154
			),
2155
2156
		);
2157
2158
		// Add modules to list so they can be toggled
2159
		$modules = Jetpack::get_available_modules();
2160
		if ( is_array( $modules ) && ! empty( $modules ) ) {
2161
			$module_args = array(
2162
				'description'       => '',
2163
				'type'              => 'boolean',
2164
				'default'           => 0,
2165
				'validate_callback' => __CLASS__ . '::validate_boolean',
2166
				'jp_group'          => 'modules',
2167
			);
2168
			foreach( $modules as $module ) {
2169
				$options[ $module ] = $module_args;
2170
			}
2171
		}
2172
2173
		if ( is_array( $selector ) ) {
2174
2175
			// Return only those options whose keys match $selector keys
2176
			return array_intersect_key( $options, $selector );
2177
		}
2178
2179
		if ( 'any' === $selector ) {
2180
2181
			// Toggle module or update any module option or any general setting
2182
			return $options;
2183
		}
2184
2185
		// We're updating the options for a single module.
2186
		if ( empty( $selector ) ) {
2187
			$selector = self::get_module_requested();
2188
		}
2189
		$selected = array();
2190
		foreach ( $options as $option => $attributes ) {
2191
2192
			// Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
2193
			if ( $selector === $attributes['jp_group'] ) {
2194
				$selected[ $option ] = $attributes;
2195
			}
2196
		}
2197
		return $selected;
2198
	}
2199
2200
	/**
2201
	 * Validates that the parameters are proper values that can be set during Jetpack onboarding.
2202
	 *
2203
	 * @since 5.4.0
2204
	 *
2205
	 * @param array           $onboarding_data Values to check.
2206
	 * @param WP_REST_Request $request         The request sent to the WP REST API.
2207
	 * @param string          $param           Name of the parameter passed to endpoint holding $value.
2208
	 *
2209
	 * @return bool|WP_Error
2210
	 */
2211
	public static function validate_onboarding( $onboarding_data, $request, $param ) {
2212
		if ( ! is_array( $onboarding_data ) ) {
2213
			return new WP_Error( 'invalid_param', esc_html__( 'Not valid onboarding data.', 'jetpack' ) );
2214
		}
2215
		foreach ( $onboarding_data as $value ) {
2216
			if ( is_string( $value ) ) {
2217
				$onboarding_choice = self::validate_string( $value, $request, $param );
2218
			} elseif ( is_array( $value ) ) {
2219
				$onboarding_choice = self::validate_onboarding( $value, $request, $param );
2220
			} else {
2221
				$onboarding_choice = self::validate_boolean( $value, $request, $param );
2222
			}
2223
			if ( is_wp_error( $onboarding_choice ) ) {
2224
				return $onboarding_choice;
2225
			}
2226
		}
2227
		return true;
2228
	}
2229
2230
	/**
2231
	 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
2232
	 *
2233
	 * @since 4.3.0
2234
	 *
2235
	 * @param string|bool $value Value to check.
2236
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2237
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2238
	 *
2239
	 * @return bool|WP_Error
2240
	 */
2241
	public static function validate_boolean( $value, $request, $param ) {
2242
		if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) {
2243
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) );
2244
		}
2245
		return true;
2246
	}
2247
2248
	/**
2249
	 * Validates that the parameter is a positive integer.
2250
	 *
2251
	 * @since 4.3.0
2252
	 *
2253
	 * @param int $value Value to check.
2254
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2255
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2256
	 *
2257
	 * @return bool|WP_Error
2258
	 */
2259
	public static function validate_posint( $value = 0, $request, $param ) {
2260
		if ( ! is_numeric( $value ) || $value <= 0 ) {
2261
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a positive integer.', 'jetpack' ), $param ) );
2262
		}
2263
		return true;
2264
	}
2265
2266
	/**
2267
	 * Validates that the parameter belongs to a list of admitted values.
2268
	 *
2269
	 * @since 4.3.0
2270
	 *
2271
	 * @param string $value Value to check.
2272
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2273
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2274
	 *
2275
	 * @return bool|WP_Error
2276
	 */
2277
	public static function validate_list_item( $value = '', $request, $param ) {
2278
		$attributes = $request->get_attributes();
2279
		if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
2280
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s not recognized', 'jetpack' ), $param ) );
2281
		}
2282
		$args = $attributes['args'][ $param ];
2283
		if ( ! empty( $args['enum'] ) ) {
2284
2285
			// If it's an associative array, use the keys to check that the value is among those admitted.
2286
			$enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) ? array_keys( $args['enum'] ) : $args['enum'];
2287 View Code Duplication
			if ( ! in_array( $value, $enum ) ) {
2288
				return new WP_Error( 'invalid_param_value', sprintf(
2289
					/* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
2290
					esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum )
2291
				) );
2292
			}
2293
		}
2294
		return true;
2295
	}
2296
2297
	/**
2298
	 * Validates that the parameter belongs to a list of admitted values.
2299
	 *
2300
	 * @since 4.3.0
2301
	 *
2302
	 * @param string $value Value to check.
2303
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2304
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2305
	 *
2306
	 * @return bool|WP_Error
2307
	 */
2308
	public static function validate_module_list( $value = '', $request, $param ) {
2309 View Code Duplication
		if ( ! is_array( $value ) ) {
2310
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be an array', 'jetpack' ), $param ) );
2311
		}
2312
2313
		$modules = Jetpack::get_available_modules();
2314
2315 View Code Duplication
		if ( count( array_intersect( $value, $modules ) ) != count( $value ) ) {
2316
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be a list of valid modules', 'jetpack' ), $param ) );
2317
		}
2318
2319
		return true;
2320
	}
2321
2322
	/**
2323
	 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
2324
	 *
2325
	 * @since 4.3.0
2326
	 *
2327
	 * @param string $value Value to check.
2328
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2329
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2330
	 *
2331
	 * @return bool|WP_Error
2332
	 */
2333
	public static function validate_alphanum( $value = '', $request, $param ) {
2334 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
2335
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) );
2336
		}
2337
		return true;
2338
	}
2339
2340
	/**
2341
	 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
2342
	 *
2343
	 * @since 4.6.0
2344
	 *
2345
	 * @param string $value Value to check.
2346
	 * @param WP_REST_Request $request
2347
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2348
	 *
2349
	 * @return bool|WP_Error
2350
	 */
2351
	public static function validate_verification_service( $value = '', $request, $param ) {
2352
		if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) {
2353
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
2354
		}
2355
		return true;
2356
	}
2357
2358
	/**
2359
	 * Validates that the parameter is among the roles allowed for Stats.
2360
	 *
2361
	 * @since 4.3.0
2362
	 *
2363
	 * @param string|bool $value Value to check.
2364
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2365
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2366
	 *
2367
	 * @return bool|WP_Error
2368
	 */
2369
	public static function validate_stats_roles( $value, $request, $param ) {
2370
		if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) {
2371
			return new WP_Error( 'invalid_param', sprintf(
2372
				/* 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. */
2373
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', self::$stats_roles )
2374
			) );
2375
		}
2376
		return true;
2377
	}
2378
2379
	/**
2380
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2381
	 *
2382
	 * @since 4.3.0
2383
	 *
2384
	 * @param string|bool $value Value to check.
2385
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2386
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2387
	 *
2388
	 * @return bool|WP_Error
2389
	 */
2390
	public static function validate_sharing_show( $value, $request, $param ) {
2391
		$views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
2392 View Code Duplication
		if ( ! is_array( $value ) ) {
2393
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) );
2394
		}
2395
		if ( ! array_intersect( $views, $value ) ) {
2396
			return new WP_Error( 'invalid_param', sprintf(
2397
				/* 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 */
2398
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views )
2399
			) );
2400
		}
2401
		return true;
2402
	}
2403
2404
	/**
2405
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2406
	 *
2407
	 * @since 4.3.0
2408
	 *
2409
	 * @param string|bool $value {
2410
	 *     Value to check received by request.
2411
	 *
2412
	 *     @type array $visible List of slug of services to share to that are displayed directly in the page.
2413
	 *     @type array $hidden  List of slug of services to share to that are concealed in a folding menu.
2414
	 * }
2415
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2416
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2417
	 *
2418
	 * @return bool|WP_Error
2419
	 */
2420
	public static function validate_services( $value, $request, $param ) {
2421 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
2422
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), $param ) );
2423
		}
2424
2425
		// Allow to clear everything.
2426
		if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
2427
			return true;
2428
		}
2429
2430 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2431
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2432
		}
2433
		$sharer = new Sharing_Service();
2434
		$services = array_keys( $sharer->get_all_services() );
2435
2436
		if (
2437
			( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
2438
			||
2439
			( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) )
2440
		{
2441
			return new WP_Error( 'invalid_param', sprintf(
2442
				/* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
2443
				esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services )
2444
			) );
2445
		}
2446
		return true;
2447
	}
2448
2449
	/**
2450
	 * Validates that the parameter has enough information to build a custom sharing button.
2451
	 *
2452
	 * @since 4.3.0
2453
	 *
2454
	 * @param string|bool $value Value to check.
2455
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2456
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2457
	 *
2458
	 * @return bool|WP_Error
2459
	 */
2460
	public static function validate_custom_service( $value, $request, $param ) {
2461 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
2462
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), $param ) );
2463
		}
2464
2465
		// Allow to clear everything.
2466
		if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
2467
			return true;
2468
		}
2469
2470 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2471
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2472
		}
2473
2474
		if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
2475
		|| ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
2476
		|| ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
2477
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), $param ) );
2478
		}
2479
		return true;
2480
	}
2481
2482
	/**
2483
	 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
2484
	 *
2485
	 * @since 4.3.0
2486
	 *
2487
	 * @param string $value Value to check.
2488
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2489
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2490
	 *
2491
	 * @return bool|WP_Error
2492
	 */
2493
	public static function validate_custom_service_id( $value = '', $request, $param ) {
2494 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
2495
			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 ) );
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
		$sharer = new Sharing_Service();
2502
		$services = array_keys( $sharer->get_all_services() );
2503
2504 View Code Duplication
		if ( ! empty( $value ) && ! in_array( $value, $services ) ) {
2505
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), $param ) );
2506
		}
2507
2508
		return true;
2509
	}
2510
2511
	/**
2512
	 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
2513
	 *
2514
	 * @since 4.3.0
2515
	 *
2516
	 * @param string $value Value to check.
2517
	 * @param WP_REST_Request $request
2518
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2519
	 *
2520
	 * @return bool|WP_Error
2521
	 */
2522
	public static function validate_twitter_username( $value = '', $request, $param ) {
2523 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
2524
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a Twitter username.', 'jetpack' ), $param ) );
2525
		}
2526
		return true;
2527
	}
2528
2529
	/**
2530
	 * Validates that the parameter is a string.
2531
	 *
2532
	 * @since 4.3.0
2533
	 *
2534
	 * @param string $value Value to check.
2535
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2536
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2537
	 *
2538
	 * @return bool|WP_Error
2539
	 */
2540
	public static function validate_string( $value = '', $request, $param ) {
2541
		if ( ! is_string( $value ) ) {
2542
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack' ), $param ) );
2543
		}
2544
		return true;
2545
	}
2546
2547
	/**
2548
	 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
2549
	 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
2550
	 *
2551
	 * @since 4.3.0
2552
	 *
2553
	 * @param string|bool $value Value to check.
2554
	 *
2555
	 * @return bool|array
2556
	 */
2557
	public static function sanitize_stats_allowed_roles( $value ) {
2558
		if ( empty( $value ) ) {
2559
			return array( 'administrator' );
2560
		}
2561
		return $value;
2562
	}
2563
2564
	/**
2565
	 * Get the currently accessed route and return the module slug in it.
2566
	 *
2567
	 * @since 4.3.0
2568
	 *
2569
	 * @param string $route Regular expression for the endpoint with the module slug to return.
2570
	 *
2571
	 * @return array|string
2572
	 */
2573
	public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
2574
2575
		if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
2576
			return '';
2577
		}
2578
2579
		preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
2580
2581
		if ( empty( $module['slug'] ) ) {
2582
			return '';
2583
		}
2584
2585
		return $module['slug'];
2586
	}
2587
2588
	/**
2589
	 * Adds extra information for modules.
2590
	 *
2591
	 * @since 4.3.0
2592
	 *
2593
	 * @param string|array $modules Can be a single module or a list of modules.
2594
	 * @param null|string  $slug    Slug of the module in the first parameter.
2595
	 *
2596
	 * @return array|string
2597
	 */
2598
	public static function prepare_modules_for_response( $modules = '', $slug = null ) {
2599
		global $wp_rewrite;
2600
2601
		/** This filter is documented in modules/sitemaps/sitemaps.php */
2602
		$location = apply_filters( 'jetpack_sitemap_location', '' );
2603
2604
		if ( $wp_rewrite->using_index_permalinks() ) {
2605
			$sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
2606
			$news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
2607
		} else if ( $wp_rewrite->using_permalinks() ) {
2608
			$sitemap_url = home_url( $location . '/sitemap.xml' );
2609
			$news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
2610
		} else {
2611
			$sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
2612
			$news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
2613
		}
2614
2615
		if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) {
2616
			// Is a list of modules
2617
			$modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
2618
			$modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
2619
		} elseif ( 'sitemaps' == $slug ) {
2620
			// It's a single module
2621
			$modules['extra']['sitemap_url'] = $sitemap_url;
2622
			$modules['extra']['news_sitemap_url'] = $news_sitemap_url;
2623
		}
2624
		return $modules;
2625
	}
2626
2627
	/**
2628
	 * Remove 'validate_callback' item from options available for module.
2629
	 * Fetch current option value and add to array of module options.
2630
	 * Prepare values of module options that need special handling, like those saved in wpcom.
2631
	 *
2632
	 * @since 4.3.0
2633
	 *
2634
	 * @param string $module Module slug.
2635
	 * @return array
2636
	 */
2637
	public static function prepare_options_for_response( $module = '' ) {
2638
		$options = self::get_updateable_data_list( $module );
2639
2640
		if ( ! is_array( $options ) || empty( $options ) ) {
2641
			return $options;
2642
		}
2643
2644
		// Some modules need special treatment.
2645
		switch ( $module ) {
2646
2647
			case 'monitor':
2648
				// Status of user notifications
2649
				$options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
2650
				break;
2651
2652
			case 'post-by-email':
2653
				// Email address
2654
				$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'] );
2655
				break;
2656
2657
			case 'protect':
2658
				// Protect
2659
				$options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
2660
				if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) {
2661
					include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' );
2662
				}
2663
				$options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist();
2664
				break;
2665
2666
			case 'related-posts':
2667
				// It's local, but it must be broken apart since it's saved as an array.
2668
				$options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
2669
				break;
2670
2671
			case 'verification-tools':
2672
				// It's local, but it must be broken apart since it's saved as an array.
2673
				$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
2674
				break;
2675
2676
			case 'google-analytics':
2677
				$wga = get_option( 'jetpack_wga' );
2678
				$code = '';
2679
				if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
2680
					 $code = $wga[ 'code' ];
2681
				}
2682
				$options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
2683
				break;
2684
2685
			case 'sharedaddy':
2686
				// It's local, but it must be broken apart since it's saved as an array.
2687
				if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2688
					break;
2689
				}
2690
				$sharer = new Sharing_Service();
2691
				$options = self::split_options( $options, $sharer->get_global_options() );
2692
				$options['sharing_services']['current_value'] = $sharer->get_blog_services();
2693
				$other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
2694 View Code Duplication
				foreach ( $other_sharedaddy_options as $key ) {
2695
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2696
					$current_value = get_option( $key, $default_value );
2697
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2698
				}
2699
				break;
2700
2701
			case 'stats':
2702
				// It's local, but it must be broken apart since it's saved as an array.
2703
				if ( ! function_exists( 'stats_get_options' ) ) {
2704
					include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
2705
				}
2706
				$options = self::split_options( $options, stats_get_options() );
2707
				break;
2708
			default:
2709
				// These option are just stored as plain WordPress options.
2710 View Code Duplication
				foreach ( $options as $key => $value ) {
2711
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2712
					$current_value = get_option( $key, $default_value );
2713
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2714
				}
2715
		}
2716
		// At this point some options have current_value not set because they're options
2717
		// that only get written on update, so we set current_value to the default one.
2718
		foreach ( $options as $key => $value ) {
2719
			// We don't need validate_callback in the response
2720
			if ( isset( $options[ $key ]['validate_callback'] ) ) {
2721
				unset( $options[ $key ]['validate_callback'] );
2722
			}
2723
			$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2724
			if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
2725
				$options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
2726
			}
2727
		}
2728
		return $options;
2729
	}
2730
2731
	/**
2732
	 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
2733
	 *
2734
	 * @since 4.3.0
2735
	 *
2736
	 * @param array  $separate_options Array of options admitted by the module.
2737
	 * @param array  $grouped_options Option saved as array to be splitted.
2738
	 * @param string $prefix Optional prefix for the separate option keys.
2739
	 *
2740
	 * @return array
2741
	 */
2742
	public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
2743
		if ( is_array( $grouped_options ) ) {
2744
			foreach ( $grouped_options as $key => $value ) {
2745
				$option_key = $prefix . $key;
2746
				if ( isset( $separate_options[ $option_key ] ) ) {
2747
					$separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
2748
				}
2749
			}
2750
		}
2751
		return $separate_options;
2752
	}
2753
2754
	/**
2755
	 * Perform a casting to the value specified in the option definition.
2756
	 *
2757
	 * @since 4.3.0
2758
	 *
2759
	 * @param mixed $value Value to cast to the proper type.
2760
	 * @param array $definition Type to cast the value to.
2761
	 *
2762
	 * @return bool|float|int|string
2763
	 */
2764
	public static function cast_value( $value, $definition ) {
2765
		if ( $value === 'NULL' ) {
2766
			return null;
2767
		}
2768
2769
		if ( isset( $definition['type'] ) ) {
2770
			switch ( $definition['type'] ) {
2771
				case 'boolean':
2772
					if ( 'true' === $value ) {
2773
						return true;
2774
					} elseif ( 'false' === $value ) {
2775
						return false;
2776
					}
2777
					return (bool) $value;
2778
					break;
2779
2780
				case 'integer':
2781
					return (int) $value;
2782
					break;
2783
2784
				case 'float':
2785
					return (float) $value;
2786
					break;
2787
2788
				case 'string':
2789
					return (string) $value;
2790
					break;
2791
			}
2792
		}
2793
		return $value;
2794
	}
2795
2796
	/**
2797
	 * Get a value not saved locally.
2798
	 *
2799
	 * @since 4.3.0
2800
	 *
2801
	 * @param string $module Module slug.
2802
	 * @param string $option Option name.
2803
	 *
2804
	 * @return bool Whether user is receiving notifications or not.
2805
	 */
2806
	public static function get_remote_value( $module, $option ) {
2807
2808
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
2809
			$option .= get_current_user_id();
2810
		}
2811
2812
		// If option doesn't exist, 'does_not_exist' will be returned.
2813
		$value = get_option( $option, 'does_not_exist' );
2814
2815
		// If option exists, just return it.
2816
		if ( 'does_not_exist' !== $value ) {
2817
			return $value;
2818
		}
2819
2820
		// Only check a remote option if Jetpack is connected.
2821
		if ( ! Jetpack::is_active() ) {
2822
			return false;
2823
		}
2824
2825
		// Do what is necessary for each module.
2826
		switch ( $module ) {
2827
			case 'monitor':
2828
				// Load the class to use the method. If class can't be found, do nothing.
2829
				if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2830
					return false;
2831
				}
2832
				$value = Jetpack_Monitor::user_receives_notifications( false );
2833
				break;
2834
2835
			case 'post-by-email':
2836
				// Load the class to use the method. If class can't be found, do nothing.
2837
				if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2838
					return false;
2839
				}
2840
				$post_by_email = new Jetpack_Post_By_Email();
2841
				$value = $post_by_email->get_post_by_email_address();
2842
				if ( $value === null ) {
2843
					$value = 'NULL'; // sentinel value so it actually gets set
2844
				}
2845
				break;
2846
		}
2847
2848
		// Normalize value to boolean.
2849
		if ( is_wp_error( $value ) || is_null( $value ) ) {
2850
			$value = false;
2851
		}
2852
2853
		// Save option to use it next time.
2854
		update_option( $option, $value );
2855
2856
		return $value;
2857
	}
2858
2859
	/**
2860
	 * Get number of plugin updates available.
2861
	 *
2862
	 * @since 4.3.0
2863
	 *
2864
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
2865
	 */
2866
	public static function get_plugin_update_count() {
2867
		$updates = wp_get_update_data();
2868
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
2869
			$count = $updates['counts']['plugins'];
2870
			if ( 0 == $count ) {
2871
				$response = array(
2872
					'code'    => 'success',
2873
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
2874
					'count'   => 0,
2875
				);
2876
			} else {
2877
				$response = array(
2878
					'code'    => 'updates-available',
2879
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
2880
					'count'   => $count,
2881
				);
2882
			}
2883
			return rest_ensure_response( $response );
2884
		}
2885
2886
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
2887
	}
2888
2889
2890
	/**
2891
	 * Returns a list of all plugins in the site.
2892
	 *
2893
	 * @since 4.2.0
2894
	 * @uses get_plugins()
2895
	 *
2896
	 * @return array
2897
	 */
2898
	private static function core_get_plugins() {
2899
		if ( ! function_exists( 'get_plugins' ) ) {
2900
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2901
		}
2902
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2903
		$plugins = apply_filters( 'all_plugins', get_plugins() );
2904
2905
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
2906
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
2907
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
2908
			}
2909
			return $plugins;
2910
		}
2911
2912
		return array();
2913
	}
2914
2915
	/**
2916
	 * Deprecated - Get third party plugin API keys.
2917
	 * @deprecated
2918
	 *
2919
	 * @param WP_REST_Request $request {
2920
	 *     Array of parameters received by request.
2921
	 *
2922
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2923
	 * }
2924
	 */
2925
	public static function get_service_api_key( $request ) {
2926
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key' );
2927
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key( $request );
2928
	}
2929
2930
	/**
2931
	 * Deprecated - Update third party plugin API keys.
2932
	 * @deprecated
2933
	 *
2934
	 * @param WP_REST_Request $request {
2935
	 *     Array of parameters received by request.
2936
	 *
2937
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2938
	 * }
2939
	 */
2940
	public static function update_service_api_key( $request ) {
2941
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key' );
2942
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key( $request ) ;
2943
	}
2944
2945
	/**
2946
	 * Deprecated - Delete a third party plugin API key.
2947
	 * @deprecated
2948
	 *
2949
	 * @param WP_REST_Request $request {
2950
	 *     Array of parameters received by request.
2951
	 *
2952
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2953
	 * }
2954
	 */
2955
	public static function delete_service_api_key( $request ) {
2956
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key' );
2957
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key( $request );
2958
	}
2959
2960
	/**
2961
	 * Deprecated - Validate the service provided in /service-api-keys/ endpoints.
2962
	 * To add a service to these endpoints, add the service name to $valid_services
2963
	 * and add '{service name}_api_key' to the non-compact return array in get_option_names(),
2964
	 * in class-jetpack-options.php
2965
	 * @deprecated
2966
	 *
2967
	 * @param string $service The service the API key is for.
2968
	 * @return string Returns the service name if valid, null if invalid.
2969
	 */
2970
	public static function validate_service_api_service( $service = null ) {
2971
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service' );
2972
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service( $service );
2973
	}
2974
2975
	/**
2976
	 * Error response for invalid service API key requests with an invalid service.
2977
	 */
2978
	public static function service_api_invalid_service_response() {
2979
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response' );
2980
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response();
2981
	}
2982
2983
	/**
2984
	 * Deprecated - Validate API Key
2985
	 * @deprecated
2986
	 *
2987
	 * @param string $key The API key to be validated.
2988
	 * @param string $service The service the API key is for.
2989
	 *
2990
	 */
2991
	public static function validate_service_api_key( $key = null, $service = null ) {
2992
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' );
2993
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key( $key , $service  );
2994
	}
2995
2996
	/**
2997
	 * Deprecated - Validate Mapbox API key
2998
	 * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php
2999
	 * @deprecated
3000
	 *
3001
	 * @param string $key The API key to be validated.
3002
	 */
3003
	public static function validate_service_api_key_mapbox( $key ) {
3004
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' );
3005
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key_mapbox( $key );
3006
3007
	}
3008
3009
	/**
3010
	 * Checks if the queried plugin is active.
3011
	 *
3012
	 * @since 4.2.0
3013
	 * @uses is_plugin_active()
3014
	 *
3015
	 * @return bool
3016
	 */
3017
	private static function core_is_plugin_active( $plugin ) {
3018
		if ( ! function_exists( 'is_plugin_active' ) ) {
3019
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
3020
		}
3021
3022
		return is_plugin_active( $plugin );
3023
	}
3024
3025
	/**
3026
	 * Get plugins data in site.
3027
	 *
3028
	 * @since 4.2.0
3029
	 *
3030
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
3031
	 */
3032
	public static function get_plugins() {
3033
		$plugins = self::core_get_plugins();
3034
3035
		if ( ! empty( $plugins ) ) {
3036
			return rest_ensure_response( $plugins );
3037
		}
3038
3039
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
3040
	}
3041
3042
	/**
3043
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
3044
	 *
3045
	 * @since 4.2.0
3046
	 *
3047
	 * @param WP_REST_Request $request {
3048
	 *     Array of parameters received by request.
3049
	 *
3050
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3051
	 * }
3052
	 *
3053
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
3054
	 */
3055
	public static function get_plugin( $request ) {
3056
3057
		$plugins = self::core_get_plugins();
3058
3059
		if ( empty( $plugins ) ) {
3060
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3061
		}
3062
3063
		$plugin = stripslashes( $request['plugin'] );
3064
3065
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
3066
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
3067
		}
3068
3069
		$plugin_data = $plugins[ $plugin ];
3070
3071
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
3072
3073
		return rest_ensure_response( array(
3074
			'code'    => 'success',
3075
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
3076
			'data'    => $plugin_data
3077
		) );
3078
	}
3079
3080
	/**
3081
	 * Proxies a request to WordPress.com to request that a magic link be sent to the current user
3082
	 * to log this user in to the mobile app via email.
3083
	 *
3084
	 * @param WP_REST_REQUEST $request The request parameters.
3085
	 * @return bool|WP_Error
3086
	 */
3087
	public static function send_mobile_magic_link( $request ) {
3088
		Jetpack::load_xml_rpc_client();
3089
		$xml = new Jetpack_IXR_Client(
3090
			array(
3091
				'user_id' => get_current_user_id(),
3092
			)
3093
		);
3094
3095
		$xml->query( 'jetpack.sendMobileMagicLink', array() );
3096
		if ( $xml->isError() ) {
3097
			return new WP_Error(
3098
				'error_sending_mobile_magic_link',
3099
				sprintf(
3100
					'%s: %s',
3101
					$xml->getErrorCode(),
3102
					$xml->getErrorMessage()
3103
				)
3104
			);
3105
		}
3106
3107
		$response = $xml->getResponse();
3108
3109
		return rest_ensure_response(
3110
			array(
3111
				'code' => 'success',
3112
			)
3113
		);
3114
	}
3115
} // class end
3116