Completed
Push — renovate/node-sass-4.x ( 72b10c...2ecf5f )
by
unknown
230:06 queued 223:36
created

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

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
use Automattic\Jetpack\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
						'option_values' => 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
		$option_values = empty( $request['option_values'] ) ? array() : $request['option_values'];
571
		Jetpack_Options::update_option( 'setup_questionnaire', $option_values );
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', 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'] );
0 ignored issues
show
The call to Pre_Connection_JITM::dismiss() has too many arguments starting with $request['feature_class'].

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

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

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

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