Completed
Push — fix/sync-empty-errors ( 53827c...ec13f3 )
by
unknown
89:12 queued 80:44
created

_inc/lib/class.core-rest-api-endpoints.php (5 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\Connection\REST_Connector;
6
use Automattic\Jetpack\JITMS\JITM;
7
use Automattic\Jetpack\Tracking;
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
	 * @deprecated 8.8.0 Use `REST_Connector::get_user_permissions_error_msg()` instead.
42
	 */
43
	public static $user_permissions_error_msg;
44
45
	/**
46
	 * @var array Roles that can access Stats once they're granted access.
47
	 */
48
	public static $stats_roles;
49
50
	/**
51
	 * Declare the Jetpack REST API endpoints.
52
	 *
53
	 * @since 4.3.0
54
	 */
55
	public static function register_endpoints() {
56
57
		// Load API endpoint base classes
58
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
59
60
		// Load API endpoints
61
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
62
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
63
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
64
65
		self::$user_permissions_error_msg = REST_Connector::get_user_permissions_error_msg();
66
67
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
68
69
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
70
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
71
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
72
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
73
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
74
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
75
		$widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
76
77
		register_rest_route( 'jetpack/v4', 'plans', array(
78
			'methods'             => WP_REST_Server::READABLE,
79
			'callback'            => __CLASS__ . '::get_plans',
80
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
81
		) );
82
83
		register_rest_route( 'jetpack/v4', 'products', array(
84
			'methods'             => WP_REST_Server::READABLE,
85
			'callback'            => __CLASS__ . '::get_products',
86
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
87
		) );
88
89
		register_rest_route( 'jetpack/v4', 'marketing/survey', array(
90
			'methods'             => WP_REST_Server::CREATABLE,
91
			'callback'            => __CLASS__ . '::submit_survey',
92
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
93
		) );
94
95
		register_rest_route( 'jetpack/v4', '/jitm', array(
96
			'methods'             => WP_REST_Server::READABLE,
97
			'callback'            => __CLASS__ . '::get_jitm_message',
98
			'permission_callback' => '__return_true',
99
		) );
100
101
		register_rest_route( 'jetpack/v4', '/jitm', array(
102
			'methods'             => WP_REST_Server::CREATABLE,
103
			'callback'            => __CLASS__ . '::delete_jitm_message',
104
			'permission_callback' => __CLASS__ . '::delete_jitm_message_permission_callback',
105
		) );
106
107
		// Test current connection status of Jetpack
108
		register_rest_route( 'jetpack/v4', '/connection/test', array(
109
			'methods'             => WP_REST_Server::READABLE,
110
			'callback'            => __CLASS__ . '::jetpack_connection_test',
111
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
112
		) );
113
114
		// Endpoint specific for privileged servers to request detailed debug information.
115
		register_rest_route( 'jetpack/v4', '/connection/test-wpcom/', array(
116
			'methods'             => WP_REST_Server::READABLE,
117
			'callback'            => __CLASS__ . '::jetpack_connection_test_for_external',
118
			'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check',
119
		) );
120
121
		register_rest_route( 'jetpack/v4', '/rewind', array(
122
			'methods'             => WP_REST_Server::READABLE,
123
			'callback'            => __CLASS__ . '::get_rewind_data',
124
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
125
		) );
126
127
		register_rest_route(
128
			'jetpack/v4',
129
			'/scan',
130
			array(
131
				'methods'             => WP_REST_Server::READABLE,
132
				'callback'            => __CLASS__ . '::get_scan_state',
133
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
134
			)
135
		);
136
137
		// Fetches a fresh connect URL
138
		register_rest_route( 'jetpack/v4', '/connection/url', array(
139
			'methods' => WP_REST_Server::READABLE,
140
			'callback' => __CLASS__ . '::build_connect_url',
141
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
142
			'args'                => array(
143
				'from'     => array( 'type' => 'string' ),
144
				'redirect' => array( 'type' => 'string' ),
145
			),
146
		) );
147
148
		// Get current user connection data
149
		register_rest_route( 'jetpack/v4', '/connection/data', array(
150
			'methods' => WP_REST_Server::READABLE,
151
			'callback' => __CLASS__ . '::get_user_connection_data',
152
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
153
		) );
154
155
		// Start the connection process by registering the site on WordPress.com servers.
156
		register_rest_route( 'jetpack/v4', '/connection/register', array(
157
			'methods'             => WP_REST_Server::EDITABLE,
158
			'callback'            => __CLASS__ . '::register_site',
159
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
160
			'args'                => array(
161
				'registration_nonce' => array( 'type' => 'string' ),
162
			),
163
		) );
164
165
		// Set the connection owner
166
		register_rest_route( 'jetpack/v4', '/connection/owner', array(
167
			'methods' => WP_REST_Server::EDITABLE,
168
			'callback' => __CLASS__ . '::set_connection_owner',
169
			'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback',
170
		) );
171
172
		// Current user: get or set tracking settings.
173
		register_rest_route( 'jetpack/v4', '/tracking/settings', array(
174
			array(
175
				'methods'             => WP_REST_Server::READABLE,
176
				'callback'            => __CLASS__ . '::get_user_tracking_settings',
177
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
178
			),
179
			array(
180
				'methods'             => WP_REST_Server::EDITABLE,
181
				'callback'            => __CLASS__ . '::update_user_tracking_settings',
182
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
183
				'args'                => array(
184
					'tracks_opt_out' => array( 'type' => 'boolean' ),
185
				),
186
			),
187
		) );
188
189
		// Disconnect site from WordPress.com servers
190
		register_rest_route( 'jetpack/v4', '/connection', array(
191
			'methods' => WP_REST_Server::EDITABLE,
192
			'callback' => __CLASS__ . '::disconnect_site',
193
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
194
		) );
195
196
		// Disconnect/unlink user from WordPress.com servers
197
		register_rest_route( 'jetpack/v4', '/connection/user', array(
198
			'methods' => WP_REST_Server::EDITABLE,
199
			'callback' => __CLASS__ . '::unlink_user',
200
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
201
		) );
202
203
		// Get current site data
204
		register_rest_route( 'jetpack/v4', '/site', array(
205
			'methods' => WP_REST_Server::READABLE,
206
			'callback' => __CLASS__ . '::get_site_data',
207
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
208
		) );
209
210
		// Get current site data
211
		register_rest_route( 'jetpack/v4', '/site/features', array(
212
			'methods' => WP_REST_Server::READABLE,
213
			'callback' => array( $site_endpoint, 'get_features' ),
214
			'permission_callback' => array( $site_endpoint , 'can_request' ),
215
		) );
216
217
		register_rest_route(
218
			'jetpack/v4',
219
			'/site/products',
220
			array(
221
				'methods'             => WP_REST_Server::READABLE,
222
				'callback'            => array( $site_endpoint, 'get_products' ),
223
				'permission_callback' => array( $site_endpoint, 'can_request' ),
224
			)
225
		);
226
227
		// Get current site purchases.
228
		register_rest_route(
229
			'jetpack/v4',
230
			'/site/purchases',
231
			array(
232
				'methods'             => WP_REST_Server::READABLE,
233
				'callback'            => array( $site_endpoint, 'get_purchases' ),
234
				'permission_callback' => array( $site_endpoint, 'can_request' ),
235
			)
236
		);
237
238
		// Get current site benefits
239
		register_rest_route( 'jetpack/v4', '/site/benefits', array(
240
			'methods'             => WP_REST_Server::READABLE,
241
			'callback'            => array( $site_endpoint, 'get_benefits' ),
242
			'permission_callback' => array( $site_endpoint, 'can_request' ),
243
		) );
244
245
		// Get Activity Log data for this site.
246
		register_rest_route( 'jetpack/v4', '/site/activity', array(
247
			'methods' => WP_REST_Server::READABLE,
248
			'callback' => __CLASS__ . '::get_site_activity',
249
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
250
		) );
251
252
		// Confirm that a site in identity crisis should be in staging mode
253
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
254
			'methods' => WP_REST_Server::EDITABLE,
255
			'callback' => __CLASS__ . '::confirm_safe_mode',
256
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
257
		) );
258
259
		// IDC resolve: create an entirely new shadow site for this URL.
260
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
261
			'methods' => WP_REST_Server::EDITABLE,
262
			'callback' => __CLASS__ . '::start_fresh_connection',
263
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
264
		) );
265
266
		// Handles the request to migrate stats and subscribers during an identity crisis.
267
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
268
			'methods' => WP_REST_Server::EDITABLE,
269
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
270
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
271
		) );
272
273
		// Return all modules
274
		register_rest_route( 'jetpack/v4', '/module/all', array(
275
			'methods' => WP_REST_Server::READABLE,
276
			'callback' => array( $module_list_endpoint, 'process' ),
277
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
278
		) );
279
280
		// Activate many modules
281
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
282
			'methods' => WP_REST_Server::EDITABLE,
283
			'callback' => array( $module_list_endpoint, 'process' ),
284
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
285
			'args' => array(
286
				'modules' => array(
287
					'default'           => '',
288
					'type'              => 'array',
289
					'items'             => array(
290
						'type'          => 'string',
291
					),
292
					'required'          => true,
293
					'validate_callback' => __CLASS__ . '::validate_module_list',
294
				),
295
				'active' => array(
296
					'default'           => true,
297
					'type'              => 'boolean',
298
					'required'          => false,
299
					'validate_callback' => __CLASS__ . '::validate_boolean',
300
				),
301
			)
302
		) );
303
304
		// Return a single module and update it when needed
305
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
306
			'methods' => WP_REST_Server::READABLE,
307
			'callback' => array( $core_api_endpoint, 'process' ),
308
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
309
		) );
310
311
		// Activate and deactivate a module
312
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
313
			'methods' => WP_REST_Server::EDITABLE,
314
			'callback' => array( $module_toggle_endpoint, 'process' ),
315
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
316
			'args' => array(
317
				'active' => array(
318
					'default'           => true,
319
					'type'              => 'boolean',
320
					'required'          => true,
321
					'validate_callback' => __CLASS__ . '::validate_boolean',
322
				),
323
			)
324
		) );
325
326
		// Update a module
327
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
328
			'methods' => WP_REST_Server::EDITABLE,
329
			'callback' => array( $core_api_endpoint, 'process' ),
330
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
331
			'args' => self::get_updateable_parameters( 'any' )
332
		) );
333
334
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
335
		// Akismet spam count, etc.
336
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
337
			'methods' => WP_REST_Server::READABLE,
338
			'callback' => array( $module_data_endpoint, 'process' ),
339
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
340
			'args' => array(
341
				'range' => array(
342
					'default'           => 'day',
343
					'type'              => 'string',
344
					'required'          => false,
345
					'validate_callback' => __CLASS__ . '::validate_string',
346
				),
347
			)
348
		) );
349
350
		// Check if the API key for a specific service is valid or not
351
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
352
			'methods' => WP_REST_Server::READABLE,
353
			'callback' => array( $module_data_endpoint, 'key_check' ),
354
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
355
			'sanitize_callback' => 'sanitize_text_field',
356
		) );
357
358
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
359
			'methods' => WP_REST_Server::EDITABLE,
360
			'callback' => array( $module_data_endpoint, 'key_check' ),
361
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
362
			'sanitize_callback' => 'sanitize_text_field',
363
			'args' => array(
364
				'api_key' => array(
365
					'default'           => '',
366
					'type'              => 'string',
367
					'validate_callback' => __CLASS__ . '::validate_alphanum',
368
				),
369
			)
370
		) );
371
372
		// Update any Jetpack module option or setting
373
		register_rest_route( 'jetpack/v4', '/settings', array(
374
			'methods' => WP_REST_Server::EDITABLE,
375
			'callback' => array( $core_api_endpoint, 'process' ),
376
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
377
			'args' => self::get_updateable_parameters( 'any' )
378
		) );
379
380
		// Update a module
381
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
382
			'methods' => WP_REST_Server::EDITABLE,
383
			'callback' => array( $core_api_endpoint, 'process' ),
384
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
385
			'args' => self::get_updateable_parameters()
386
		) );
387
388
		// Return all module settings
389
		register_rest_route( 'jetpack/v4', '/settings/', array(
390
			'methods' => WP_REST_Server::READABLE,
391
			'callback' => array( $core_api_endpoint, 'process' ),
392
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
393
		) );
394
395
		// Reset all Jetpack options
396
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
397
			'methods' => WP_REST_Server::EDITABLE,
398
			'callback' => __CLASS__ . '::reset_jetpack_options',
399
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
400
		) );
401
402
		// Updates: get number of plugin updates available
403
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
404
			'methods' => WP_REST_Server::READABLE,
405
			'callback' => __CLASS__ . '::get_plugin_update_count',
406
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
407
		) );
408
409
		// Dismiss Jetpack Notices
410
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
411
			'methods' => WP_REST_Server::EDITABLE,
412
			'callback' => __CLASS__ . '::dismiss_notice',
413
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
414
		) );
415
416
		// Plugins: get list of all plugins.
417
		register_rest_route( 'jetpack/v4', '/plugins', array(
418
			'methods' => WP_REST_Server::READABLE,
419
			'callback' => __CLASS__ . '::get_plugins',
420
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
421
		) );
422
423
		register_rest_route( 'jetpack/v4', '/plugins/akismet/activate', array(
424
			'methods' => WP_REST_Server::EDITABLE,
425
			'callback' => __CLASS__ . '::activate_akismet',
426
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
427
		) );
428
429
		// Plugins: check if the plugin is active.
430
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
431
			'methods' => WP_REST_Server::READABLE,
432
			'callback' => __CLASS__ . '::get_plugin',
433
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
434
		) );
435
436
		// Widgets: get information about a widget that supports it.
437
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
438
			'methods' => WP_REST_Server::READABLE,
439
			'callback' => array( $widget_endpoint, 'process' ),
440
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
441
		) );
442
443
		// Site Verify: check if the site is verified, and a get verification token if not
444
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
445
			'methods' => WP_REST_Server::READABLE,
446
			'callback' => __CLASS__ . '::is_site_verified_and_token',
447
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
448
		) );
449
450
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
451
			'methods' => WP_REST_Server::READABLE,
452
			'callback' => __CLASS__ . '::is_site_verified_and_token',
453
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
454
		) );
455
456
		// Site Verify: tell a service to verify the site
457
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
458
			'methods' => WP_REST_Server::EDITABLE,
459
			'callback' => __CLASS__ . '::verify_site',
460
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
461
			'args' => array(
462
				'keyring_id' => array(
463
					'required'          => true,
464
					'type'              => 'integer',
465
					'validate_callback' => __CLASS__  . '::validate_posint',
466
				),
467
			)
468
		) );
469
470
		// Get and set API keys.
471
		// Note: permission_callback intentionally omitted from the GET method.
472
		// Map block requires open access to API keys on the front end.
473
		register_rest_route(
474
			'jetpack/v4',
475
			'/service-api-keys/(?P<service>[a-z\-_]+)',
476
			array(
477
				array(
478
					'methods'             => WP_REST_Server::READABLE,
479
					'callback'            => __CLASS__ . '::get_service_api_key',
480
					'permission_callback' => '__return_true',
481
				),
482
				array(
483
					'methods'             => WP_REST_Server::EDITABLE,
484
					'callback'            => __CLASS__ . '::update_service_api_key',
485
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
486
					'args'                => array(
487
						'service_api_key' => array(
488
							'required' => true,
489
							'type'     => 'text',
490
						),
491
					),
492
				),
493
				array(
494
					'methods'             => WP_REST_Server::DELETABLE,
495
					'callback'            => __CLASS__ . '::delete_service_api_key',
496
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
497
				),
498
			)
499
		);
500
501
		register_rest_route(
502
			'jetpack/v4',
503
			'/mobile/send-login-email',
504
			array(
505
				'methods'             => WP_REST_Server::EDITABLE,
506
				'callback'            => __CLASS__ . '::send_mobile_magic_link',
507
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
508
			)
509
		);
510
511
		/*
512
		 * Get and update settings from the Jetpack wizard.
513
		 */
514
		register_rest_route(
515
			'jetpack/v4',
516
			'/setup/questionnaire',
517
			array(
518
				array(
519
					'methods'             => WP_REST_Server::READABLE,
520
					'callback'            => __CLASS__ . '::get_setup_wizard_questionnaire',
521
					'permission_callback' => __CLASS__ . '::update_settings_permission_check',
522
				),
523
				array(
524
					'methods'             => WP_REST_Server::EDITABLE,
525
					'callback'            => __CLASS__ . '::update_setup_wizard_questionnaire',
526
					'permission_callback' => __CLASS__ . '::update_settings_permission_check',
527
					'args'                => array(
528
						'questionnaire' => array(
529
							'required'          => false,
530
							'type'              => 'object',
531
							'validate_callback' => __CLASS__ . '::validate_setup_wizard_questionnaire',
532
						),
533
						'status'        => array(
534
							'required'          => false,
535
							'type'              => 'string',
536
							'validate_callback' => __CLASS__ . '::validate_string',
537
						),
538
					),
539
				),
540
			)
541
		);
542
	}
543
544
	/**
545
	 * Get the settings for the wizard questionnaire
546
	 *
547
	 * @return array Questionnaire settings.
548
	 */
549
	public static function get_setup_wizard_questionnaire() {
550
		return Jetpack_Options::get_option( 'setup_wizard_questionnaire', (object) array() );
551
	}
552
553
	/**
554
	 * Update the settings selected on the wizard questionnaire
555
	 *
556
	 * @param WP_REST_Request $request The request.
557
	 *
558
	 * @return bool true.
559
	 */
560
	public static function update_setup_wizard_questionnaire( $request ) {
561
		$questionnaire = $request['questionnaire'];
562
		if ( ! empty( $questionnaire ) ) {
563
			Jetpack_Options::update_option( 'setup_wizard_questionnaire', $questionnaire );
564
		}
565
566
		$status = $request['status'];
567
		if ( ! empty( $status ) ) {
568
			Jetpack_Options::update_option( 'setup_wizard_status', $status );
569
		}
570
571
		return true;
572
	}
573
574
	/**
575
	 * Validate the answers on the setup wizard questionnaire
576
	 *
577
	 * @param array           $value Value to check received by request.
578
	 * @param WP_REST_Request $request The request sent to the WP REST API.
579
	 * @param string          $param Name of the parameter passed to endpoint holding $value.
580
	 *
581
	 * @return bool|WP_Error
582
	 */
583
	public static function validate_setup_wizard_questionnaire( $value, $request, $param ) {
584 View Code Duplication
		if ( ! is_array( $value ) ) {
585
			/* translators: Name of a parameter that must be an object */
586
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an object.', 'jetpack' ), $param ) );
587
		}
588
589
		foreach ( $value as $answer_key => $answer ) {
590
			if ( is_string( $answer ) ) {
591
				$validate = self::validate_string( $answer, $request, $param );
592
			} else {
593
				$validate = self::validate_boolean( $answer, $request, $param );
594
			}
595
596
			if ( is_wp_error( $validate ) ) {
597
				return $validate;
598
			}
599
		}
600
601
		return true;
602
	}
603
604
	public static function get_plans( $request ) {
605
		$request = Client::wpcom_json_api_request_as_user(
606
			'/plans?_locale=' . get_user_locale(),
607
			'2',
608
			array(
609
				'method'  => 'GET',
610
				'headers' => array(
611
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
612
				),
613
			)
614
		);
615
616
		$body = json_decode( wp_remote_retrieve_body( $request ) );
617
		if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
618
			$data = $body;
619
		} else {
620
			// something went wrong so we'll just return the response without caching
621
			return $body;
622
		}
623
624
		return $data;
625
	}
626
627
	/**
628
	 * Gets the WP.com products that are in use on wpcom.
629
	 * Similar to the WP.com plans that we currently in user on WPCOM.
630
	 *
631
	 * @param WP_REST_Request $request The request.
632
	 *
633
	 * @return string|WP_Error A JSON object of wpcom products if the request was successful, or a WP_Error otherwise.
634
	 */
635
	public static function get_products( $request ) {
636
		$wpcom_request = Client::wpcom_json_api_request_as_user(
637
			'/products?_locale=' . get_user_locale() . '&type=jetpack',
638
			'2',
639
			array(
640
				'method'  => 'GET',
641
				'headers' => array(
642
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
643
				),
644
			)
645
		);
646
647
		$response_code = wp_remote_retrieve_response_code( $wpcom_request );
648
		if ( 200 === $response_code ) {
649
			return json_decode( wp_remote_retrieve_body( $wpcom_request ) );
650
		} else {
651
			// Something went wrong so we'll just return the response without caching.
652
			return new WP_Error(
653
				'failed_to_fetch_data',
654
				esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
655
				array( 'status' => $response_code )
656
			);
657
		}
658
	}
659
660
	public static function submit_survey( $request ) {
661
662
		$wpcom_request = Client::wpcom_json_api_request_as_user(
663
			'/marketing/survey',
664
			'v2',
665
			array(
666
				'method'  => 'POST',
667
				'headers' => array(
668
					'Content-Type'    => 'application/json',
669
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
670
				),
671
			),
672
			$request->get_json_params()
673
		);
674
675
		$wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
676
		if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) {
677
			$data = $wpcom_request_body;
678
		} else {
679
			// something went wrong so we'll just return the response without caching
680
			return $wpcom_request_body;
681
		}
682
683
		return $data;
684
	}
685
686
	/**
687
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
688
	 *
689
	 * @param $request WP_REST_Request
690
	 *
691
	 * @return array An array of jitms
692
	 */
693
	public static function get_jitm_message( $request ) {
694
		$jitm = JITM::get_instance();
695
696
		if ( ! $jitm->register() ) {
697
			return array();
698
		}
699
700
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ), 'true' === $request['full_jp_logo_exists'] ? true : false );
701
	}
702
703
	/**
704
	 * Dismisses a jitm.
705
	 *
706
	 * @param WP_REST_Request $request The request.
707
	 *
708
	 * @return bool Always True
709
	 */
710
	public static function delete_jitm_message( $request ) {
711
		$jitm = JITM::get_instance();
712
713
		if ( ! $jitm->register() ) {
714
			return true;
715
		}
716
717
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
718
	}
719
720
	/**
721
	 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
722
	 *  keyring to use to get the token if it is not
723
	 *
724
	 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
725
	 *
726
	 * @since 6.6.0
727
	 *
728
	 * @param WP_REST_Request $request The request sent to the WP REST API.
729
	 *
730
	 * @return array|wp-error
731
	 */
732
	public static function is_site_verified_and_token( $request ) {
733
		/**
734
		 * Return an error if the site uses a Maintenance / Coming Soon plugin
735
		 * and if the plugin is configured to make the site private.
736
		 *
737
		 * We currently handle the following plugins:
738
		 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
739
		 * - https://wordpress.org/plugins/mojo-under-construction
740
		 * - https://wordpress.org/plugins/under-construction-page
741
		 * - https://wordpress.org/plugins/ultimate-under-construction
742
		 * - https://wordpress.org/plugins/coming-soon
743
		 *
744
		 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
745
		 * If the filter returns true, we will consider the site as under construction.
746
		 */
747
		$mm_coming_soon                       = get_option( 'mm_coming_soon', null );
748
		$under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
749
		$ucp_options                          = get_option( 'ucp_options', array() );
750
		$uuc_settings                         = get_option( 'uuc_settings', array() );
751
		$csp4                                 = get_option( 'seed_csp4_settings_content', array() );
752
		if (
753
			( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
754
			|| Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
755
			|| ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
756
			|| ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
757
			|| ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) &&  isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
758
			/**
759
			 * Allow plugins to mark a site as "under construction".
760
			 *
761
			 * @since 6.7.0
762
			 *
763
			 * @param false bool Is the site under construction? Default to false.
764
			 */
765
			|| true === apply_filters( 'jetpack_is_under_construction_plugin', false )
766
		) {
767
			return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
768
		}
769
770
 		$xml = new Jetpack_IXR_Client( array(
771
 			'user_id' => get_current_user_id(),
772
		) );
773
774
		$args = array(
775
			'user_id' => get_current_user_id(),
776
			'service' => $request[ 'service' ],
777
		);
778
779
		if ( isset( $request[ 'keyring_id' ] ) ) {
780
			$args[ 'keyring_id' ] = $request[ 'keyring_id' ];
781
		}
782
783
		$xml->query( 'jetpack.isSiteVerified', $args );
784
785
		if ( $xml->isError() ) {
786
			return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
787
		} else {
788
			return $xml->getResponse();
789
		}
790
	}
791
792
793
794
	public static function verify_site( $request ) {
795
		$xml = new Jetpack_IXR_Client( array(
796
			'user_id' => get_current_user_id(),
797
		) );
798
799
		$params = $request->get_json_params();
800
801
		$xml->query( 'jetpack.verifySite', array(
802
				'user_id' => get_current_user_id(),
803
				'service' => $request[ 'service' ],
804
				'keyring_id' => $params[ 'keyring_id' ],
805
			)
806
		);
807
808
		if ( $xml->isError() ) {
809
			return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
810
		} else {
811
			$response = $xml->getResponse();
812
813
			if ( ! empty( $response['errors'] ) ) {
814
				$error = new WP_Error;
815
				$error->errors = $response['errors'];
816
				return $error;
817
			}
818
819
			return $response;
820
		}
821
	}
822
823
	/**
824
	 * Handles verification that a site is registered
825
	 *
826
	 * @since 5.4.0
827
	 * @deprecated 8.8.0 The method is moved to the `REST_Connector` class.
828
	 *
829
	 * @param WP_REST_Request $request The request sent to the WP REST API.
830
	 *
831
	 * @return array|wp-error
832
	 */
833
	public static function remote_authorize( $request ) {
834
		_deprecated_function( __METHOD__, 'jetpack-8.8.0', '\Automattic\Jetpack\Connection\REST_Connector::remote_authorize' );
835
		return REST_Connector::remote_authorize( $request );
836
	}
837
838
	/**
839
	 * Handles dismissing of Jetpack Notices
840
	 *
841
	 * @since 4.3.0
842
	 *
843
	 * @param WP_REST_Request $request The request sent to the WP REST API.
844
	 *
845
	 * @return array|wp-error
846
	 */
847
	public static function dismiss_notice( $request ) {
848
		$notice = $request['notice'];
849
850
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
851
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
852
		}
853
854
		if ( isset( $notice ) && ! empty( $notice ) ) {
855
			switch( $notice ) {
856
				case 'feedback_dash_request':
857
				case 'welcome':
858
					$notices = get_option( 'jetpack_dismissed_notices', array() );
859
					$notices[ $notice ] = true;
860
					update_option( 'jetpack_dismissed_notices', $notices );
861
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
862
863
				default:
864
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
865
			}
866
		}
867
868
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
869
	}
870
871
	/**
872
	 * Verify that the user can disconnect the site.
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 disconnect_site_permission_callback() {
879
		if ( current_user_can( 'jetpack_disconnect' ) ) {
880
			return true;
881
		}
882
883
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
884
885
	}
886
887
	/**
888
	 * Verify that the user can dismiss JITM messages.
889
	 *
890
	 * @since 8.8.0
891
	 *
892
	 * @return bool|WP_Error True if user is able to dismiss JITM messages.
893
	 */
894
	public static function delete_jitm_message_permission_callback() {
895
		if ( current_user_can( 'read' ) ) {
896
			return true;
897
		}
898
899
		return new WP_Error( 'invalid_user_permission_jetpack_delete_jitm_message', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'invalid_user_permission...ck_delete_jitm_message'.

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...
Deprecated Code introduced by
The method Jetpack_Core_Json_Api_En...ization_required_code() has been deprecated with message: since version 8.8.0. Taken from rest_authorization_required_code() in WP-API plugin until is added to core.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
900
	}
901
902
	/**
903
	 * Verify that the user can get a connect/link URL
904
	 *
905
	 * @since 4.3.0
906
	 *
907
	 * @return bool|WP_Error True if user is able to disconnect the site.
908
	 */
909 View Code Duplication
	public static function connect_url_permission_callback() {
910
		if ( current_user_can( 'jetpack_connect_user' ) ) {
911
			return true;
912
		}
913
914
		return new WP_Error( 'invalid_user_permission_jetpack_connect', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
915
916
	}
917
918
	/**
919
	 * Verify that a user can get the data about the current user.
920
	 * Only those who can connect.
921
	 *
922
	 * @since 4.3.0
923
	 *
924
	 * @uses Jetpack::is_user_connected();
925
	 *
926
	 * @return bool|WP_Error True if user is able to unlink.
927
	 */
928 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
929
		if ( current_user_can( 'jetpack_connect_user' ) ) {
930
			return true;
931
		}
932
933
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
934
	}
935
936
	/**
937
	 * Check that user has permission to change the master user.
938
	 *
939
	 * @since 6.2.0
940
	 * @since 7.7.0 Update so that any user with jetpack_disconnect privs can set owner.
941
	 *
942
	 * @return bool|WP_Error True if user is able to change master user.
943
	 */
944 View Code Duplication
	public static function set_connection_owner_permission_callback() {
945
		if ( current_user_can( 'jetpack_disconnect' ) ) {
946
			return true;
947
		}
948
949
		return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
950
	}
951
952
	/**
953
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
954
	 *
955
	 * @since 4.3.0
956
	 *
957
	 * @uses Jetpack::is_user_connected();
958
	 *
959
	 * @return bool|WP_Error True if user is able to unlink.
960
	 */
961
	public static function unlink_user_permission_callback() {
962
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
963
			return true;
964
		}
965
966
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
967
	}
968
969
	/**
970
	 * Verify that user can manage Jetpack modules.
971
	 *
972
	 * @since 4.3.0
973
	 *
974
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
975
	 */
976
	public static function manage_modules_permission_check() {
977
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
978
			return true;
979
		}
980
981
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
982
	}
983
984
	/**
985
	 * Verify that user can update Jetpack modules.
986
	 *
987
	 * @since 4.3.0
988
	 *
989
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
990
	 */
991 View Code Duplication
	public static function configure_modules_permission_check() {
992
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
993
			return true;
994
		}
995
996
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
997
	}
998
999
	/**
1000
	 * Verify that user can view Jetpack admin page.
1001
	 *
1002
	 * @since 4.3.0
1003
	 *
1004
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
1005
	 */
1006
	public static function view_admin_page_permission_check() {
1007
		if ( current_user_can( 'jetpack_admin_page' ) ) {
1008
			return true;
1009
		}
1010
1011
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
1012
	}
1013
1014
	/**
1015
	 * Verify that user can mitigate an identity crisis.
1016
	 *
1017
	 * @since 4.4.0
1018
	 *
1019
	 * @return bool Whether user has capability 'jetpack_disconnect'.
1020
	 */
1021 View Code Duplication
	public static function identity_crisis_mitigation_permission_check() {
1022
		if ( current_user_can( 'jetpack_disconnect' ) ) {
1023
			return true;
1024
		}
1025
1026
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
1027
	}
1028
1029
	/**
1030
	 * Verify that user can update Jetpack general settings.
1031
	 *
1032
	 * @since 4.3.0
1033
	 *
1034
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
1035
	 */
1036 View Code Duplication
	public static function update_settings_permission_check() {
1037
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
1038
			return true;
1039
		}
1040
1041
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
1042
	}
1043
1044
	/**
1045
	 * Verify that user can view Jetpack admin page and can activate plugins.
1046
	 *
1047
	 * @since 4.3.0
1048
	 *
1049
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
1050
	 */
1051
	public static function activate_plugins_permission_check() {
1052
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
1053
			return true;
1054
		}
1055
1056
		return new WP_Error( 'invalid_user_permission_activate_plugins', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
1057
	}
1058
1059
	/**
1060
	 * Verify that user can edit other's posts (Editors and Administrators).
1061
	 *
1062
	 * @return bool Whether user has the capability 'edit_others_posts'.
1063
	 */
1064
	public static function edit_others_posts_check() {
1065
		if ( current_user_can( 'edit_others_posts' ) ) {
1066
			return true;
1067
		}
1068
1069
		return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Deprecated Code introduced by
The property Jetpack_Core_Json_Api_En...r_permissions_error_msg has been deprecated with message: 8.8.0 Use `REST_Connector::get_user_permissions_error_msg()` instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

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...
2932
		}
2933
		return true;
2934
	}
2935
2936
	/**
2937
	 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
2938
	 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
2939
	 *
2940
	 * @since 4.3.0
2941
	 *
2942
	 * @param string|bool $value Value to check.
2943
	 *
2944
	 * @return bool|array
2945
	 */
2946
	public static function sanitize_stats_allowed_roles( $value ) {
2947
		if ( empty( $value ) ) {
2948
			return array( 'administrator' );
2949
		}
2950
		return $value;
2951
	}
2952
2953
	/**
2954
	 * Get the currently accessed route and return the module slug in it.
2955
	 *
2956
	 * @since 4.3.0
2957
	 *
2958
	 * @param string $route Regular expression for the endpoint with the module slug to return.
2959
	 *
2960
	 * @return array|string
2961
	 */
2962
	public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
2963
2964
		if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
2965
			return '';
2966
		}
2967
2968
		preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
2969
2970
		if ( empty( $module['slug'] ) ) {
2971
			return '';
2972
		}
2973
2974
		return $module['slug'];
2975
	}
2976
2977
	/**
2978
	 * Adds extra information for modules.
2979
	 *
2980
	 * @since 4.3.0
2981
	 *
2982
	 * @param string|array $modules Can be a single module or a list of modules.
2983
	 * @param null|string  $slug    Slug of the module in the first parameter.
2984
	 *
2985
	 * @return array|string
2986
	 */
2987
	public static function prepare_modules_for_response( $modules = '', $slug = null ) {
2988
		global $wp_rewrite;
2989
2990
		/** This filter is documented in modules/sitemaps/sitemaps.php */
2991
		$location = apply_filters( 'jetpack_sitemap_location', '' );
2992
2993
		if ( $wp_rewrite->using_index_permalinks() ) {
2994
			$sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
2995
			$news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
2996
		} else if ( $wp_rewrite->using_permalinks() ) {
2997
			$sitemap_url = home_url( $location . '/sitemap.xml' );
2998
			$news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
2999
		} else {
3000
			$sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
3001
			$news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
3002
		}
3003
3004
		if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) {
3005
			// Is a list of modules
3006
			$modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
3007
			$modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
3008
		} elseif ( 'sitemaps' == $slug ) {
3009
			// It's a single module
3010
			$modules['extra']['sitemap_url'] = $sitemap_url;
3011
			$modules['extra']['news_sitemap_url'] = $news_sitemap_url;
3012
		}
3013
		return $modules;
3014
	}
3015
3016
	/**
3017
	 * Remove 'validate_callback' item from options available for module.
3018
	 * Fetch current option value and add to array of module options.
3019
	 * Prepare values of module options that need special handling, like those saved in wpcom.
3020
	 *
3021
	 * @since 4.3.0
3022
	 *
3023
	 * @param string $module Module slug.
3024
	 * @return array
3025
	 */
3026
	public static function prepare_options_for_response( $module = '' ) {
3027
		$options = self::get_updateable_data_list( $module );
3028
3029
		if ( ! is_array( $options ) || empty( $options ) ) {
3030
			return $options;
3031
		}
3032
3033
		// Some modules need special treatment.
3034
		switch ( $module ) {
3035
3036
			case 'monitor':
3037
				// Status of user notifications
3038
				$options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
3039
				break;
3040
3041
			case 'post-by-email':
3042
				// Email address
3043
				$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'] );
3044
				break;
3045
3046
			case 'protect':
3047
				// Protect
3048
				$options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
3049
				if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) {
3050
					include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' );
3051
				}
3052
				$options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist();
3053
				break;
3054
3055
			case 'related-posts':
3056
				// It's local, but it must be broken apart since it's saved as an array.
3057
				$options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
3058
				break;
3059
3060
			case 'verification-tools':
3061
				// It's local, but it must be broken apart since it's saved as an array.
3062
				$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
3063
				break;
3064
3065
			case 'google-analytics':
3066
				$wga = get_option( 'jetpack_wga' );
3067
				$code = '';
3068
				if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
3069
					 $code = $wga[ 'code' ];
3070
				}
3071
				$options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
3072
				break;
3073
3074
			case 'sharedaddy':
3075
				// It's local, but it must be broken apart since it's saved as an array.
3076
				if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
3077
					break;
3078
				}
3079
				$sharer = new Sharing_Service();
3080
				$options = self::split_options( $options, $sharer->get_global_options() );
3081
				$options['sharing_services']['current_value'] = $sharer->get_blog_services();
3082
				$other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
3083 View Code Duplication
				foreach ( $other_sharedaddy_options as $key ) {
3084
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
3085
					$current_value = get_option( $key, $default_value );
3086
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
3087
				}
3088
				break;
3089
3090
			case 'stats':
3091
				// It's local, but it must be broken apart since it's saved as an array.
3092
				if ( ! function_exists( 'stats_get_options' ) ) {
3093
					include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
3094
				}
3095
				$options = self::split_options( $options, stats_get_options() );
3096
				break;
3097
			default:
3098
				// These option are just stored as plain WordPress options.
3099 View Code Duplication
				foreach ( $options as $key => $value ) {
3100
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
3101
					$current_value = get_option( $key, $default_value );
3102
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
3103
				}
3104
		}
3105
		// At this point some options have current_value not set because they're options
3106
		// that only get written on update, so we set current_value to the default one.
3107
		foreach ( $options as $key => $value ) {
3108
			// We don't need validate_callback in the response
3109
			if ( isset( $options[ $key ]['validate_callback'] ) ) {
3110
				unset( $options[ $key ]['validate_callback'] );
3111
			}
3112
			$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
3113
			if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
3114
				$options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
3115
			}
3116
		}
3117
		return $options;
3118
	}
3119
3120
	/**
3121
	 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
3122
	 *
3123
	 * @since 4.3.0
3124
	 *
3125
	 * @param array  $separate_options Array of options admitted by the module.
3126
	 * @param array  $grouped_options Option saved as array to be splitted.
3127
	 * @param string $prefix Optional prefix for the separate option keys.
3128
	 *
3129
	 * @return array
3130
	 */
3131
	public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
3132
		if ( is_array( $grouped_options ) ) {
3133
			foreach ( $grouped_options as $key => $value ) {
3134
				$option_key = $prefix . $key;
3135
				if ( isset( $separate_options[ $option_key ] ) ) {
3136
					$separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
3137
				}
3138
			}
3139
		}
3140
		return $separate_options;
3141
	}
3142
3143
	/**
3144
	 * Perform a casting to the value specified in the option definition.
3145
	 *
3146
	 * @since 4.3.0
3147
	 *
3148
	 * @param mixed $value Value to cast to the proper type.
3149
	 * @param array $definition Type to cast the value to.
3150
	 *
3151
	 * @return bool|float|int|string
3152
	 */
3153
	public static function cast_value( $value, $definition ) {
3154
		if ( $value === 'NULL' ) {
3155
			return null;
3156
		}
3157
3158
		if ( isset( $definition['type'] ) ) {
3159
			switch ( $definition['type'] ) {
3160
				case 'boolean':
3161
					if ( 'true' === $value || 'on' === $value ) {
3162
						return true;
3163
					} elseif ( 'false' === $value || 'off' === $value ) {
3164
						return false;
3165
					}
3166
					return (bool) $value;
3167
					break;
3168
3169
				case 'integer':
3170
					return (int) $value;
3171
					break;
3172
3173
				case 'float':
3174
					return (float) $value;
3175
					break;
3176
3177
				case 'string':
3178
					return (string) $value;
3179
					break;
3180
			}
3181
		}
3182
		return $value;
3183
	}
3184
3185
	/**
3186
	 * Get a value not saved locally.
3187
	 *
3188
	 * @since 4.3.0
3189
	 *
3190
	 * @param string $module Module slug.
3191
	 * @param string $option Option name.
3192
	 *
3193
	 * @return bool Whether user is receiving notifications or not.
3194
	 */
3195
	public static function get_remote_value( $module, $option ) {
3196
3197
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
3198
			$option .= get_current_user_id();
3199
		}
3200
3201
		// If option doesn't exist, 'does_not_exist' will be returned.
3202
		$value = get_option( $option, 'does_not_exist' );
3203
3204
		// If option exists, just return it.
3205
		if ( 'does_not_exist' !== $value ) {
3206
			return $value;
3207
		}
3208
3209
		// Only check a remote option if Jetpack is connected.
3210
		if ( ! Jetpack::is_active() ) {
3211
			return false;
3212
		}
3213
3214
		// Do what is necessary for each module.
3215
		switch ( $module ) {
3216 View Code Duplication
			case 'monitor':
3217
				// Load the class to use the method. If class can't be found, do nothing.
3218
				if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
3219
					return false;
3220
				}
3221
				$value = Jetpack_Monitor::user_receives_notifications( false );
3222
				break;
3223
3224 View Code Duplication
			case 'post-by-email':
3225
				// Load the class to use the method. If class can't be found, do nothing.
3226
				if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
3227
					return false;
3228
				}
3229
				$value = Jetpack_Post_By_Email::init()->get_post_by_email_address();
3230
				if ( $value === null ) {
3231
					$value = 'NULL'; // sentinel value so it actually gets set
3232
				}
3233
				break;
3234
		}
3235
3236
		// Normalize value to boolean.
3237
		if ( is_wp_error( $value ) || is_null( $value ) ) {
3238
			$value = false;
3239
		}
3240
3241
		// Save option to use it next time.
3242
		update_option( $option, $value );
3243
3244
		return $value;
3245
	}
3246
3247
	/**
3248
	 * Get number of plugin updates available.
3249
	 *
3250
	 * @since 4.3.0
3251
	 *
3252
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
3253
	 */
3254
	public static function get_plugin_update_count() {
3255
		$updates = wp_get_update_data();
3256
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
3257
			$count = $updates['counts']['plugins'];
3258
			if ( 0 == $count ) {
3259
				$response = array(
3260
					'code'    => 'success',
3261
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
3262
					'count'   => 0,
3263
				);
3264
			} else {
3265
				$response = array(
3266
					'code'    => 'updates-available',
3267
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
3268
					'count'   => $count,
3269
				);
3270
			}
3271
			return rest_ensure_response( $response );
3272
		}
3273
3274
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
3275
	}
3276
3277
3278
	/**
3279
	 * Returns a list of all plugins in the site.
3280
	 *
3281
	 * @since 4.2.0
3282
	 * @uses get_plugins()
3283
	 *
3284
	 * @return array
3285
	 */
3286
	private static function core_get_plugins() {
3287
		if ( ! function_exists( 'get_plugins' ) ) {
3288
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
3289
		}
3290
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
3291
		$plugins = apply_filters( 'all_plugins', get_plugins() );
3292
3293
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
3294
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
3295
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
3296
			}
3297
			return $plugins;
3298
		}
3299
3300
		return array();
3301
	}
3302
3303
	/**
3304
	 * Deprecated - Get third party plugin API keys.
3305
	 * @deprecated
3306
	 *
3307
	 * @param WP_REST_Request $request {
3308
	 *     Array of parameters received by request.
3309
	 *
3310
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3311
	 * }
3312
	 */
3313
	public static function get_service_api_key( $request ) {
3314
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key' );
3315
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key( $request );
3316
	}
3317
3318
	/**
3319
	 * Deprecated - Update third party plugin API keys.
3320
	 * @deprecated
3321
	 *
3322
	 * @param WP_REST_Request $request {
3323
	 *     Array of parameters received by request.
3324
	 *
3325
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3326
	 * }
3327
	 */
3328
	public static function update_service_api_key( $request ) {
3329
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key' );
3330
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key( $request ) ;
3331
	}
3332
3333
	/**
3334
	 * Deprecated - Delete a third party plugin API key.
3335
	 * @deprecated
3336
	 *
3337
	 * @param WP_REST_Request $request {
3338
	 *     Array of parameters received by request.
3339
	 *
3340
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3341
	 * }
3342
	 */
3343
	public static function delete_service_api_key( $request ) {
3344
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key' );
3345
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key( $request );
3346
	}
3347
3348
	/**
3349
	 * Deprecated - Validate the service provided in /service-api-keys/ endpoints.
3350
	 * To add a service to these endpoints, add the service name to $valid_services
3351
	 * and add '{service name}_api_key' to the non-compact return array in get_option_names(),
3352
	 * in class-jetpack-options.php
3353
	 * @deprecated
3354
	 *
3355
	 * @param string $service The service the API key is for.
3356
	 * @return string Returns the service name if valid, null if invalid.
3357
	 */
3358
	public static function validate_service_api_service( $service = null ) {
3359
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service' );
3360
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service( $service );
3361
	}
3362
3363
	/**
3364
	 * Error response for invalid service API key requests with an invalid service.
3365
	 */
3366
	public static function service_api_invalid_service_response() {
3367
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response' );
3368
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response();
3369
	}
3370
3371
	/**
3372
	 * Deprecated - Validate API Key
3373
	 * @deprecated
3374
	 *
3375
	 * @param string $key The API key to be validated.
3376
	 * @param string $service The service the API key is for.
3377
	 *
3378
	 */
3379
	public static function validate_service_api_key( $key = null, $service = null ) {
3380
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' );
3381
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key( $key , $service  );
3382
	}
3383
3384
	/**
3385
	 * Deprecated - Validate Mapbox API key
3386
	 * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php
3387
	 * @deprecated
3388
	 *
3389
	 * @param string $key The API key to be validated.
3390
	 */
3391
	public static function validate_service_api_key_mapbox( $key ) {
3392
		_deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' );
3393
		return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key_mapbox( $key );
3394
3395
	}
3396
3397
	/**
3398
	 * Checks if the queried plugin is active.
3399
	 *
3400
	 * @since 4.2.0
3401
	 * @uses is_plugin_active()
3402
	 *
3403
	 * @return bool
3404
	 */
3405
	private static function core_is_plugin_active( $plugin ) {
3406
		if ( ! function_exists( 'is_plugin_active' ) ) {
3407
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
3408
		}
3409
3410
		return is_plugin_active( $plugin );
3411
	}
3412
3413
	/**
3414
	 * Get plugins data in site.
3415
	 *
3416
	 * @since 4.2.0
3417
	 *
3418
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
3419
	 */
3420
	public static function get_plugins() {
3421
		$plugins = self::core_get_plugins();
3422
3423
		if ( ! empty( $plugins ) ) {
3424
			return rest_ensure_response( $plugins );
3425
		}
3426
3427
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
3428
	}
3429
3430
	/**
3431
	 * Ensures that Akismet is installed and activated.
3432
	 *
3433
	 * @since 7.7
3434
	 *
3435
	 * @return WP_REST_Response A response indicating whether or not the installation was successful.
3436
	 */
3437
	public static function activate_akismet() {
3438
		jetpack_require_lib( 'plugins' );
3439
		$result = Jetpack_Plugins::install_and_activate_plugin('akismet');
3440
3441
		if ( is_wp_error( $result ) ) {
3442
			return rest_ensure_response( array(
3443
				'code'    => 'failure',
3444
				'message' => esc_html__( 'Unable to activate Akismet', 'jetpack' )
3445
			) );
3446
		} else {
3447
			return rest_ensure_response( array(
3448
				'code'    => 'success',
3449
				'message' => esc_html__( 'Activated Akismet', 'jetpack' )
3450
			) );
3451
		}
3452
	}
3453
3454
	/**
3455
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
3456
	 *
3457
	 * @since 4.2.0
3458
	 *
3459
	 * @param WP_REST_Request $request {
3460
	 *     Array of parameters received by request.
3461
	 *
3462
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3463
	 * }
3464
	 *
3465
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
3466
	 */
3467
	public static function get_plugin( $request ) {
3468
3469
		$plugins = self::core_get_plugins();
3470
3471
		if ( empty( $plugins ) ) {
3472
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'no_plugins_found'.

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...
3473
		}
3474
3475
		$plugin = stripslashes( $request['plugin'] );
3476
3477
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
3478
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
3479
		}
3480
3481
		$plugin_data = $plugins[ $plugin ];
3482
3483
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
3484
3485
		return rest_ensure_response( array(
3486
			'code'    => 'success',
3487
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
3488
			'data'    => $plugin_data
3489
		) );
3490
	}
3491
3492
	/**
3493
	 * Proxies a request to WordPress.com to request that a magic link be sent to the current user
3494
	 * to log this user in to the mobile app via email.
3495
	 *
3496
	 * @param WP_REST_REQUEST $request The request parameters.
3497
	 * @return bool|WP_Error
3498
	 */
3499 View Code Duplication
	public static function send_mobile_magic_link( $request ) {
3500
		$xml = new Jetpack_IXR_Client(
3501
			array(
3502
				'user_id' => get_current_user_id(),
3503
			)
3504
		);
3505
3506
		$xml->query( 'jetpack.sendMobileMagicLink', array() );
3507
		if ( $xml->isError() ) {
3508
			return new WP_Error(
3509
				'error_sending_mobile_magic_link',
3510
				sprintf(
3511
					'%s: %s',
3512
					$xml->getErrorCode(),
3513
					$xml->getErrorMessage()
3514
				)
3515
			);
3516
		}
3517
3518
		$response = $xml->getResponse();
3519
3520
		return rest_ensure_response(
3521
			array(
3522
				'code' => 'success',
3523
			)
3524
		);
3525
	}
3526
} // class end
3527