Completed
Push — add/block-image-compare ( 093ae3...f647cd )
by
unknown
07:08
created

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

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...
1259
		}
1260
1261
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
1262
1263
		if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
1264
			return new WP_Error( 'scan_state_fetch_failed' );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'scan_state_fetch_failed'.

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...
1265
		}
1266
1267
		$body = wp_remote_retrieve_body( $response );
1268
1269
		return json_decode( $body );
1270
	}
1271
1272
	/**
1273
	 * Get Scan state for API.
1274
	 *
1275
	 * @since 8.5.0
1276
	 *
1277
	 * @return WP_REST_Response|WP_Error REST response or error state.
1278
	 */
1279 View Code Duplication
	public static function get_scan_state() {
1280
		$scan_state = self::scan_state();
1281
1282
		if ( ! is_wp_error( $scan_state ) ) {
1283
			return rest_ensure_response(
1284
				array(
1285
					'code'    => 'success',
1286
					'message' => esc_html__( 'Scan state correctly received.', 'jetpack' ),
1287
					'data'    => wp_json_encode( $scan_state ),
1288
				)
1289
			);
1290
		}
1291
1292
		if ( $scan_state->get_error_code() === 'scan_state_fetch_failed' ) {
0 ignored issues
show
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1293
			return new WP_Error( 'scan_state_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'scan_state_fetch_failed'.

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...
1294
		}
1295
1296
		if ( $scan_state->get_error_code() === 'site_id_missing' ) {
0 ignored issues
show
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1297
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'site_id_missing'.

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...
1298
		}
1299
1300
		return new WP_Error(
1301
			'error_get_rewind_data',
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'error_get_rewind_data'.

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