Completed
Push — add/e2e-latest-gutenberg-in-ci ( bfab32...c58b86 )
by Yaroslav
15:57 queued 07:19
created

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

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
use Automattic\Jetpack\Connection\Client;
4
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
5
use Automattic\Jetpack\JITMS\JITM;
6
use Automattic\Jetpack\Tracking;
7
use Automattic\Jetpack\Status;
8
9
/**
10
 * Register WP REST API endpoints for Jetpack.
11
 *
12
 * @author Automattic
13
 */
14
15
/**
16
 * Disable direct access.
17
 */
18
if ( ! defined( 'ABSPATH' ) ) {
19
	exit;
20
}
21
22
// Load WP_Error for error messages.
23
require_once ABSPATH . '/wp-includes/class-wp-error.php';
24
25
// Register endpoints when WP REST API is initialized.
26
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
27
// Load API endpoints that are synced with WP.com
28
// Each of these is a class that will register its own routes on 'rest_api_init'.
29
require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
30
31
/**
32
 * Class Jetpack_Core_Json_Api_Endpoints
33
 *
34
 * @since 4.3.0
35
 */
36
class Jetpack_Core_Json_Api_Endpoints {
37
38
	/**
39
	 * @var string Generic error message when user is not allowed to perform an action.
40
	 */
41
	public static $user_permissions_error_msg;
42
43
	/**
44
	 * @var array Roles that can access Stats once they're granted access.
45
	 */
46
	public static $stats_roles;
47
48
	/**
49
	 * Declare the Jetpack REST API endpoints.
50
	 *
51
	 * @since 4.3.0
52
	 */
53
	public static function register_endpoints() {
54
55
		// Load API endpoint base classes
56
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
57
58
		// Load API endpoints
59
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
60
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
61
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
62
63
		self::$user_permissions_error_msg = esc_html__(
64
			'You do not have the correct user permissions to perform this action.
65
			Please contact your site admin if you think this is a mistake.',
66
			'jetpack'
67
		);
68
69
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
70
71
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
72
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
73
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
74
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
75
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
76
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
77
		$widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
78
79
		register_rest_route( 'jetpack/v4', 'plans', array(
80
			'methods'             => WP_REST_Server::READABLE,
81
			'callback'            => __CLASS__ . '::get_plans',
82
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
83
		) );
84
85
		register_rest_route( 'jetpack/v4', 'products', array(
86
			'methods'             => WP_REST_Server::READABLE,
87
			'callback'            => __CLASS__ . '::get_products',
88
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
89
		) );
90
91
		register_rest_route( 'jetpack/v4', 'marketing/survey', array(
92
			'methods'             => WP_REST_Server::CREATABLE,
93
			'callback'            => __CLASS__ . '::submit_survey',
94
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
95
		) );
96
97
		register_rest_route( 'jetpack/v4', '/jitm', array(
98
			'methods'  => WP_REST_Server::READABLE,
99
			'callback' => __CLASS__ . '::get_jitm_message',
100
		) );
101
102
		register_rest_route( 'jetpack/v4', '/jitm', array(
103
			'methods'  => WP_REST_Server::CREATABLE,
104
			'callback' => __CLASS__ . '::delete_jitm_message'
105
		) );
106
107
		// Authorize a remote user
108
		register_rest_route( 'jetpack/v4', '/remote_authorize', array(
109
			'methods' => WP_REST_Server::EDITABLE,
110
			'callback' => __CLASS__ . '::remote_authorize',
111
		) );
112
113
		// Get current connection status of Jetpack
114
		register_rest_route( 'jetpack/v4', '/connection', array(
115
			'methods' => WP_REST_Server::READABLE,
116
			'callback' => __CLASS__ . '::jetpack_connection_status',
117
		) );
118
119
		// Test current connection status of Jetpack
120
		register_rest_route( 'jetpack/v4', '/connection/test', array(
121
			'methods' => WP_REST_Server::READABLE,
122
			'callback' => __CLASS__ . '::jetpack_connection_test',
123
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
124
		) );
125
126
		// Endpoint specific for privileged servers to request detailed debug information.
127
		register_rest_route( 'jetpack/v4', '/connection/test-wpcom/', array(
128
			'methods' => WP_REST_Server::READABLE,
129
			'callback' => __CLASS__ . '::jetpack_connection_test_for_external',
130
			'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check',
131
		) );
132
133
		register_rest_route( 'jetpack/v4', '/rewind', array(
134
			'methods' => WP_REST_Server::READABLE,
135
			'callback' => __CLASS__ . '::get_rewind_data',
136
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
137
		) );
138
139
		register_rest_route(
140
			'jetpack/v4',
141
			'/scan',
142
			array(
143
				'methods'             => WP_REST_Server::READABLE,
144
				'callback'            => __CLASS__ . '::get_scan_state',
145
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
146
			)
147
		);
148
149
		// Fetches a fresh connect URL
150
		register_rest_route( 'jetpack/v4', '/connection/url', array(
151
			'methods' => WP_REST_Server::READABLE,
152
			'callback' => __CLASS__ . '::build_connect_url',
153
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
154
			'args'                => array(
155
				'from'     => array( 'type' => 'string' ),
156
				'redirect' => array( 'type' => 'string' ),
157
			),
158
		) );
159
160
		// Get current user connection data
161
		register_rest_route( 'jetpack/v4', '/connection/data', array(
162
			'methods' => WP_REST_Server::READABLE,
163
			'callback' => __CLASS__ . '::get_user_connection_data',
164
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
165
		) );
166
167
		// Get list of plugins that use the Jetpack connection.
168
		register_rest_route(
169
			'jetpack/v4',
170
			'/connection/plugins',
171
			array(
172
				'methods'             => WP_REST_Server::READABLE,
173
				'callback'            => __CLASS__ . '::get_connection_plugins',
174
				'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
175
			)
176
		);
177
178
		// Start the connection process by registering the site on WordPress.com servers.
179
		register_rest_route( 'jetpack/v4', '/connection/register', array(
180
			'methods'             => WP_REST_Server::EDITABLE,
181
			'callback'            => __CLASS__ . '::register_site',
182
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
183
			'args'                => array(
184
				'registration_nonce' => array( 'type' => 'string' ),
185
			),
186
		) );
187
188
		// Set the connection owner
189
		register_rest_route( 'jetpack/v4', '/connection/owner', array(
190
			'methods' => WP_REST_Server::EDITABLE,
191
			'callback' => __CLASS__ . '::set_connection_owner',
192
			'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback',
193
		) );
194
195
		// Current user: get or set tracking settings.
196
		register_rest_route( 'jetpack/v4', '/tracking/settings', array(
197
			array(
198
				'methods'             => WP_REST_Server::READABLE,
199
				'callback'            => __CLASS__ . '::get_user_tracking_settings',
200
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
201
			),
202
			array(
203
				'methods'             => WP_REST_Server::EDITABLE,
204
				'callback'            => __CLASS__ . '::update_user_tracking_settings',
205
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
206
				'args'                => array(
207
					'tracks_opt_out' => array( 'type' => 'boolean' ),
208
				),
209
			),
210
		) );
211
212
		// Disconnect site from WordPress.com servers
213
		register_rest_route( 'jetpack/v4', '/connection', array(
214
			'methods' => WP_REST_Server::EDITABLE,
215
			'callback' => __CLASS__ . '::disconnect_site',
216
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
217
		) );
218
219
		// Disconnect/unlink user from WordPress.com servers
220
		register_rest_route( 'jetpack/v4', '/connection/user', array(
221
			'methods' => WP_REST_Server::EDITABLE,
222
			'callback' => __CLASS__ . '::unlink_user',
223
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
224
		) );
225
226
		// Get current site data
227
		register_rest_route( 'jetpack/v4', '/site', array(
228
			'methods' => WP_REST_Server::READABLE,
229
			'callback' => __CLASS__ . '::get_site_data',
230
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
231
		) );
232
233
		// Get current site data
234
		register_rest_route( 'jetpack/v4', '/site/features', array(
235
			'methods' => WP_REST_Server::READABLE,
236
			'callback' => array( $site_endpoint, 'get_features' ),
237
			'permission_callback' => array( $site_endpoint , 'can_request' ),
238
		) );
239
240
		register_rest_route(
241
			'jetpack/v4',
242
			'/site/products',
243
			array(
244
				'methods'             => WP_REST_Server::READABLE,
245
				'callback'            => array( $site_endpoint, 'get_products' ),
246
				'permission_callback' => array( $site_endpoint, 'can_request' ),
247
			)
248
		);
249
250
		// Get current site purchases.
251
		register_rest_route(
252
			'jetpack/v4',
253
			'/site/purchases',
254
			array(
255
				'methods'             => WP_REST_Server::READABLE,
256
				'callback'            => array( $site_endpoint, 'get_purchases' ),
257
				'permission_callback' => array( $site_endpoint, 'can_request' ),
258
			)
259
		);
260
261
		// Get current site benefits
262
		register_rest_route( 'jetpack/v4', '/site/benefits', array(
263
			'methods'             => WP_REST_Server::READABLE,
264
			'callback'            => array( $site_endpoint, 'get_benefits' ),
265
			'permission_callback' => array( $site_endpoint, 'can_request' ),
266
		) );
267
268
		// Get Activity Log data for this site.
269
		register_rest_route( 'jetpack/v4', '/site/activity', array(
270
			'methods' => WP_REST_Server::READABLE,
271
			'callback' => __CLASS__ . '::get_site_activity',
272
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
273
		) );
274
275
		// Confirm that a site in identity crisis should be in staging mode
276
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
277
			'methods' => WP_REST_Server::EDITABLE,
278
			'callback' => __CLASS__ . '::confirm_safe_mode',
279
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
280
		) );
281
282
		// IDC resolve: create an entirely new shadow site for this URL.
283
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
284
			'methods' => WP_REST_Server::EDITABLE,
285
			'callback' => __CLASS__ . '::start_fresh_connection',
286
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
287
		) );
288
289
		// Handles the request to migrate stats and subscribers during an identity crisis.
290
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
291
			'methods' => WP_REST_Server::EDITABLE,
292
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
293
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
294
		) );
295
296
		// Return all modules
297
		register_rest_route( 'jetpack/v4', '/module/all', array(
298
			'methods' => WP_REST_Server::READABLE,
299
			'callback' => array( $module_list_endpoint, 'process' ),
300
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
301
		) );
302
303
		// Activate many modules
304
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
305
			'methods' => WP_REST_Server::EDITABLE,
306
			'callback' => array( $module_list_endpoint, 'process' ),
307
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
308
			'args' => array(
309
				'modules' => array(
310
					'default'           => '',
311
					'type'              => 'array',
312
					'items'             => array(
313
						'type'          => 'string',
314
					),
315
					'required'          => true,
316
					'validate_callback' => __CLASS__ . '::validate_module_list',
317
				),
318
				'active' => array(
319
					'default'           => true,
320
					'type'              => 'boolean',
321
					'required'          => false,
322
					'validate_callback' => __CLASS__ . '::validate_boolean',
323
				),
324
			)
325
		) );
326
327
		// Return a single module and update it when needed
328
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
329
			'methods' => WP_REST_Server::READABLE,
330
			'callback' => array( $core_api_endpoint, 'process' ),
331
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
332
		) );
333
334
		// Activate and deactivate a module
335
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
336
			'methods' => WP_REST_Server::EDITABLE,
337
			'callback' => array( $module_toggle_endpoint, 'process' ),
338
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
339
			'args' => array(
340
				'active' => array(
341
					'default'           => true,
342
					'type'              => 'boolean',
343
					'required'          => true,
344
					'validate_callback' => __CLASS__ . '::validate_boolean',
345
				),
346
			)
347
		) );
348
349
		// Update a module
350
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
351
			'methods' => WP_REST_Server::EDITABLE,
352
			'callback' => array( $core_api_endpoint, 'process' ),
353
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
354
			'args' => self::get_updateable_parameters( 'any' )
355
		) );
356
357
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
358
		// Akismet spam count, etc.
359
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
360
			'methods' => WP_REST_Server::READABLE,
361
			'callback' => array( $module_data_endpoint, 'process' ),
362
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
363
			'args' => array(
364
				'range' => array(
365
					'default'           => 'day',
366
					'type'              => 'string',
367
					'required'          => false,
368
					'validate_callback' => __CLASS__ . '::validate_string',
369
				),
370
			)
371
		) );
372
373
		// Check if the API key for a specific service is valid or not
374
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
375
			'methods' => WP_REST_Server::READABLE,
376
			'callback' => array( $module_data_endpoint, 'key_check' ),
377
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
378
			'sanitize_callback' => 'sanitize_text_field',
379
		) );
380
381
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
382
			'methods' => WP_REST_Server::EDITABLE,
383
			'callback' => array( $module_data_endpoint, 'key_check' ),
384
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
385
			'sanitize_callback' => 'sanitize_text_field',
386
			'args' => array(
387
				'api_key' => array(
388
					'default'           => '',
389
					'type'              => 'string',
390
					'validate_callback' => __CLASS__ . '::validate_alphanum',
391
				),
392
			)
393
		) );
394
395
		// Update any Jetpack module option or setting
396
		register_rest_route( 'jetpack/v4', '/settings', array(
397
			'methods' => WP_REST_Server::EDITABLE,
398
			'callback' => array( $core_api_endpoint, 'process' ),
399
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
400
			'args' => self::get_updateable_parameters( 'any' )
401
		) );
402
403
		// Update a module
404
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
405
			'methods' => WP_REST_Server::EDITABLE,
406
			'callback' => array( $core_api_endpoint, 'process' ),
407
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
408
			'args' => self::get_updateable_parameters()
409
		) );
410
411
		// Return all module settings
412
		register_rest_route( 'jetpack/v4', '/settings/', array(
413
			'methods' => WP_REST_Server::READABLE,
414
			'callback' => array( $core_api_endpoint, 'process' ),
415
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
416
		) );
417
418
		// Reset all Jetpack options
419
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
420
			'methods' => WP_REST_Server::EDITABLE,
421
			'callback' => __CLASS__ . '::reset_jetpack_options',
422
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
423
		) );
424
425
		// Updates: get number of plugin updates available
426
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
427
			'methods' => WP_REST_Server::READABLE,
428
			'callback' => __CLASS__ . '::get_plugin_update_count',
429
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
430
		) );
431
432
		// Dismiss Jetpack Notices
433
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
434
			'methods' => WP_REST_Server::EDITABLE,
435
			'callback' => __CLASS__ . '::dismiss_notice',
436
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
437
		) );
438
439
		// Plugins: get list of all plugins.
440
		register_rest_route( 'jetpack/v4', '/plugins', array(
441
			'methods' => WP_REST_Server::READABLE,
442
			'callback' => __CLASS__ . '::get_plugins',
443
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
444
		) );
445
446
		register_rest_route( 'jetpack/v4', '/plugins/akismet/activate', array(
447
			'methods' => WP_REST_Server::EDITABLE,
448
			'callback' => __CLASS__ . '::activate_akismet',
449
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
450
		) );
451
452
		// Plugins: check if the plugin is active.
453
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
454
			'methods' => WP_REST_Server::READABLE,
455
			'callback' => __CLASS__ . '::get_plugin',
456
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
457
		) );
458
459
		// Widgets: get information about a widget that supports it.
460
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
461
			'methods' => WP_REST_Server::READABLE,
462
			'callback' => array( $widget_endpoint, 'process' ),
463
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
464
		) );
465
466
		// Site Verify: check if the site is verified, and a get verification token if not
467
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
468
			'methods' => WP_REST_Server::READABLE,
469
			'callback' => __CLASS__ . '::is_site_verified_and_token',
470
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
471
		) );
472
473
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
474
			'methods' => WP_REST_Server::READABLE,
475
			'callback' => __CLASS__ . '::is_site_verified_and_token',
476
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
477
		) );
478
479
		// Site Verify: tell a service to verify the site
480
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
481
			'methods' => WP_REST_Server::EDITABLE,
482
			'callback' => __CLASS__ . '::verify_site',
483
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
484
			'args' => array(
485
				'keyring_id' => array(
486
					'required'          => true,
487
					'type'              => 'integer',
488
					'validate_callback' => __CLASS__  . '::validate_posint',
489
				),
490
			)
491
		) );
492
493
		// Get and set API keys.
494
		// Note: permission_callback intentionally omitted from the GET method.
495
		// Map block requires open access to API keys on the front end.
496
		register_rest_route(
497
			'jetpack/v4',
498
			'/service-api-keys/(?P<service>[a-z\-_]+)',
499
			array(
500
				array(
501
					'methods'             => WP_REST_Server::READABLE,
502
					'callback'            => __CLASS__ . '::get_service_api_key',
503
				),
504
				array(
505
					'methods'             => WP_REST_Server::EDITABLE,
506
					'callback'            => __CLASS__ . '::update_service_api_key',
507
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
508
					'args'                => array(
509
						'service_api_key' => array(
510
							'required' => true,
511
							'type'     => 'text',
512
						),
513
					),
514
				),
515
				array(
516
					'methods'             => WP_REST_Server::DELETABLE,
517
					'callback'            => __CLASS__ . '::delete_service_api_key',
518
					'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ),
519
				),
520
			)
521
		);
522
523
		register_rest_route(
524
			'jetpack/v4',
525
			'/mobile/send-login-email',
526
			array(
527
				'methods'             => WP_REST_Server::EDITABLE,
528
				'callback'            => __CLASS__ . '::send_mobile_magic_link',
529
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
530
			)
531
		);
532
533
		/*
534
		 * Get and update settings from the Jetpack wizard.
535
		 */
536
		register_rest_route(
537
			'jetpack/v4',
538
			'/setup/questionnaire',
539
			array(
540
				array(
541
					'methods'             => WP_REST_Server::READABLE,
542
					'callback'            => __CLASS__ . '::get_setup_wizard_questionnaire',
543
					'permission_callback' => __CLASS__ . '::update_settings_permission_check',
544
				),
545
				array(
546
					'methods'             => WP_REST_Server::EDITABLE,
547
					'callback'            => __CLASS__ . '::update_setup_wizard_questionnaire',
548
					'permission_callback' => __CLASS__ . '::update_settings_permission_check',
549
					'args'                => array(
550
						'questionnaire' => array(
551
							'required'          => false,
552
							'type'              => 'object',
553
							'validate_callback' => __CLASS__ . '::validate_setup_wizard_questionnaire',
554
						),
555
						'status'        => array(
556
							'required'          => false,
557
							'type'              => 'string',
558
							'validate_callback' => __CLASS__ . '::validate_string',
559
						),
560
					),
561
				),
562
			)
563
		);
564
	}
565
566
	/**
567
	 * Get the settings for the wizard questionnaire
568
	 *
569
	 * @return array Questionnaire settings.
570
	 */
571
	public static function get_setup_wizard_questionnaire() {
572
		return Jetpack_Options::get_option( 'setup_wizard_questionnaire', (object) array() );
573
	}
574
575
	/**
576
	 * Update the settings selected on the wizard questionnaire
577
	 *
578
	 * @param WP_REST_Request $request The request.
579
	 *
580
	 * @return bool true.
581
	 */
582
	public static function update_setup_wizard_questionnaire( $request ) {
583
		$questionnaire = $request['questionnaire'];
584
		if ( ! empty( $questionnaire ) ) {
585
			Jetpack_Options::update_option( 'setup_wizard_questionnaire', $questionnaire );
586
		}
587
588
		$status = $request['status'];
589
		if ( ! empty( $status ) ) {
590
			Jetpack_Options::update_option( 'setup_wizard_status', $status );
591
		}
592
593
		return true;
594
	}
595
596
	/**
597
	 * Validate the answers on the setup wizard questionnaire
598
	 *
599
	 * @param array           $value Value to check received by request.
600
	 * @param WP_REST_Request $request The request sent to the WP REST API.
601
	 * @param string          $param Name of the parameter passed to endpoint holding $value.
602
	 *
603
	 * @return bool|WP_Error
604
	 */
605
	public static function validate_setup_wizard_questionnaire( $value, $request, $param ) {
606 View Code Duplication
		if ( ! is_array( $value ) ) {
607
			/* translators: Name of a parameter that must be an object */
608
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an object.', 'jetpack' ), $param ) );
609
		}
610
611
		foreach ( $value as $answer_key => $answer ) {
612
			if ( is_string( $answer ) ) {
613
				$validate = self::validate_string( $answer, $request, $param );
614
			} else {
615
				$validate = self::validate_boolean( $answer, $request, $param );
616
			}
617
618
			if ( is_wp_error( $validate ) ) {
619
				return $validate;
620
			}
621
		}
622
623
		return true;
624
	}
625
626
	public static function get_plans( $request ) {
627
		$request = Client::wpcom_json_api_request_as_user(
628
			'/plans?_locale=' . get_user_locale(),
629
			'2',
630
			array(
631
				'method'  => 'GET',
632
				'headers' => array(
633
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
634
				),
635
			)
636
		);
637
638
		$body = json_decode( wp_remote_retrieve_body( $request ) );
639
		if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
640
			$data = $body;
641
		} else {
642
			// something went wrong so we'll just return the response without caching
643
			return $body;
644
		}
645
646
		return $data;
647
	}
648
649
	/**
650
	 * Gets the WP.com products that are in use on wpcom.
651
	 * Similar to the WP.com plans that we currently in user on WPCOM.
652
	 *
653
	 * @param WP_REST_Request $request The request.
654
	 *
655
	 * @return string|WP_Error A JSON object of wpcom products if the request was successful, or a WP_Error otherwise.
656
	 */
657
	public static function get_products( $request ) {
658
		$wpcom_request = Client::wpcom_json_api_request_as_user(
659
			'/products?_locale=' . get_user_locale() . '&type=jetpack',
660
			'2',
661
			array(
662
				'method'  => 'GET',
663
				'headers' => array(
664
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
665
				),
666
			)
667
		);
668
669
		$response_code = wp_remote_retrieve_response_code( $wpcom_request );
670
		if ( 200 === $response_code ) {
671
			return json_decode( wp_remote_retrieve_body( $wpcom_request ) );
672
		} else {
673
			// Something went wrong so we'll just return the response without caching.
674
			return new WP_Error(
675
				'failed_to_fetch_data',
676
				esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
677
				array( 'status' => $response_code )
678
			);
679
		}
680
	}
681
682
	public static function submit_survey( $request ) {
683
684
		$wpcom_request = Client::wpcom_json_api_request_as_user(
685
			'/marketing/survey',
686
			'v2',
687
			array(
688
				'method'  => 'POST',
689
				'headers' => array(
690
					'Content-Type'    => 'application/json',
691
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
692
				),
693
			),
694
			$request->get_json_params()
695
		);
696
697
		$wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
698
		if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) {
699
			$data = $wpcom_request_body;
700
		} else {
701
			// something went wrong so we'll just return the response without caching
702
			return $wpcom_request_body;
703
		}
704
705
		return $data;
706
	}
707
708
	/**
709
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
710
	 *
711
	 * @param $request WP_REST_Request
712
	 *
713
	 * @return array An array of jitms
714
	 */
715
	public static function get_jitm_message( $request ) {
716
		$jitm = JITM::get_instance();
717
718
		if ( ! $jitm->register() ) {
719
			return array();
720
		}
721
722
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ), 'true' === $request['full_jp_logo_exists'] ? true : false );
723
	}
724
725
	/**
726
	 * Dismisses a jitm
727
	 * @param $request WP_REST_Request The request
728
	 *
729
	 * @return bool Always True
730
	 */
731
	public static function delete_jitm_message( $request ) {
732
		$jitm = JITM::get_instance();
733
734
		if ( ! $jitm->register() ) {
735
			return true;
736
		}
737
738
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
0 ignored issues
show
The call to Pre_Connection_JITM::dismiss() has too many arguments starting with $request['feature_class'].

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

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

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

Loading history...
739
	}
740
741
	/**
742
	 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
743
	 *  keyring to use to get the token if it is not
744
	 *
745
	 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
746
	 *
747
	 * @since 6.6.0
748
	 *
749
	 * @param WP_REST_Request $request The request sent to the WP REST API.
750
	 *
751
	 * @return array|wp-error
752
	 */
753
	public static function is_site_verified_and_token( $request ) {
754
		/**
755
		 * Return an error if the site uses a Maintenance / Coming Soon plugin
756
		 * and if the plugin is configured to make the site private.
757
		 *
758
		 * We currently handle the following plugins:
759
		 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
760
		 * - https://wordpress.org/plugins/mojo-under-construction
761
		 * - https://wordpress.org/plugins/under-construction-page
762
		 * - https://wordpress.org/plugins/ultimate-under-construction
763
		 * - https://wordpress.org/plugins/coming-soon
764
		 *
765
		 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
766
		 * If the filter returns true, we will consider the site as under construction.
767
		 */
768
		$mm_coming_soon                       = get_option( 'mm_coming_soon', null );
769
		$under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
770
		$ucp_options                          = get_option( 'ucp_options', array() );
771
		$uuc_settings                         = get_option( 'uuc_settings', array() );
772
		$csp4                                 = get_option( 'seed_csp4_settings_content', array() );
773
		if (
774
			( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
775
			|| Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
776
			|| ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
777
			|| ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
778
			|| ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) &&  isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
779
			/**
780
			 * Allow plugins to mark a site as "under construction".
781
			 *
782
			 * @since 6.7.0
783
			 *
784
			 * @param false bool Is the site under construction? Default to false.
785
			 */
786
			|| true === apply_filters( 'jetpack_is_under_construction_plugin', false )
787
		) {
788
			return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
789
		}
790
791
 		$xml = new Jetpack_IXR_Client( array(
792
 			'user_id' => get_current_user_id(),
793
		) );
794
795
		$args = array(
796
			'user_id' => get_current_user_id(),
797
			'service' => $request[ 'service' ],
798
		);
799
800
		if ( isset( $request[ 'keyring_id' ] ) ) {
801
			$args[ 'keyring_id' ] = $request[ 'keyring_id' ];
802
		}
803
804
		$xml->query( 'jetpack.isSiteVerified', $args );
805
806
		if ( $xml->isError() ) {
807
			return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
808
		} else {
809
			return $xml->getResponse();
810
		}
811
	}
812
813
814
815
	public static function verify_site( $request ) {
816
		$xml = new Jetpack_IXR_Client( array(
817
			'user_id' => get_current_user_id(),
818
		) );
819
820
		$params = $request->get_json_params();
821
822
		$xml->query( 'jetpack.verifySite', array(
823
				'user_id' => get_current_user_id(),
824
				'service' => $request[ 'service' ],
825
				'keyring_id' => $params[ 'keyring_id' ],
826
			)
827
		);
828
829
		if ( $xml->isError() ) {
830
			return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
831
		} else {
832
			$response = $xml->getResponse();
833
834
			if ( ! empty( $response['errors'] ) ) {
835
				$error = new WP_Error;
836
				$error->errors = $response['errors'];
837
				return $error;
838
			}
839
840
			return $response;
841
		}
842
	}
843
844
	/**
845
	 * Handles verification that a site is registered
846
	 *
847
	 * @since 5.4.0
848
	 *
849
	 * @param WP_REST_Request $request The request sent to the WP REST API.
850
	 *
851
	 * @return array|wp-error
852
	 */
853
	 public static function remote_authorize( $request ) {
854
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
855
		$result = $xmlrpc_server->remote_authorize( $request );
856
857
		if ( is_a( $result, 'IXR_Error' ) ) {
858
			$result = new WP_Error( $result->code, $result->message );
859
		}
860
861
		return $result;
862
	 }
863
864
	/**
865
	 * Handles dismissing of Jetpack Notices
866
	 *
867
	 * @since 4.3.0
868
	 *
869
	 * @param WP_REST_Request $request The request sent to the WP REST API.
870
	 *
871
	 * @return array|wp-error
872
	 */
873
	public static function dismiss_notice( $request ) {
874
		$notice = $request['notice'];
875
876
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
877
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
878
		}
879
880
		if ( isset( $notice ) && ! empty( $notice ) ) {
881
			switch( $notice ) {
882
				case 'feedback_dash_request':
883
				case 'welcome':
884
					$notices = get_option( 'jetpack_dismissed_notices', array() );
885
					$notices[ $notice ] = true;
886
					update_option( 'jetpack_dismissed_notices', $notices );
887
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
888
889
				default:
890
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
891
			}
892
		}
893
894
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
895
	}
896
897
	/**
898
	 * Verify that the user can disconnect the site.
899
	 *
900
	 * @since 4.3.0
901
	 *
902
	 * @return bool|WP_Error True if user is able to disconnect the site.
903
	 */
904 View Code Duplication
	public static function disconnect_site_permission_callback() {
905
		if ( current_user_can( 'jetpack_disconnect' ) ) {
906
			return true;
907
		}
908
909
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
910
911
	}
912
913
	/**
914
	 * Verify that the user can get a connect/link URL
915
	 *
916
	 * @since 4.3.0
917
	 *
918
	 * @return bool|WP_Error True if user is able to disconnect the site.
919
	 */
920 View Code Duplication
	public static function connect_url_permission_callback() {
921
		if ( current_user_can( 'jetpack_connect_user' ) ) {
922
			return true;
923
		}
924
925
		return new WP_Error( 'invalid_user_permission_jetpack_connect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
926
927
	}
928
929
	/**
930
	 * Verify that a user can get the data about the current user.
931
	 * Only those who can connect.
932
	 *
933
	 * @since 4.3.0
934
	 *
935
	 * @uses Jetpack::is_user_connected();
936
	 *
937
	 * @return bool|WP_Error True if user is able to unlink.
938
	 */
939 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
940
		if ( current_user_can( 'jetpack_connect_user' ) ) {
941
			return true;
942
		}
943
944
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
945
	}
946
947
	/**
948
	 * Check that user has permission to change the master user.
949
	 *
950
	 * @since 6.2.0
951
	 * @since 7.7.0 Update so that any user with jetpack_disconnect privs can set owner.
952
	 *
953
	 * @return bool|WP_Error True if user is able to change master user.
954
	 */
955 View Code Duplication
	public static function set_connection_owner_permission_callback() {
956
		if ( current_user_can( 'jetpack_disconnect' ) ) {
957
			return true;
958
		}
959
960
		return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
961
	}
962
963
	/**
964
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
965
	 *
966
	 * @since 4.3.0
967
	 *
968
	 * @uses Jetpack::is_user_connected();
969
	 *
970
	 * @return bool|WP_Error True if user is able to unlink.
971
	 */
972
	public static function unlink_user_permission_callback() {
973
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
974
			return true;
975
		}
976
977
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
978
	}
979
980
	/**
981
	 * Verify that user can manage Jetpack modules.
982
	 *
983
	 * @since 4.3.0
984
	 *
985
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
986
	 */
987
	public static function manage_modules_permission_check() {
988
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
989
			return true;
990
		}
991
992
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
993
	}
994
995
	/**
996
	 * Verify that user can update Jetpack modules.
997
	 *
998
	 * @since 4.3.0
999
	 *
1000
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
1001
	 */
1002 View Code Duplication
	public static function configure_modules_permission_check() {
1003
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
1004
			return true;
1005
		}
1006
1007
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1008
	}
1009
1010
	/**
1011
	 * Verify that user can view Jetpack admin page.
1012
	 *
1013
	 * @since 4.3.0
1014
	 *
1015
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
1016
	 */
1017 View Code Duplication
	public static function view_admin_page_permission_check() {
1018
		if ( current_user_can( 'jetpack_admin_page' ) ) {
1019
			return true;
1020
		}
1021
1022
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1023
	}
1024
1025
	/**
1026
	 * Verify that user can mitigate an identity crisis.
1027
	 *
1028
	 * @since 4.4.0
1029
	 *
1030
	 * @return bool Whether user has capability 'jetpack_disconnect'.
1031
	 */
1032 View Code Duplication
	public static function identity_crisis_mitigation_permission_check() {
1033
		if ( current_user_can( 'jetpack_disconnect' ) ) {
1034
			return true;
1035
		}
1036
1037
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1038
	}
1039
1040
	/**
1041
	 * Verify that user can update Jetpack general settings.
1042
	 *
1043
	 * @since 4.3.0
1044
	 *
1045
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
1046
	 */
1047 View Code Duplication
	public static function update_settings_permission_check() {
1048
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
1049
			return true;
1050
		}
1051
1052
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1053
	}
1054
1055
	/**
1056
	 * Verify that user can view Jetpack admin page and can activate plugins.
1057
	 *
1058
	 * @since 4.3.0
1059
	 *
1060
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
1061
	 */
1062 View Code Duplication
	public static function activate_plugins_permission_check() {
1063
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
1064
			return true;
1065
		}
1066
1067
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1068
	}
1069
1070
	/**
1071
	 * Verify that user can edit other's posts (Editors and Administrators).
1072
	 *
1073
	 * @return bool Whether user has the capability 'edit_others_posts'.
1074
	 */
1075
	public static function edit_others_posts_check() {
1076
		if ( current_user_can( 'edit_others_posts' ) ) {
1077
			return true;
1078
		}
1079
1080
		return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
1081
	}
1082
1083
	/**
1084
	 * Contextual HTTP error code for authorization failure.
1085
	 *
1086
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
1087
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
1088
	 *
1089
	 * @since 4.3.0
1090
	 *
1091
	 * @return int
1092
	 */
1093
	public static function rest_authorization_required_code() {
1094
		return is_user_logged_in() ? 403 : 401;
1095
	}
1096
1097
	/**
1098
	 * Get connection status for this Jetpack site.
1099
	 *
1100
	 * @since 4.3.0
1101
	 *
1102
	 * @return bool True if site is connected
1103
	 */
1104
	public static function jetpack_connection_status() {
1105
		$status = new Status();
1106
		return rest_ensure_response( array(
1107
			'isActive'     => Jetpack::is_active(),
1108
			'isStaging'    => $status->is_staging_site(),
1109
			'isRegistered' => Jetpack::connection()->is_registered(),
1110
			'devMode'      => array(
1111
				'isActive' => $status->is_development_mode(),
1112
				'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
1113
				'url'      => site_url() && false === strpos( site_url(), '.' ),
1114
				'filter'   => apply_filters( 'jetpack_development_mode', false ),
1115
			),
1116
			)
1117
		);
1118
	}
1119
1120
	/**
1121
	 * Test connection status for this Jetpack site.
1122
	 *
1123
	 * @since 6.8.0
1124
	 *
1125
	 * @return array|WP_Error WP_Error returned if connection test does not succeed.
1126
	 */
1127
	public static function jetpack_connection_test() {
1128
		jetpack_require_lib( 'debugger' );
1129
		$cxntests = new Jetpack_Cxn_Tests();
1130
1131
		if ( $cxntests->pass() ) {
1132
			return rest_ensure_response(
1133
				array(
1134
					'code'    => 'success',
1135
					'message' => __( 'All connection tests passed.', 'jetpack' ),
1136
				)
1137
			);
1138
		} else {
1139
			return $cxntests->output_fails_as_wp_error();
1140
		}
1141
	}
1142
1143
	/**
1144
	 * Get plugins connected to the Jetpack.
1145
	 *
1146
	 * @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
1147
	 */
1148
	public static function get_connection_plugins() {
1149
		$plugins = ( new Connection_Manager() )->get_connected_plugins();
1150
1151
		if ( is_wp_error( $plugins ) ) {
1152
			return $plugins;
1153
		}
1154
1155
		array_walk(
1156
			$plugins,
1157
			function( &$data, $slug ) {
1158
				$data['slug'] = $slug;
1159
			}
1160
		);
1161
1162
		return rest_ensure_response( array_values( $plugins ) );
1163
	}
1164
1165
	/**
1166
	 * Test connection permission check method.
1167
	 *
1168
	 * @since 7.1.0
1169
	 *
1170
	 * @return bool
1171
	 */
1172
	public static function view_jetpack_connection_test_check() {
1173
		if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'] ) ) {
1174
			return false;
1175
		}
1176
		$signature = base64_decode( $_GET['signature'] );
1177
1178
		$signature_data = wp_json_encode(
1179
			array(
1180
				'rest_route' => $_GET['rest_route'],
1181
				'timestamp' => intval( $_GET['timestamp'] ),
1182
				'url' => wp_unslash( $_GET['url'] ),
1183
			)
1184
		);
1185
1186
		if (
1187
			! function_exists( 'openssl_verify' )
1188
			|| 1 !== openssl_verify(
1189
				$signature_data,
1190
				$signature,
1191
				JETPACK__DEBUGGER_PUBLIC_KEY
1192
			)
1193
		) {
1194
			return false;
1195
		}
1196
1197
		// signature timestamp must be within 5min of current time
1198
		if ( abs( time() - intval( $_GET['timestamp'] ) ) > 300 ) {
1199
			return false;
1200
		}
1201
1202
		return true;
1203
	}
1204
1205
	/**
1206
	 * Test connection status for this Jetpack site, encrypt the results for decryption by a third-party.
1207
	 *
1208
	 * @since 7.1.0
1209
	 *
1210
	 * @return array|mixed|object|WP_Error
1211
	 */
1212
	public static function jetpack_connection_test_for_external() {
1213
		// 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.
1214
		add_filter( 'jetpack_debugger_run_self_test', '__return_false' );
1215
		jetpack_require_lib( 'debugger' );
1216
		$cxntests = new Jetpack_Cxn_Tests();
1217
1218
		if ( $cxntests->pass() ) {
1219
			$result = array(
1220
				'code'    => 'success',
1221
				'message' => __( 'All connection tests passed.', 'jetpack' ),
1222
			);
1223
		} else {
1224
			$error  = $cxntests->output_fails_as_wp_error(); // Using this so the output is similar both ways.
1225
			$errors = array();
1226
1227
			// Borrowed from WP_REST_Server::error_to_response().
1228
			foreach ( (array) $error->errors as $code => $messages ) {
1229
				foreach ( (array) $messages as $message ) {
1230
					$errors[] = array(
1231
						'code'    => $code,
1232
						'message' => $message,
1233
						'data'    => $error->get_error_data( $code ),
1234
					);
1235
				}
1236
			}
1237
1238
			$result = ( ! empty( $errors ) ) ? $errors[0] : null;
1239
			if ( count( $errors ) > 1 ) {
1240
				// Remove the primary error.
1241
				array_shift( $errors );
1242
				$result['additional_errors'] = $errors;
1243
			}
1244
		}
1245
1246
		$result = wp_json_encode( $result );
1247
1248
		$encrypted = $cxntests->encrypt_string_for_wpcom( $result );
1249
1250
		if ( ! $encrypted || ! is_array( $encrypted ) ) {
1251
			return rest_ensure_response(
1252
				array(
1253
					'code'    => 'action_required',
1254
					'message' => 'Please request results from the in-plugin debugger',
1255
				)
1256
			);
1257
		}
1258
1259
		return rest_ensure_response(
1260
			array(
1261
				'code'  => 'response',
1262
				'debug' => array(
1263
					'data' => $encrypted['data'],
1264
					'key'  => $encrypted['key'],
1265
				),
1266
			)
1267
		);
1268
	}
1269
1270
	public static function rewind_data() {
1271
		$site_id = Jetpack_Options::get_option( 'id' );
1272
1273
		if ( ! $site_id ) {
1274
			return new WP_Error( 'site_id_missing' );
1275
		}
1276
1277
		if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1278
			$rewind_state = get_transient( 'jetpack_rewind_state' );
1279
			if ( $rewind_state ) {
1280
				return $rewind_state;
1281
			}
1282
		}
1283
1284
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' );
1285
1286
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1287
			return new WP_Error( 'rewind_data_fetch_failed' );
1288
		}
1289
1290
		$body   = wp_remote_retrieve_body( $response );
1291
		$result = json_decode( $body );
1292
		set_transient( 'jetpack_rewind_state', $result, 30 * MINUTE_IN_SECONDS );
1293
1294
		return $result;
1295
	}
1296
1297
	/**
1298
	 * Get rewind data
1299
	 *
1300
	 * @since 5.7.0
1301
	 *
1302
	 * @return array Array of rewind properties.
1303
	 */
1304 View Code Duplication
	public static function get_rewind_data() {
1305
		$rewind_data = self::rewind_data();
1306
1307
		if ( ! is_wp_error( $rewind_data ) ) {
1308
			return rest_ensure_response( array(
1309
					'code' => 'success',
1310
					'message' => esc_html__( 'Backup & Scan data correctly received.', 'jetpack' ),
1311
					'data' => wp_json_encode( $rewind_data ),
1312
				)
1313
			);
1314
		}
1315
1316
		if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) {
1317
			return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1318
		}
1319
1320
		if ( $rewind_data->get_error_code() === 'site_id_missing' ) {
1321
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1322
		}
1323
1324
		return new WP_Error(
1325
			'error_get_rewind_data',
1326
			esc_html__( 'Could not retrieve Backup & Scan data.', 'jetpack' ),
1327
			array( 'status' => 500 )
1328
		);
1329
	}
1330
1331
	/**
1332
	 * Gets Scan state data.
1333
	 *
1334
	 * @since 8.5.0
1335
	 *
1336
	 * @return array|WP_Error Result from WPCOM API or error.
1337
	 */
1338
	public static function scan_state() {
1339
1340
		if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1341
			$scan_state = get_transient( 'jetpack_scan_state' );
1342
			if ( ! empty( $scan_state ) ) {
1343
				return $scan_state;
1344
			}
1345
		}
1346
		$site_id = Jetpack_Options::get_option( 'id' );
1347
1348
		if ( ! $site_id ) {
1349
			return new WP_Error( 'site_id_missing' );
1350
		}
1351
		// The default timeout was too short in come cases.
1352
		add_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 );
1353
		$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
1354
		remove_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 );
1355
1356
		if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
1357
			return new WP_Error( 'scan_state_fetch_failed' );
1358
		}
1359
1360
		$body   = wp_remote_retrieve_body( $response );
1361
		$result = json_decode( $body );
1362
		set_transient( 'jetpack_scan_state', $result, 30 * MINUTE_IN_SECONDS );
1363
1364
		return $result;
1365
	}
1366
1367
	/**
1368
	 * Increases the request timeout value to 30 seconds.
1369
	 *
1370
	 * @return int Always returns 30.
1371
	 */
1372
	public static function increase_timeout_30() {
1373
		return 30; // 30 Seconds
1374
	}
1375
1376
	/**
1377
	 * Get Scan state for API.
1378
	 *
1379
	 * @since 8.5.0
1380
	 *
1381
	 * @return WP_REST_Response|WP_Error REST response or error state.
1382
	 */
1383 View Code Duplication
	public static function get_scan_state() {
1384
		$scan_state = self::scan_state();
1385
1386
		if ( ! is_wp_error( $scan_state ) ) {
1387
			return rest_ensure_response(
1388
				array(
1389
					'code'    => 'success',
1390
					'message' => esc_html__( 'Scan state correctly received.', 'jetpack' ),
1391
					'data'    => wp_json_encode( $scan_state ),
1392
				)
1393
			);
1394
		}
1395
1396
		if ( $scan_state->get_error_code() === 'scan_state_fetch_failed' ) {
1397
			return new WP_Error( 'scan_state_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1398
		}
1399
1400
		if ( $scan_state->get_error_code() === 'site_id_missing' ) {
1401
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1402
		}
1403
1404
		return new WP_Error(
1405
			'error_get_rewind_data',
1406
			esc_html__( 'Could not retrieve Scan state.', 'jetpack' ),
1407
			array( 'status' => 500 )
1408
		);
1409
	}
1410
1411
	/**
1412
	 * Disconnects Jetpack from the WordPress.com Servers
1413
	 *
1414
	 * @uses Jetpack::disconnect();
1415
	 * @since 4.3.0
1416
	 *
1417
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1418
	 *
1419
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
1420
	 */
1421 View Code Duplication
	public static function disconnect_site( $request ) {
1422
1423
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
1424
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1425
		}
1426
1427
		if ( Jetpack::is_active() ) {
1428
			Jetpack::disconnect();
1429
			return rest_ensure_response( array( 'code' => 'success' ) );
1430
		}
1431
1432
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1433
	}
1434
1435
	/**
1436
	 * Registers the Jetpack site
1437
	 *
1438
	 * @uses Jetpack::try_registration();
1439
	 * @since 7.7.0
1440
	 *
1441
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1442
	 *
1443
	 * @return bool|WP_Error True if Jetpack successfully registered
1444
	 */
1445
	public static function register_site( $request ) {
1446
		if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
1447
			return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack' ), array( 'status' => 403 ) );
1448
		}
1449
1450
		$response = Jetpack::try_registration();
1451
1452
		if ( is_wp_error( $response ) ) {
1453
			return $response;
1454
		}
1455
1456
		return rest_ensure_response(
1457
			array(
1458
				'authorizeUrl' => Jetpack::build_authorize_url( false, true )
1459
			) );
1460
	}
1461
1462
	/**
1463
	 * Gets a new connect raw URL with fresh nonce.
1464
	 *
1465
	 * @uses Jetpack::disconnect();
1466
	 * @since 4.3.0
1467
	 *
1468
	 * @param WP_REST_Request $request The request sent to the WP REST API.
0 ignored issues
show
Should the type for parameter $request not be WP_REST_Request|array?

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

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

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

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