Completed
Push — fix/nav-unification-menu-open-... ( d629c6...f468b4 )
by
unknown
17:24 queued 09:08
created

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

Upgrade to new PHP Analysis Engine

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

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

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

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