Completed
Push — add/recommended-features-state ( f510df...b9c995 )
by
unknown
130:08 queued 122:48
created

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

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\JITMS\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
		// Get list of plugins that use the Jetpack connection.
168
		register_rest_route(
169
			'jetpack/v4',
170
			'/connection/plugins',
171
			array(
172
				'methods'             => WP_REST_Server::READABLE,
173
				'callback'            => __CLASS__ . '::get_connection_plugins',
174
				'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
175
			)
176
		);
177
178
		// Start the connection process by registering the site on WordPress.com servers.
179
		register_rest_route( 'jetpack/v4', '/connection/register', array(
180
			'methods'             => WP_REST_Server::EDITABLE,
181
			'callback'            => __CLASS__ . '::register_site',
182
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
183
			'args'                => array(
184
				'registration_nonce' => array( 'type' => 'string' ),
185
			),
186
		) );
187
188
		// Set the connection owner
189
		register_rest_route( 'jetpack/v4', '/connection/owner', array(
190
			'methods' => WP_REST_Server::EDITABLE,
191
			'callback' => __CLASS__ . '::set_connection_owner',
192
			'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback',
193
		) );
194
195
		// Current user: get or set tracking settings.
196
		register_rest_route( 'jetpack/v4', '/tracking/settings', array(
197
			array(
198
				'methods'             => WP_REST_Server::READABLE,
199
				'callback'            => __CLASS__ . '::get_user_tracking_settings',
200
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
201
			),
202
			array(
203
				'methods'             => WP_REST_Server::EDITABLE,
204
				'callback'            => __CLASS__ . '::update_user_tracking_settings',
205
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
206
				'args'                => array(
207
					'tracks_opt_out' => array( 'type' => 'boolean' ),
208
				),
209
			),
210
		) );
211
212
		// Disconnect site from WordPress.com servers
213
		register_rest_route( 'jetpack/v4', '/connection', array(
214
			'methods' => WP_REST_Server::EDITABLE,
215
			'callback' => __CLASS__ . '::disconnect_site',
216
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
217
		) );
218
219
		// Disconnect/unlink user from WordPress.com servers
220
		register_rest_route( 'jetpack/v4', '/connection/user', array(
221
			'methods' => WP_REST_Server::EDITABLE,
222
			'callback' => __CLASS__ . '::unlink_user',
223
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
224
		) );
225
226
		// Get current site data
227
		register_rest_route( 'jetpack/v4', '/site', array(
228
			'methods' => WP_REST_Server::READABLE,
229
			'callback' => __CLASS__ . '::get_site_data',
230
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
231
		) );
232
233
		// Get current site data
234
		register_rest_route( 'jetpack/v4', '/site/features', array(
235
			'methods' => WP_REST_Server::READABLE,
236
			'callback' => array( $site_endpoint, 'get_features' ),
237
			'permission_callback' => array( $site_endpoint , 'can_request' ),
238
		) );
239
240
		register_rest_route(
241
			'jetpack/v4',
242
			'/site/products',
243
			array(
244
				'methods'             => WP_REST_Server::READABLE,
245
				'callback'            => array( $site_endpoint, 'get_products' ),
246
				'permission_callback' => array( $site_endpoint, 'can_request' ),
247
			)
248
		);
249
250
		// Get current site purchases.
251
		register_rest_route(
252
			'jetpack/v4',
253
			'/site/purchases',
254
			array(
255
				'methods'             => WP_REST_Server::READABLE,
256
				'callback'            => array( $site_endpoint, 'get_purchases' ),
257
				'permission_callback' => array( $site_endpoint, 'can_request' ),
258
			)
259
		);
260
261
		// Get current site benefits
262
		register_rest_route( 'jetpack/v4', '/site/benefits', array(
263
			'methods'             => WP_REST_Server::READABLE,
264
			'callback'            => array( $site_endpoint, 'get_benefits' ),
265
			'permission_callback' => array( $site_endpoint, 'can_request' ),
266
		) );
267
268
		// Get Activity Log data for this site.
269
		register_rest_route( 'jetpack/v4', '/site/activity', array(
270
			'methods' => WP_REST_Server::READABLE,
271
			'callback' => __CLASS__ . '::get_site_activity',
272
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
273
		) );
274
275
		// Confirm that a site in identity crisis should be in staging mode
276
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
277
			'methods' => WP_REST_Server::EDITABLE,
278
			'callback' => __CLASS__ . '::confirm_safe_mode',
279
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
280
		) );
281
282
		// IDC resolve: create an entirely new shadow site for this URL.
283
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
284
			'methods' => WP_REST_Server::EDITABLE,
285
			'callback' => __CLASS__ . '::start_fresh_connection',
286
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
287
		) );
288
289
		// Handles the request to migrate stats and subscribers during an identity crisis.
290
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
291
			'methods' => WP_REST_Server::EDITABLE,
292
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
293
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
294
		) );
295
296
		// Return all modules
297
		register_rest_route( 'jetpack/v4', '/module/all', array(
298
			'methods' => WP_REST_Server::READABLE,
299
			'callback' => array( $module_list_endpoint, 'process' ),
300
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
301
		) );
302
303
		// Activate many modules
304
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
305
			'methods' => WP_REST_Server::EDITABLE,
306
			'callback' => array( $module_list_endpoint, 'process' ),
307
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
308
			'args' => array(
309
				'modules' => array(
310
					'default'           => '',
311
					'type'              => 'array',
312
					'items'             => array(
313
						'type'          => 'string',
314
					),
315
					'required'          => true,
316
					'validate_callback' => __CLASS__ . '::validate_module_list',
317
				),
318
				'active' => array(
319
					'default'           => true,
320
					'type'              => 'boolean',
321
					'required'          => false,
322
					'validate_callback' => __CLASS__ . '::validate_boolean',
323
				),
324
			)
325
		) );
326
327
		// Return a single module and update it when needed
328
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
329
			'methods' => WP_REST_Server::READABLE,
330
			'callback' => array( $core_api_endpoint, 'process' ),
331
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
332
		) );
333
334
		// Activate and deactivate a module
335
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
336
			'methods' => WP_REST_Server::EDITABLE,
337
			'callback' => array( $module_toggle_endpoint, 'process' ),
338
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
339
			'args' => array(
340
				'active' => array(
341
					'default'           => true,
342
					'type'              => 'boolean',
343
					'required'          => true,
344
					'validate_callback' => __CLASS__ . '::validate_boolean',
345
				),
346
			)
347
		) );
348
349
		// Update a module
350
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
351
			'methods' => WP_REST_Server::EDITABLE,
352
			'callback' => array( $core_api_endpoint, 'process' ),
353
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
354
			'args' => self::get_updateable_parameters( 'any' )
355
		) );
356
357
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
358
		// Akismet spam count, etc.
359
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
360
			'methods' => WP_REST_Server::READABLE,
361
			'callback' => array( $module_data_endpoint, 'process' ),
362
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
363
			'args' => array(
364
				'range' => array(
365
					'default'           => 'day',
366
					'type'              => 'string',
367
					'required'          => false,
368
					'validate_callback' => __CLASS__ . '::validate_string',
369
				),
370
			)
371
		) );
372
373
		// Check if the API key for a specific service is valid or not
374
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
375
			'methods' => WP_REST_Server::READABLE,
376
			'callback' => array( $module_data_endpoint, 'key_check' ),
377
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
378
			'sanitize_callback' => 'sanitize_text_field',
379
		) );
380
381
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
382
			'methods' => WP_REST_Server::EDITABLE,
383
			'callback' => array( $module_data_endpoint, 'key_check' ),
384
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
385
			'sanitize_callback' => 'sanitize_text_field',
386
			'args' => array(
387
				'api_key' => array(
388
					'default'           => '',
389
					'type'              => 'string',
390
					'validate_callback' => __CLASS__ . '::validate_alphanum',
391
				),
392
			)
393
		) );
394
395
		// Update any Jetpack module option or setting
396
		register_rest_route( 'jetpack/v4', '/settings', array(
397
			'methods' => WP_REST_Server::EDITABLE,
398
			'callback' => array( $core_api_endpoint, 'process' ),
399
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
400
			'args' => self::get_updateable_parameters( 'any' )
401
		) );
402
403
		// Update a module
404
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
405
			'methods' => WP_REST_Server::EDITABLE,
406
			'callback' => array( $core_api_endpoint, 'process' ),
407
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
408
			'args' => self::get_updateable_parameters()
409
		) );
410
411
		// Return all module settings
412
		register_rest_route( 'jetpack/v4', '/settings/', array(
413
			'methods' => WP_REST_Server::READABLE,
414
			'callback' => array( $core_api_endpoint, 'process' ),
415
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
416
		) );
417
418
		// Reset all Jetpack options
419
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
420
			'methods' => WP_REST_Server::EDITABLE,
421
			'callback' => __CLASS__ . '::reset_jetpack_options',
422
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
423
		) );
424
425
		// Updates: get number of plugin updates available
426
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
427
			'methods' => WP_REST_Server::READABLE,
428
			'callback' => __CLASS__ . '::get_plugin_update_count',
429
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
430
		) );
431
432
		// Dismiss Jetpack Notices
433
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
434
			'methods' => WP_REST_Server::EDITABLE,
435
			'callback' => __CLASS__ . '::dismiss_notice',
436
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
437
		) );
438
439
		// Plugins: get list of all plugins.
440
		register_rest_route( 'jetpack/v4', '/plugins', array(
441
			'methods' => WP_REST_Server::READABLE,
442
			'callback' => __CLASS__ . '::get_plugins',
443
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
444
		) );
445
446
		register_rest_route( 'jetpack/v4', '/plugins/akismet/activate', array(
447
			'methods' => WP_REST_Server::EDITABLE,
448
			'callback' => __CLASS__ . '::activate_akismet',
449
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
450
		) );
451
452
		// Plugins: check if the plugin is active.
453
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
454
			'methods' => WP_REST_Server::READABLE,
455
			'callback' => __CLASS__ . '::get_plugin',
456
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
457
		) );
458
459
		// Widgets: get information about a widget that supports it.
460
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
461
			'methods' => WP_REST_Server::READABLE,
462
			'callback' => array( $widget_endpoint, 'process' ),
463
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
464
		) );
465
466
		// Site Verify: check if the site is verified, and a get verification token if not
467
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
468
			'methods' => WP_REST_Server::READABLE,
469
			'callback' => __CLASS__ . '::is_site_verified_and_token',
470
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
471
		) );
472
473
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
474
			'methods' => WP_REST_Server::READABLE,
475
			'callback' => __CLASS__ . '::is_site_verified_and_token',
476
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
477
		) );
478
479
		// Site Verify: tell a service to verify the site
480
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
481
			'methods' => WP_REST_Server::EDITABLE,
482
			'callback' => __CLASS__ . '::verify_site',
483
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
484
			'args' => array(
485
				'keyring_id' => array(
486
					'required'          => true,
487
					'type'              => 'integer',
488
					'validate_callback' => __CLASS__  . '::validate_posint',
489
				),
490
			)
491
		) );
492
493
		// Get and set API keys.
494
		// Note: permission_callback intentionally omitted from the GET method.
495
		// Map block requires open access to API keys on the front end.
496
		register_rest_route(
497
			'jetpack/v4',
498
			'/service-api-keys/(?P<service>[a-z\-_]+)',
499
			array(
500
				array(
501
					'methods'             => WP_REST_Server::READABLE,
502
					'callback'            => __CLASS__ . '::get_service_api_key',
503
				),
504
				array(
505
					'methods'             => WP_REST_Server::EDITABLE,
506
					'callback'            => __CLASS__ . '::update_service_api_key',
507
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
508
					'args'                => array(
509
						'service_api_key' => array(
510
							'required' => true,
511
							'type'     => 'text',
512
						),
513
					),
514
				),
515
				array(
516
					'methods'             => WP_REST_Server::DELETABLE,
517
					'callback'            => __CLASS__ . '::delete_service_api_key',
518
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
519
				),
520
			)
521
		);
522
523
		register_rest_route(
524
			'jetpack/v4',
525
			'/mobile/send-login-email',
526
			array(
527
				'methods'             => WP_REST_Server::EDITABLE,
528
				'callback'            => __CLASS__ . '::send_mobile_magic_link',
529
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
530
			)
531
		);
532
533
		/*
534
		 * Get and update settings from the Jetpack wizard.
535
		 */
536
		register_rest_route(
537
			'jetpack/v4',
538
			'/setup/questionnaire',
539
			array(
540
				array(
541
					'methods'             => WP_REST_Server::READABLE,
542
					'callback'            => __CLASS__ . '::get_setup_questionnaire',
543
					'permission_callback' => __CLASS__ . '::update_settings_permission_check',
544
				),
545
				array(
546
					'methods'             => WP_REST_Server::EDITABLE,
547
					'callback'            => __CLASS__ . '::update_setup_questionnaire',
548
					'permission_callback' => __CLASS__ . '::update_settings_permission_check',
549
					'args'                => array(
550
						'questionnaire' => array(
551
							'required' => true,
552
							'type'     => 'object',
553
						),
554
					),
555
				),
556
			)
557
		);
558
	}
559
560
	/**
561
	 * Update the settings selected on the wizard questionnaire
562
	 *
563
	 * @param WP_REST_Request $request The request.
564
	 *
565
	 * @return bool true.
566
	 */
567
	public static function update_setup_questionnaire( $request ) {
568
		// TODO: add validation.
569
570
		$questionnaire = empty( $request['questionnaire'] ) ? (object) array() : $request['questionnaire'];
571
		Jetpack_Options::update_option( 'setup_questionnaire', $questionnaire );
572
		return true;
573
	}
574
575
	/**
576
	 * Get the settings for the wizard questionnaire
577
	 *
578
	 * @return array Questionnaire settings.
579
	 */
580
	public static function get_setup_questionnaire() {
581
		return Jetpack_Options::get_option( 'setup_questionnaire', (object) array() );
582
	}
583
584
	public static function get_plans( $request ) {
585
		$request = Client::wpcom_json_api_request_as_user(
586
			'/plans?_locale=' . get_user_locale(),
587
			'2',
588
			array(
589
				'method'  => 'GET',
590
				'headers' => array(
591
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
592
				),
593
			)
594
		);
595
596
		$body = json_decode( wp_remote_retrieve_body( $request ) );
597
		if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
598
			$data = $body;
599
		} else {
600
			// something went wrong so we'll just return the response without caching
601
			return $body;
602
		}
603
604
		return $data;
605
	}
606
607
	/**
608
	 * Gets the WP.com products that are in use on wpcom.
609
	 * Similar to the WP.com plans that we currently in user on WPCOM.
610
	 *
611
	 * @param WP_REST_Request $request The request.
612
	 *
613
	 * @return string|WP_Error A JSON object of wpcom products if the request was successful, or a WP_Error otherwise.
614
	 */
615
	public static function get_products( $request ) {
616
		$wpcom_request = Client::wpcom_json_api_request_as_user(
617
			'/products?_locale=' . get_user_locale() . '&type=jetpack',
618
			'2',
619
			array(
620
				'method'  => 'GET',
621
				'headers' => array(
622
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
623
				),
624
			)
625
		);
626
627
		$response_code = wp_remote_retrieve_response_code( $wpcom_request );
628
		if ( 200 === $response_code ) {
629
			return json_decode( wp_remote_retrieve_body( $wpcom_request ) );
630
		} else {
631
			// Something went wrong so we'll just return the response without caching.
632
			return new WP_Error(
633
				'failed_to_fetch_data',
634
				esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
635
				array( 'status' => $response_code )
636
			);
637
		}
638
	}
639
640
	public static function submit_survey( $request ) {
641
642
		$wpcom_request = Client::wpcom_json_api_request_as_user(
643
			'/marketing/survey',
644
			'v2',
645
			array(
646
				'method'  => 'POST',
647
				'headers' => array(
648
					'Content-Type'    => 'application/json',
649
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
650
				),
651
			),
652
			$request->get_json_params()
653
		);
654
655
		$wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
656
		if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) {
657
			$data = $wpcom_request_body;
658
		} else {
659
			// something went wrong so we'll just return the response without caching
660
			return $wpcom_request_body;
661
		}
662
663
		return $data;
664
	}
665
666
	/**
667
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
668
	 *
669
	 * @param $request WP_REST_Request
670
	 *
671
	 * @return array An array of jitms
672
	 */
673
	public static function get_jitm_message( $request ) {
674
		$jitm = JITM::get_instance();
675
676
		if ( ! $jitm->register() ) {
677
			return array();
678
		}
679
680
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ), 'true' === $request['full_jp_logo_exists'] ? true : false );
681
	}
682
683
	/**
684
	 * Dismisses a jitm
685
	 * @param $request WP_REST_Request The request
686
	 *
687
	 * @return bool Always True
688
	 */
689
	public static function delete_jitm_message( $request ) {
690
		$jitm = JITM::get_instance();
691
692
		if ( ! $jitm->register() ) {
693
			return true;
694
		}
695
696
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
697
	}
698
699
	/**
700
	 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
701
	 *  keyring to use to get the token if it is not
702
	 *
703
	 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
704
	 *
705
	 * @since 6.6.0
706
	 *
707
	 * @param WP_REST_Request $request The request sent to the WP REST API.
708
	 *
709
	 * @return array|wp-error
710
	 */
711
	public static function is_site_verified_and_token( $request ) {
712
		/**
713
		 * Return an error if the site uses a Maintenance / Coming Soon plugin
714
		 * and if the plugin is configured to make the site private.
715
		 *
716
		 * We currently handle the following plugins:
717
		 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
718
		 * - https://wordpress.org/plugins/mojo-under-construction
719
		 * - https://wordpress.org/plugins/under-construction-page
720
		 * - https://wordpress.org/plugins/ultimate-under-construction
721
		 * - https://wordpress.org/plugins/coming-soon
722
		 *
723
		 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
724
		 * If the filter returns true, we will consider the site as under construction.
725
		 */
726
		$mm_coming_soon                       = get_option( 'mm_coming_soon', null );
727
		$under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
728
		$ucp_options                          = get_option( 'ucp_options', array() );
729
		$uuc_settings                         = get_option( 'uuc_settings', array() );
730
		$csp4                                 = get_option( 'seed_csp4_settings_content', array() );
731
		if (
732
			( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
733
			|| Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
734
			|| ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
735
			|| ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
736
			|| ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) &&  isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
737
			/**
738
			 * Allow plugins to mark a site as "under construction".
739
			 *
740
			 * @since 6.7.0
741
			 *
742
			 * @param false bool Is the site under construction? Default to false.
743
			 */
744
			|| true === apply_filters( 'jetpack_is_under_construction_plugin', false )
745
		) {
746
			return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
747
		}
748
749
 		$xml = new Jetpack_IXR_Client( array(
750
 			'user_id' => get_current_user_id(),
751
		) );
752
753
		$args = array(
754
			'user_id' => get_current_user_id(),
755
			'service' => $request[ 'service' ],
756
		);
757
758
		if ( isset( $request[ 'keyring_id' ] ) ) {
759
			$args[ 'keyring_id' ] = $request[ 'keyring_id' ];
760
		}
761
762
		$xml->query( 'jetpack.isSiteVerified', $args );
763
764
		if ( $xml->isError() ) {
765
			return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
766
		} else {
767
			return $xml->getResponse();
768
		}
769
	}
770
771
772
773
	public static function verify_site( $request ) {
774
		$xml = new Jetpack_IXR_Client( array(
775
			'user_id' => get_current_user_id(),
776
		) );
777
778
		$params = $request->get_json_params();
779
780
		$xml->query( 'jetpack.verifySite', array(
781
				'user_id' => get_current_user_id(),
782
				'service' => $request[ 'service' ],
783
				'keyring_id' => $params[ 'keyring_id' ],
784
			)
785
		);
786
787
		if ( $xml->isError() ) {
788
			return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
789
		} else {
790
			$response = $xml->getResponse();
791
792
			if ( ! empty( $response['errors'] ) ) {
793
				$error = new WP_Error;
794
				$error->errors = $response['errors'];
795
				return $error;
796
			}
797
798
			return $response;
799
		}
800
	}
801
802
	/**
803
	 * Handles verification that a site is registered
804
	 *
805
	 * @since 5.4.0
806
	 *
807
	 * @param WP_REST_Request $request The request sent to the WP REST API.
808
	 *
809
	 * @return array|wp-error
810
	 */
811
	 public static function remote_authorize( $request ) {
812
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
813
		$result = $xmlrpc_server->remote_authorize( $request );
814
815
		if ( is_a( $result, 'IXR_Error' ) ) {
816
			$result = new WP_Error( $result->code, $result->message );
817
		}
818
819
		return $result;
820
	 }
821
822
	/**
823
	 * Handles dismissing of Jetpack Notices
824
	 *
825
	 * @since 4.3.0
826
	 *
827
	 * @param WP_REST_Request $request The request sent to the WP REST API.
828
	 *
829
	 * @return array|wp-error
830
	 */
831
	public static function dismiss_notice( $request ) {
832
		$notice = $request['notice'];
833
834
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
835
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
836
		}
837
838
		if ( isset( $notice ) && ! empty( $notice ) ) {
839
			switch( $notice ) {
840
				case 'feedback_dash_request':
841
				case 'welcome':
842
					$notices = get_option( 'jetpack_dismissed_notices', array() );
843
					$notices[ $notice ] = true;
844
					update_option( 'jetpack_dismissed_notices', $notices );
845
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
846
847
				default:
848
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
849
			}
850
		}
851
852
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
853
	}
854
855
	/**
856
	 * Verify that the user can disconnect the site.
857
	 *
858
	 * @since 4.3.0
859
	 *
860
	 * @return bool|WP_Error True if user is able to disconnect the site.
861
	 */
862 View Code Duplication
	public static function disconnect_site_permission_callback() {
863
		if ( current_user_can( 'jetpack_disconnect' ) ) {
864
			return true;
865
		}
866
867
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
868
869
	}
870
871
	/**
872
	 * Verify that the user can get a connect/link URL
873
	 *
874
	 * @since 4.3.0
875
	 *
876
	 * @return bool|WP_Error True if user is able to disconnect the site.
877
	 */
878 View Code Duplication
	public static function connect_url_permission_callback() {
879
		if ( current_user_can( 'jetpack_connect_user' ) ) {
880
			return true;
881
		}
882
883
		return new WP_Error( 'invalid_user_permission_jetpack_connect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
884
885
	}
886
887
	/**
888
	 * Verify that a user can get the data about the current user.
889
	 * Only those who can connect.
890
	 *
891
	 * @since 4.3.0
892
	 *
893
	 * @uses Jetpack::is_user_connected();
894
	 *
895
	 * @return bool|WP_Error True if user is able to unlink.
896
	 */
897 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
898
		if ( current_user_can( 'jetpack_connect_user' ) ) {
899
			return true;
900
		}
901
902
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
903
	}
904
905
	/**
906
	 * Check that user has permission to change the master user.
907
	 *
908
	 * @since 6.2.0
909
	 * @since 7.7.0 Update so that any user with jetpack_disconnect privs can set owner.
910
	 *
911
	 * @return bool|WP_Error True if user is able to change master user.
912
	 */
913 View Code Duplication
	public static function set_connection_owner_permission_callback() {
914
		if ( current_user_can( 'jetpack_disconnect' ) ) {
915
			return true;
916
		}
917
918
		return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
919
	}
920
921
	/**
922
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
923
	 *
924
	 * @since 4.3.0
925
	 *
926
	 * @uses Jetpack::is_user_connected();
927
	 *
928
	 * @return bool|WP_Error True if user is able to unlink.
929
	 */
930
	public static function unlink_user_permission_callback() {
931
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
932
			return true;
933
		}
934
935
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
936
	}
937
938
	/**
939
	 * Verify that user can manage Jetpack modules.
940
	 *
941
	 * @since 4.3.0
942
	 *
943
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
944
	 */
945
	public static function manage_modules_permission_check() {
946
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
947
			return true;
948
		}
949
950
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
951
	}
952
953
	/**
954
	 * Verify that user can update Jetpack modules.
955
	 *
956
	 * @since 4.3.0
957
	 *
958
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
959
	 */
960 View Code Duplication
	public static function configure_modules_permission_check() {
961
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
962
			return true;
963
		}
964
965
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
966
	}
967
968
	/**
969
	 * Verify that user can view Jetpack admin page.
970
	 *
971
	 * @since 4.3.0
972
	 *
973
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
974
	 */
975 View Code Duplication
	public static function view_admin_page_permission_check() {
976
		if ( current_user_can( 'jetpack_admin_page' ) ) {
977
			return true;
978
		}
979
980
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
981
	}
982
983
	/**
984
	 * Verify that user can mitigate an identity crisis.
985
	 *
986
	 * @since 4.4.0
987
	 *
988
	 * @return bool Whether user has capability 'jetpack_disconnect'.
989
	 */
990 View Code Duplication
	public static function identity_crisis_mitigation_permission_check() {
991
		if ( current_user_can( 'jetpack_disconnect' ) ) {
992
			return true;
993
		}
994
995
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
996
	}
997
998
	/**
999
	 * Verify that user can update Jetpack general settings.
1000
	 *
1001
	 * @since 4.3.0
1002
	 *
1003
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
1004
	 */
1005 View Code Duplication
	public static function update_settings_permission_check() {
1006
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
1007
			return true;
1008
		}
1009
1010
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1011
	}
1012
1013
	/**
1014
	 * Verify that user can view Jetpack admin page and can activate plugins.
1015
	 *
1016
	 * @since 4.3.0
1017
	 *
1018
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
1019
	 */
1020 View Code Duplication
	public static function activate_plugins_permission_check() {
1021
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
1022
			return true;
1023
		}
1024
1025
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1026
	}
1027
1028
	/**
1029
	 * Verify that user can edit other's posts (Editors and Administrators).
1030
	 *
1031
	 * @return bool Whether user has the capability 'edit_others_posts'.
1032
	 */
1033
	public static function edit_others_posts_check() {
1034
		if ( current_user_can( 'edit_others_posts' ) ) {
1035
			return true;
1036
		}
1037
1038
		return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1039
	}
1040
1041
	/**
1042
	 * Contextual HTTP error code for authorization failure.
1043
	 *
1044
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
1045
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
1046
	 *
1047
	 * @since 4.3.0
1048
	 *
1049
	 * @return int
1050
	 */
1051
	public static function rest_authorization_required_code() {
1052
		return is_user_logged_in() ? 403 : 401;
1053
	}
1054
1055
	/**
1056
	 * Get connection status for this Jetpack site.
1057
	 *
1058
	 * @since 4.3.0
1059
	 *
1060
	 * @return bool True if site is connected
1061
	 */
1062
	public static function jetpack_connection_status() {
1063
		$status = new Status();
1064
		return rest_ensure_response( array(
1065
			'isActive'     => Jetpack::is_active(),
1066
			'isStaging'    => $status->is_staging_site(),
1067
			'isRegistered' => Jetpack::connection()->is_registered(),
1068
			'devMode'      => array(
1069
				'isActive' => $status->is_development_mode(),
1070
				'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
1071
				'url'      => site_url() && false === strpos( site_url(), '.' ),
1072
				'filter'   => apply_filters( 'jetpack_development_mode', false ),
1073
			),
1074
			)
1075
		);
1076
	}
1077
1078
	/**
1079
	 * Test connection status for this Jetpack site.
1080
	 *
1081
	 * @since 6.8.0
1082
	 *
1083
	 * @return array|WP_Error WP_Error returned if connection test does not succeed.
1084
	 */
1085
	public static function jetpack_connection_test() {
1086
		jetpack_require_lib( 'debugger' );
1087
		$cxntests = new Jetpack_Cxn_Tests();
1088
1089
		if ( $cxntests->pass() ) {
1090
			return rest_ensure_response(
1091
				array(
1092
					'code'    => 'success',
1093
					'message' => __( 'All connection tests passed.', 'jetpack' ),
1094
				)
1095
			);
1096
		} else {
1097
			return $cxntests->output_fails_as_wp_error();
1098
		}
1099
	}
1100
1101
	/**
1102
	 * Get plugins connected to the Jetpack.
1103
	 *
1104
	 * @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
1105
	 */
1106
	public static function get_connection_plugins() {
1107
		$plugins = ( new Connection_Manager() )->get_connected_plugins();
1108
1109
		if ( is_wp_error( $plugins ) ) {
1110
			return $plugins;
1111
		}
1112
1113
		array_walk(
1114
			$plugins,
1115
			function( &$data, $slug ) {
1116
				$data['slug'] = $slug;
1117
			}
1118
		);
1119
1120
		return rest_ensure_response( array_values( $plugins ) );
1121
	}
1122
1123
	/**
1124
	 * Test connection permission check method.
1125
	 *
1126
	 * @since 7.1.0
1127
	 *
1128
	 * @return bool
1129
	 */
1130
	public static function view_jetpack_connection_test_check() {
1131
		if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'] ) ) {
1132
			return false;
1133
		}
1134
		$signature = base64_decode( $_GET['signature'] );
1135
1136
		$signature_data = wp_json_encode(
1137
			array(
1138
				'rest_route' => $_GET['rest_route'],
1139
				'timestamp' => intval( $_GET['timestamp'] ),
1140
				'url' => wp_unslash( $_GET['url'] ),
1141
			)
1142
		);
1143
1144
		if (
1145
			! function_exists( 'openssl_verify' )
1146
			|| 1 !== openssl_verify(
1147
				$signature_data,
1148
				$signature,
1149
				JETPACK__DEBUGGER_PUBLIC_KEY
1150
			)
1151
		) {
1152
			return false;
1153
		}
1154
1155
		// signature timestamp must be within 5min of current time
1156
		if ( abs( time() - intval( $_GET['timestamp'] ) ) > 300 ) {
1157
			return false;
1158
		}
1159
1160
		return true;
1161
	}
1162
1163
	/**
1164
	 * Test connection status for this Jetpack site, encrypt the results for decryption by a third-party.
1165
	 *
1166
	 * @since 7.1.0
1167
	 *
1168
	 * @return array|mixed|object|WP_Error
1169
	 */
1170
	public static function jetpack_connection_test_for_external() {
1171
		// 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.
1172
		add_filter( 'jetpack_debugger_run_self_test', '__return_false' );
1173
		jetpack_require_lib( 'debugger' );
1174
		$cxntests = new Jetpack_Cxn_Tests();
1175
1176
		if ( $cxntests->pass() ) {
1177
			$result = array(
1178
				'code'    => 'success',
1179
				'message' => __( 'All connection tests passed.', 'jetpack' ),
1180
			);
1181
		} else {
1182
			$error  = $cxntests->output_fails_as_wp_error(); // Using this so the output is similar both ways.
1183
			$errors = array();
1184
1185
			// Borrowed from WP_REST_Server::error_to_response().
1186
			foreach ( (array) $error->errors as $code => $messages ) {
1187
				foreach ( (array) $messages as $message ) {
1188
					$errors[] = array(
1189
						'code'    => $code,
1190
						'message' => $message,
1191
						'data'    => $error->get_error_data( $code ),
1192
					);
1193
				}
1194
			}
1195
1196
			$result = ( ! empty( $errors ) ) ? $errors[0] : null;
1197
			if ( count( $errors ) > 1 ) {
1198
				// Remove the primary error.
1199
				array_shift( $errors );
1200
				$result['additional_errors'] = $errors;
1201
			}
1202
		}
1203
1204
		$result = wp_json_encode( $result );
1205
1206
		$encrypted = $cxntests->encrypt_string_for_wpcom( $result );
1207
1208
		if ( ! $encrypted || ! is_array( $encrypted ) ) {
1209
			return rest_ensure_response(
1210
				array(
1211
					'code'    => 'action_required',
1212
					'message' => 'Please request results from the in-plugin debugger',
1213
				)
1214
			);
1215
		}
1216
1217
		return rest_ensure_response(
1218
			array(
1219
				'code'  => 'response',
1220
				'debug' => array(
1221
					'data' => $encrypted['data'],
1222
					'key'  => $encrypted['key'],
1223
				),
1224
			)
1225
		);
1226
	}
1227
1228
	public static function rewind_data() {
1229
		$site_id = Jetpack_Options::get_option( 'id' );
1230
1231
		if ( ! $site_id ) {
1232
			return new WP_Error( 'site_id_missing' );
1233
		}
1234
1235
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' );
1236
1237
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1238
			return new WP_Error( 'rewind_data_fetch_failed' );
1239
		}
1240
1241
		$body = wp_remote_retrieve_body( $response );
1242
1243
		return json_decode( $body );
1244
	}
1245
1246
	/**
1247
	 * Get rewind data
1248
	 *
1249
	 * @since 5.7.0
1250
	 *
1251
	 * @return array Array of rewind properties.
1252
	 */
1253 View Code Duplication
	public static function get_rewind_data() {
1254
		$rewind_data = self::rewind_data();
1255
1256
		if ( ! is_wp_error( $rewind_data ) ) {
1257
			return rest_ensure_response( array(
1258
					'code' => 'success',
1259
					'message' => esc_html__( 'Backup & Scan data correctly received.', 'jetpack' ),
1260
					'data' => wp_json_encode( $rewind_data ),
1261
				)
1262
			);
1263
		}
1264
1265
		if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) {
1266
			return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1267
		}
1268
1269
		if ( $rewind_data->get_error_code() === 'site_id_missing' ) {
1270
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1271
		}
1272
1273
		return new WP_Error(
1274
			'error_get_rewind_data',
1275
			esc_html__( 'Could not retrieve Backup & Scan data.', 'jetpack' ),
1276
			array( 'status' => 500 )
1277
		);
1278
	}
1279
1280
	/**
1281
	 * Gets Scan state data.
1282
	 *
1283
	 * @since 8.5.0
1284
	 *
1285
	 * @return array|WP_Error Result from WPCOM API or error.
1286
	 */
1287
	public static function scan_state() {
1288
1289
		if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1290
			$scan_state = get_transient( 'jetpack_scan_state' );
1291
			if ( ! empty( $scan_state ) ) {
1292
				return $scan_state;
1293
			}
1294
		}
1295
		$site_id = Jetpack_Options::get_option( 'id' );
1296
1297
		if ( ! $site_id ) {
1298
			return new WP_Error( 'site_id_missing' );
1299
		}
1300
		// The default timeout was too short in come cases.
1301
		add_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 );
1302
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
1303
		remove_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 );
1304
1305
		if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
1306
			return new WP_Error( 'scan_state_fetch_failed' );
1307
		}
1308
1309
		$body   = wp_remote_retrieve_body( $response );
1310
		$result = json_decode( $body );
1311
		set_transient( 'jetpack_scan_state', $result, 30 * MINUTE_IN_SECONDS );
1312
1313
		return $result;
1314
	}
1315
1316
	/**
1317
	 * Increases the request timeout value to 30 seconds.
1318
	 *
1319
	 * @return int Always returns 30.
1320
	 */
1321
	public static function increase_timeout_30() {
1322
		return 30; // 30 Seconds
1323
	}
1324
1325
	/**
1326
	 * Get Scan state for API.
1327
	 *
1328
	 * @since 8.5.0
1329
	 *
1330
	 * @return WP_REST_Response|WP_Error REST response or error state.
1331
	 */
1332 View Code Duplication
	public static function get_scan_state() {
1333
		$scan_state = self::scan_state();
1334
1335
		if ( ! is_wp_error( $scan_state ) ) {
1336
			return rest_ensure_response(
1337
				array(
1338
					'code'    => 'success',
1339
					'message' => esc_html__( 'Scan state correctly received.', 'jetpack' ),
1340
					'data'    => wp_json_encode( $scan_state ),
1341
				)
1342
			);
1343
		}
1344
1345
		if ( $scan_state->get_error_code() === 'scan_state_fetch_failed' ) {
1346
			return new WP_Error( 'scan_state_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1347
		}
1348
1349
		if ( $scan_state->get_error_code() === 'site_id_missing' ) {
1350
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1351
		}
1352
1353
		return new WP_Error(
1354
			'error_get_rewind_data',
1355
			esc_html__( 'Could not retrieve Scan state.', 'jetpack' ),
1356
			array( 'status' => 500 )
1357
		);
1358
	}
1359
1360
	/**
1361
	 * Disconnects Jetpack from the WordPress.com Servers
1362
	 *
1363
	 * @uses Jetpack::disconnect();
1364
	 * @since 4.3.0
1365
	 *
1366
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1367
	 *
1368
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
1369
	 */
1370 View Code Duplication
	public static function disconnect_site( $request ) {
1371
1372
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
1373
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1374
		}
1375
1376
		if ( Jetpack::is_active() ) {
1377
			Jetpack::disconnect();
1378
			return rest_ensure_response( array( 'code' => 'success' ) );
1379
		}
1380
1381
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1382
	}
1383
1384
	/**
1385
	 * Registers the Jetpack site
1386
	 *
1387
	 * @uses Jetpack::try_registration();
1388
	 * @since 7.7.0
1389
	 *
1390
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1391
	 *
1392
	 * @return bool|WP_Error True if Jetpack successfully registered
1393
	 */
1394
	public static function register_site( $request ) {
1395
		if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
1396
			return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack' ), array( 'status' => 403 ) );
1397
		}
1398
1399
		$response = Jetpack::try_registration();
1400
1401
		if ( is_wp_error( $response ) ) {
1402
			return $response;
1403
		}
1404
1405
		return rest_ensure_response(
1406
			array(
1407
				'authorizeUrl' => Jetpack::build_authorize_url( false, true )
1408
			) );
1409
	}
1410
1411
	/**
1412
	 * Gets a new connect raw URL with fresh nonce.
1413
	 *
1414
	 * @uses Jetpack::disconnect();
1415
	 * @since 4.3.0
1416
	 *
1417
	 * @param WP_REST_Request $request The request sent to the WP REST API.
0 ignored issues
show
Should the type for parameter $request not be WP_REST_Request|array?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

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