Completed
Push — update/package-sync-wp-super-c... ( 646a03...89a5a9 )
by
unknown
22:00 queued 12:43
created

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

Upgrade to new PHP Analysis Engine

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

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

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
3262
				sprintf(
3263
					'%s: %s',
3264
					$xml->getErrorCode(),
3265
					$xml->getErrorMessage()
3266
				)
3267
			);
3268
		}
3269
3270
		$response = $xml->getResponse();
0 ignored issues
show
$response is not used, you could remove the assignment.

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

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

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

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

Loading history...
3271
3272
		return rest_ensure_response(
3273
			array(
3274
				'code' => 'success',
3275
			)
3276
		);
3277
	}
3278
} // class end
3279