Completed
Push — try/auto-verify-existing-users ( 43ab54...e05dc1 )
by
unknown
87:23 queued 79:32
created

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

Severity

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
 * Register WP REST API endpoints for Jetpack.
4
 *
5
 * @author Automattic
6
 */
7
8
/**
9
 * Disable direct access.
10
 */
11
if ( ! defined( 'ABSPATH' ) ) {
12
	exit;
13
}
14
15
// Load WP_Error for error messages.
16
require_once ABSPATH . '/wp-includes/class-wp-error.php';
17
18
// Register endpoints when WP REST API is initialized.
19
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
20
// Load API endpoints that are synced with WP.com
21
// Each of these is a class that will register its own routes on 'rest_api_init'.
22
require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
23
24
/**
25
 * Class Jetpack_Core_Json_Api_Endpoints
26
 *
27
 * @since 4.3.0
28
 */
29
class Jetpack_Core_Json_Api_Endpoints {
30
31
	/**
32
	 * @var string Generic error message when user is not allowed to perform an action.
33
	 */
34
	public static $user_permissions_error_msg;
35
36
	/**
37
	 * @var array Roles that can access Stats once they're granted access.
38
	 */
39
	public static $stats_roles;
40
41
	/**
42
	 * Declare the Jetpack REST API endpoints.
43
	 *
44
	 * @since 4.3.0
45
	 */
46
	public static function register_endpoints() {
47
48
		// Load API endpoint base classes
49
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
50
51
		// Load API endpoints
52
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
53
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
54
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
55
56
		self::$user_permissions_error_msg = esc_html__(
57
			'You do not have the correct user permissions to perform this action.
58
			Please contact your site admin if you think this is a mistake.',
59
			'jetpack'
60
		);
61
62
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
63
64
		Jetpack::load_xml_rpc_client();
65
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
66
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
67
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
68
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
69
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
70
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
71
		$widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
72
73
		register_rest_route( 'jetpack/v4', 'plans', array(
74
			'methods'             => WP_REST_Server::READABLE,
75
			'callback'            => __CLASS__ . '::get_plans',
76
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
77
78
		) );
79
80
		register_rest_route( 'jetpack/v4', '/jitm', array(
81
			'methods'  => WP_REST_Server::READABLE,
82
			'callback' => __CLASS__ . '::get_jitm_message',
83
		) );
84
85
		register_rest_route( 'jetpack/v4', '/jitm', array(
86
			'methods'  => WP_REST_Server::CREATABLE,
87
			'callback' => __CLASS__ . '::delete_jitm_message'
88
		) );
89
90
		// Register a site
91
		register_rest_route( 'jetpack/v4', '/verify_registration', array(
92
			'methods' => WP_REST_Server::EDITABLE,
93
			'callback' => __CLASS__ . '::verify_registration',
94
		) );
95
96
		// Authorize a remote user
97
		register_rest_route( 'jetpack/v4', '/remote_authorize', array(
98
			'methods' => WP_REST_Server::EDITABLE,
99
			'callback' => __CLASS__ . '::remote_authorize',
100
		) );
101
102
		// Get current connection status of Jetpack
103
		register_rest_route( 'jetpack/v4', '/connection', array(
104
			'methods' => WP_REST_Server::READABLE,
105
			'callback' => __CLASS__ . '::jetpack_connection_status',
106
		) );
107
108
		register_rest_route( 'jetpack/v4', '/rewind', array(
109
			'methods' => WP_REST_Server::READABLE,
110
			'callback' => __CLASS__ . '::get_rewind_data',
111
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
112
		) );
113
114
		// Fetches a fresh connect URL
115
		register_rest_route( 'jetpack/v4', '/connection/url', array(
116
			'methods' => WP_REST_Server::READABLE,
117
			'callback' => __CLASS__ . '::build_connect_url',
118
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
119
		) );
120
121
		// Get current user connection data
122
		register_rest_route( 'jetpack/v4', '/connection/data', array(
123
			'methods' => WP_REST_Server::READABLE,
124
			'callback' => __CLASS__ . '::get_user_connection_data',
125
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
126
		) );
127
128
		// Set the connection owner
129
		register_rest_route( 'jetpack/v4', '/connection/owner', array(
130
			'methods' => WP_REST_Server::EDITABLE,
131
			'callback' => __CLASS__ . '::set_connection_owner',
132
			'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback',
133
		) );
134
135
		// Current user: get or set tracking settings.
136
		register_rest_route( 'jetpack/v4', '/tracking/settings', array(
137
			array(
138
				'methods'             => WP_REST_Server::READABLE,
139
				'callback'            => __CLASS__ . '::get_user_tracking_settings',
140
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
141
			),
142
			array(
143
				'methods'             => WP_REST_Server::EDITABLE,
144
				'callback'            => __CLASS__ . '::update_user_tracking_settings',
145
				'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
146
				'args'                => array(
147
					'tracks_opt_out' => array( 'type' => 'boolean' ),
148
				),
149
			),
150
		) );
151
152
		// Disconnect site from WordPress.com servers
153
		register_rest_route( 'jetpack/v4', '/connection', array(
154
			'methods' => WP_REST_Server::EDITABLE,
155
			'callback' => __CLASS__ . '::disconnect_site',
156
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
157
		) );
158
159
		// Disconnect/unlink user from WordPress.com servers
160
		register_rest_route( 'jetpack/v4', '/connection/user', array(
161
			'methods' => WP_REST_Server::EDITABLE,
162
			'callback' => __CLASS__ . '::unlink_user',
163
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
164
		) );
165
166
		// Get current site data
167
		register_rest_route( 'jetpack/v4', '/site', array(
168
			'methods' => WP_REST_Server::READABLE,
169
			'callback' => __CLASS__ . '::get_site_data',
170
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
171
		) );
172
173
		// Get current site data
174
		register_rest_route( 'jetpack/v4', '/site/features', array(
175
			'methods' => WP_REST_Server::READABLE,
176
			'callback' => array( $site_endpoint, 'get_features' ),
177
			'permission_callback' => array( $site_endpoint , 'can_request' ),
178
		) );
179
180
		// Get related posts of a certain site post
181
		register_rest_route( 'jetpack/v4', '/site/posts/related', array(
182
			'methods' => WP_REST_Server::READABLE,
183
			'callback' => array( $site_endpoint, 'get_related_posts' ),
184
			'permission_callback' => array( $site_endpoint , 'can_request' ),
185
		) );
186
187
		// Confirm that a site in identity crisis should be in staging mode
188
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
189
			'methods' => WP_REST_Server::EDITABLE,
190
			'callback' => __CLASS__ . '::confirm_safe_mode',
191
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
192
		) );
193
194
		// IDC resolve: create an entirely new shadow site for this URL.
195
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
196
			'methods' => WP_REST_Server::EDITABLE,
197
			'callback' => __CLASS__ . '::start_fresh_connection',
198
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
199
		) );
200
201
		// Handles the request to migrate stats and subscribers during an identity crisis.
202
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
203
			'methods' => WP_REST_Server::EDITABLE,
204
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
205
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
206
		) );
207
208
		// Return all modules
209
		register_rest_route( 'jetpack/v4', '/module/all', array(
210
			'methods' => WP_REST_Server::READABLE,
211
			'callback' => array( $module_list_endpoint, 'process' ),
212
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
213
		) );
214
215
		// Activate many modules
216
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
217
			'methods' => WP_REST_Server::EDITABLE,
218
			'callback' => array( $module_list_endpoint, 'process' ),
219
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
220
			'args' => array(
221
				'modules' => array(
222
					'default'           => '',
223
					'type'              => 'array',
224
					'items'             => array(
225
						'type'          => 'string',
226
					),
227
					'required'          => true,
228
					'validate_callback' => __CLASS__ . '::validate_module_list',
229
				),
230
				'active' => array(
231
					'default'           => true,
232
					'type'              => 'boolean',
233
					'required'          => false,
234
					'validate_callback' => __CLASS__ . '::validate_boolean',
235
				),
236
			)
237
		) );
238
239
		// Return a single module and update it when needed
240
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
241
			'methods' => WP_REST_Server::READABLE,
242
			'callback' => array( $core_api_endpoint, 'process' ),
243
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
244
		) );
245
246
		// Activate and deactivate a module
247
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
248
			'methods' => WP_REST_Server::EDITABLE,
249
			'callback' => array( $module_toggle_endpoint, 'process' ),
250
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
251
			'args' => array(
252
				'active' => array(
253
					'default'           => true,
254
					'type'              => 'boolean',
255
					'required'          => true,
256
					'validate_callback' => __CLASS__ . '::validate_boolean',
257
				),
258
			)
259
		) );
260
261
		// Update a module
262
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
263
			'methods' => WP_REST_Server::EDITABLE,
264
			'callback' => array( $core_api_endpoint, 'process' ),
265
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
266
			'args' => self::get_updateable_parameters( 'any' )
267
		) );
268
269
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
270
		// Akismet spam count, etc.
271
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
272
			'methods' => WP_REST_Server::READABLE,
273
			'callback' => array( $module_data_endpoint, 'process' ),
274
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
275
			'args' => array(
276
				'range' => array(
277
					'default'           => 'day',
278
					'type'              => 'string',
279
					'required'          => false,
280
					'validate_callback' => __CLASS__ . '::validate_string',
281
				),
282
			)
283
		) );
284
285
		// Check if the API key for a specific service is valid or not
286
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
287
			'methods' => WP_REST_Server::READABLE,
288
			'callback' => array( $module_data_endpoint, 'key_check' ),
289
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
290
			'sanitize_callback' => 'sanitize_text_field',
291
		) );
292
293
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
294
			'methods' => WP_REST_Server::EDITABLE,
295
			'callback' => array( $module_data_endpoint, 'key_check' ),
296
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
297
			'sanitize_callback' => 'sanitize_text_field',
298
			'args' => array(
299
				'api_key' => array(
300
					'default'           => '',
301
					'type'              => 'string',
302
					'validate_callback' => __CLASS__ . '::validate_alphanum',
303
				),
304
			)
305
		) );
306
307
		// Update any Jetpack module option or setting
308
		register_rest_route( 'jetpack/v4', '/settings', array(
309
			'methods' => WP_REST_Server::EDITABLE,
310
			'callback' => array( $core_api_endpoint, 'process' ),
311
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
312
			'args' => self::get_updateable_parameters( 'any' )
313
		) );
314
315
		// Update a module
316
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
317
			'methods' => WP_REST_Server::EDITABLE,
318
			'callback' => array( $core_api_endpoint, 'process' ),
319
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
320
			'args' => self::get_updateable_parameters()
321
		) );
322
323
		// Return all module settings
324
		register_rest_route( 'jetpack/v4', '/settings/', array(
325
			'methods' => WP_REST_Server::READABLE,
326
			'callback' => array( $core_api_endpoint, 'process' ),
327
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
328
		) );
329
330
		// Reset all Jetpack options
331
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
332
			'methods' => WP_REST_Server::EDITABLE,
333
			'callback' => __CLASS__ . '::reset_jetpack_options',
334
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
335
		) );
336
337
		// Return current Jumpstart status
338
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
339
			'methods'             => WP_REST_Server::READABLE,
340
			'callback'            => __CLASS__ . '::jumpstart_status',
341
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
342
		) );
343
344
		// Update Jumpstart
345
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
346
			'methods'             => WP_REST_Server::EDITABLE,
347
			'callback'            => __CLASS__ . '::jumpstart_toggle',
348
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
349
			'args'                => array(
350
				'active' => array(
351
					'required'          => true,
352
					'validate_callback' => __CLASS__  . '::validate_boolean',
353
				),
354
			),
355
		) );
356
357
		// Updates: get number of plugin updates available
358
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
359
			'methods' => WP_REST_Server::READABLE,
360
			'callback' => __CLASS__ . '::get_plugin_update_count',
361
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
362
		) );
363
364
		// Dismiss Jetpack Notices
365
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
366
			'methods' => WP_REST_Server::EDITABLE,
367
			'callback' => __CLASS__ . '::dismiss_notice',
368
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
369
		) );
370
371
		// Plugins: get list of all plugins.
372
		register_rest_route( 'jetpack/v4', '/plugins', array(
373
			'methods' => WP_REST_Server::READABLE,
374
			'callback' => __CLASS__ . '::get_plugins',
375
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
376
		) );
377
378
		// Plugins: check if the plugin is active.
379
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
380
			'methods' => WP_REST_Server::READABLE,
381
			'callback' => __CLASS__ . '::get_plugin',
382
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
383
		) );
384
385
		// Widgets: get information about a widget that supports it.
386
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
387
			'methods' => WP_REST_Server::READABLE,
388
			'callback' => array( $widget_endpoint, 'process' ),
389
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
390
		) );
391
392
		// Site Verify: check if the site is verified, and a get verification token if not
393
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
394
			'methods' => WP_REST_Server::READABLE,
395
			'callback' => __CLASS__ . '::is_site_verified_and_token',
396
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
397
		) );
398
399
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
400
			'methods' => WP_REST_Server::READABLE,
401
			'callback' => __CLASS__ . '::is_site_verified_and_token',
402
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
403
		) );
404
405
		// Site Verify: tell a service to verify the site
406
		register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
407
			'methods' => WP_REST_Server::EDITABLE,
408
			'callback' => __CLASS__ . '::verify_site',
409
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
410
			'args' => array(
411
				'keyring_id' => array(
412
					'required'          => true,
413
					'type'              => 'integer',
414
					'validate_callback' => __CLASS__  . '::validate_posint',
415
				),
416
			)
417
		) );
418
	}
419
420
	public static function get_plans( $request ) {
421
		$request = Jetpack_Client::wpcom_json_api_request_as_user(
422
			'/plans?_locale=' . get_user_locale(),
423
			'2',
424
			array(
425
				'method'  => 'GET',
426
				'headers' => array(
427
					'X-Forwarded-For' => Jetpack::current_user_ip( true ),
428
				),
429
			)
430
		);
431
432
		$body = wp_remote_retrieve_body( $request );
433
		if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
434
			$data = $body;
435
		} else {
436
			// something went wrong so we'll just return the response without caching
437
			return $body;
438
		}
439
440
		return $data;
441
	}
442
443
	/**
444
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
445
	 *
446
	 * @param $request WP_REST_Request
447
	 *
448
	 * @return array An array of jitms
449
	 */
450
	public static function get_jitm_message( $request ) {
451
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
452
453
		$jitm = Jetpack_JITM::init();
454
455
		if ( ! $jitm ) {
456
			return array();
457
		}
458
459
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) );
460
	}
461
462
	/**
463
	 * Dismisses a jitm
464
	 * @param $request WP_REST_Request The request
465
	 *
466
	 * @return bool Always True
467
	 */
468
	public static function delete_jitm_message( $request ) {
469
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
470
471
		$jitm = Jetpack_JITM::init();
472
473
		if ( ! $jitm ) {
474
			return true;
475
		}
476
477
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
478
	}
479
480
	/**
481
	 * Handles verification that a site is registered
482
	 *
483
	 * @since 5.4.0
484
	 *
485
	 * @param WP_REST_Request $request The request sent to the WP REST API.
486
	 *
487
	 * @return array|wp-error
488
	 */
489
	public static function verify_registration( $request ) {
490
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
491
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
492
		$result = $xmlrpc_server->verify_registration( array( $request['secret_1'], $request['state'] ) );
493
494
		if ( is_a( $result, 'IXR_Error' ) ) {
495
			$result = new WP_Error( $result->code, $result->message );
496
		}
497
498
		return $result;
499
	}
500
501
502
	/**
503
	 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
504
	 *  keyring to use to get the token if it is not
505
	 *
506
	 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
507
	 *
508
	 * @since 6.6.0
509
	 *
510
	 * @param WP_REST_Request $request The request sent to the WP REST API.
511
	 *
512
	 * @return array|wp-error
513
	 */
514
	public static function is_site_verified_and_token( $request ) {
515
		/**
516
		 * Return an error if the site uses a Maintenance / Coming Soon plugin
517
		 * and if the plugin is configured to make the site private.
518
		 *
519
		 * We currently handle the following plugins:
520
		 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
521
		 * - https://wordpress.org/plugins/mojo-under-construction
522
		 * - https://wordpress.org/plugins/under-construction-page
523
		 * - https://wordpress.org/plugins/ultimate-under-construction
524
		 * - https://wordpress.org/plugins/coming-soon
525
		 *
526
		 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
527
		 * If the filter returns true, we will consider the site as under construction.
528
		 */
529
		$mm_coming_soon                       = get_option( 'mm_coming_soon', null );
530
		$under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
531
		$ucp_options                          = get_option( 'ucp_options', array() );
532
		$uuc_settings                         = get_option( 'uuc_settings', array() );
533
		$csp4                                 = get_option( 'seed_csp4_settings_content', array() );
534
		if (
535
			( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
536
			|| Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
537
			|| ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
538
			|| ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
539
			|| ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) &&  isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
540
			/**
541
			 * Allow plugins to mark a site as "under construction".
542
			 *
543
			 * @since 6.7.0
544
			 *
545
			 * @param false bool Is the site under construction? Default to false.
546
			 */
547
			|| true === apply_filters( 'jetpack_is_under_construction_plugin', false )
548
		) {
549
			return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
550
		}
551
552
		Jetpack::load_xml_rpc_client();
553
 		$xml = new Jetpack_IXR_Client( array(
554
 			'user_id' => get_current_user_id(),
555
		) );
556
557
		$args = array(
558
			'user_id' => get_current_user_id(),
559
			'service' => $request[ 'service' ],
560
		);
561
562
		if ( isset( $request[ 'keyring_id' ] ) ) {
563
			$args[ 'keyring_id' ] = $request[ 'keyring_id' ];
564
		}
565
566
		$xml->query( 'jetpack.isSiteVerified', $args );
567
568
		if ( $xml->isError() ) {
569
			return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
570
		} else {
571
			return $xml->getResponse();
572
		}
573
	}
574
575
576
577
	public static function verify_site( $request ) {
578
		Jetpack::load_xml_rpc_client();
579
		$xml = new Jetpack_IXR_Client( array(
580
			'user_id' => get_current_user_id(),
581
		) );
582
583
		$params = $request->get_json_params();
584
585
		$xml->query( 'jetpack.verifySite', array(
586
				'user_id' => get_current_user_id(),
587
				'service' => $request[ 'service' ],
588
				'keyring_id' => $params[ 'keyring_id' ],
589
			)
590
		);
591
592
		if ( $xml->isError() ) {
593
			return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
594
		} else {
595
			$response = $xml->getResponse();
596
597
			if ( ! empty( $response['errors'] ) ) {
598
				$error = new WP_Error;
599
				$error->errors = $response['errors'];
600
				return $error;
601
			}
602
603
			return $response;
604
		}
605
	}
606
607
	/**
608
	 * Handles verification that a site is registered
609
	 *
610
	 * @since 5.4.0
611
	 *
612
	 * @param WP_REST_Request $request The request sent to the WP REST API.
613
	 *
614
	 * @return array|wp-error
615
	 */
616
	 public static function remote_authorize( $request ) {
617
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
618
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
619
		$result = $xmlrpc_server->remote_authorize( $request );
620
621
		if ( is_a( $result, 'IXR_Error' ) ) {
622
			$result = new WP_Error( $result->code, $result->message );
623
		}
624
625
		return $result;
626
	 }
627
628
	/**
629
	 * Handles dismissing of Jetpack Notices
630
	 *
631
	 * @since 4.3.0
632
	 *
633
	 * @param WP_REST_Request $request The request sent to the WP REST API.
634
	 *
635
	 * @return array|wp-error
636
	 */
637
	public static function dismiss_notice( $request ) {
638
		$notice = $request['notice'];
639
640
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
641
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
642
		}
643
644
		if ( isset( $notice ) && ! empty( $notice ) ) {
645
			switch( $notice ) {
646
				case 'feedback_dash_request':
647
				case 'welcome':
648
					$notices = get_option( 'jetpack_dismissed_notices', array() );
649
					$notices[ $notice ] = true;
650
					update_option( 'jetpack_dismissed_notices', $notices );
651
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
652
653
				default:
654
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
655
			}
656
		}
657
658
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
659
	}
660
661
	/**
662
	 * Verify that the user can disconnect the site.
663
	 *
664
	 * @since 4.3.0
665
	 *
666
	 * @return bool|WP_Error True if user is able to disconnect the site.
667
	 */
668
	public static function disconnect_site_permission_callback() {
669
		if ( current_user_can( 'jetpack_disconnect' ) ) {
670
			return true;
671
		}
672
673
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
674
675
	}
676
677
	/**
678
	 * Verify that the user can get a connect/link URL
679
	 *
680
	 * @since 4.3.0
681
	 *
682
	 * @return bool|WP_Error True if user is able to disconnect the site.
683
	 */
684 View Code Duplication
	public static function connect_url_permission_callback() {
685
		if ( current_user_can( 'jetpack_connect_user' ) ) {
686
			return true;
687
		}
688
689
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
690
691
	}
692
693
	/**
694
	 * Verify that a user can get the data about the current user.
695
	 * Only those who can connect.
696
	 *
697
	 * @since 4.3.0
698
	 *
699
	 * @uses Jetpack::is_user_connected();
700
	 *
701
	 * @return bool|WP_Error True if user is able to unlink.
702
	 */
703 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
704
		if ( current_user_can( 'jetpack_connect_user' ) ) {
705
			return true;
706
		}
707
708
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
709
	}
710
711
	/**
712
	 * Check that user has permission to change the master user.
713
	 *
714
	 * @since 6.2.0
715
	 *
716
	 * @return bool|WP_Error True if user is able to change master user.
717
	 */
718 View Code Duplication
	public static function set_connection_owner_permission_callback() {
719
		if ( get_current_user_id() === Jetpack_Options::get_option( 'master_user' ) ) {
720
			return true;
721
		}
722
723
		return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
724
	}
725
726
	/**
727
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
728
	 *
729
	 * @since 4.3.0
730
	 *
731
	 * @uses Jetpack::is_user_connected();
732
	 *
733
	 * @return bool|WP_Error True if user is able to unlink.
734
	 */
735 View Code Duplication
	public static function unlink_user_permission_callback() {
736
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
737
			return true;
738
		}
739
740
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
741
	}
742
743
	/**
744
	 * Verify that user can manage Jetpack modules.
745
	 *
746
	 * @since 4.3.0
747
	 *
748
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
749
	 */
750
	public static function manage_modules_permission_check() {
751
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
752
			return true;
753
		}
754
755
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
756
	}
757
758
	/**
759
	 * Verify that user can update Jetpack modules.
760
	 *
761
	 * @since 4.3.0
762
	 *
763
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
764
	 */
765 View Code Duplication
	public static function configure_modules_permission_check() {
766
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
767
			return true;
768
		}
769
770
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
771
	}
772
773
	/**
774
	 * Verify that user can view Jetpack admin page.
775
	 *
776
	 * @since 4.3.0
777
	 *
778
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
779
	 */
780 View Code Duplication
	public static function view_admin_page_permission_check() {
781
		if ( current_user_can( 'jetpack_admin_page' ) ) {
782
			return true;
783
		}
784
785
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
786
	}
787
788
	/**
789
	 * Verify that user can mitigate an identity crisis.
790
	 *
791
	 * @since 4.4.0
792
	 *
793
	 * @return bool Whether user has capability 'jetpack_disconnect'.
794
	 */
795
	public static function identity_crisis_mitigation_permission_check() {
796
		if ( current_user_can( 'jetpack_disconnect' ) ) {
797
			return true;
798
		}
799
800
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
801
	}
802
803
	/**
804
	 * Verify that user can update Jetpack general settings.
805
	 *
806
	 * @since 4.3.0
807
	 *
808
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
809
	 */
810 View Code Duplication
	public static function update_settings_permission_check() {
811
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
812
			return true;
813
		}
814
815
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
816
	}
817
818
	/**
819
	 * Verify that user can view Jetpack admin page and can activate plugins.
820
	 *
821
	 * @since 4.3.0
822
	 *
823
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
824
	 */
825 View Code Duplication
	public static function activate_plugins_permission_check() {
826
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
827
			return true;
828
		}
829
830
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
831
	}
832
833
	/**
834
	 * Contextual HTTP error code for authorization failure.
835
	 *
836
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
837
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
838
	 *
839
	 * @since 4.3.0
840
	 *
841
	 * @return int
842
	 */
843
	public static function rest_authorization_required_code() {
844
		return is_user_logged_in() ? 403 : 401;
845
	}
846
847
	/**
848
	 * Get connection status for this Jetpack site.
849
	 *
850
	 * @since 4.3.0
851
	 *
852
	 * @return bool True if site is connected
853
	 */
854
	public static function jetpack_connection_status() {
855
		return rest_ensure_response( array(
856
				'isActive'  => Jetpack::is_active(),
857
				'isStaging' => Jetpack::is_staging_site(),
858
				'devMode'   => array(
859
					'isActive' => Jetpack::is_development_mode(),
860
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
861
					'url'      => site_url() && false === strpos( site_url(), '.' ),
862
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
863
				),
864
			)
865
		);
866
	}
867
868
	public static function rewind_data() {
869
		$site_id = Jetpack_Options::get_option( 'id' );
870
871
		if ( ! $site_id ) {
872
			return new WP_Error( 'site_id_missing' );
873
		}
874
875
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' );
876
877
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
878
			return new WP_Error( 'rewind_data_fetch_failed' );
879
		}
880
881
		$body = wp_remote_retrieve_body( $response );
882
883
		return json_decode( $body );
884
	}
885
886
	/**
887
	 * Get rewind data
888
	 *
889
	 * @since 5.7.0
890
	 *
891
	 * @return array Array of rewind properties.
892
	 */
893
	public static function get_rewind_data() {
894
		$rewind_data = self::rewind_data();
895
896 View Code Duplication
		if ( ! is_wp_error( $rewind_data ) ) {
897
			return rest_ensure_response( array(
898
					'code' => 'success',
899
					'message' => esc_html__( 'Rewind data correctly received.', 'jetpack' ),
900
					'data' => wp_json_encode( $rewind_data ),
901
				)
902
			);
903
		}
904
905 View Code Duplication
		if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) {
906
			return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
907
		}
908
909 View Code Duplication
		if ( $rewind_data->get_error_code() === 'site_id_missing' ) {
910
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
911
		}
912
913
		return new WP_Error(
914
			'error_get_rewind_data',
915
			esc_html__( 'Could not retrieve Rewind data.', 'jetpack' ),
916
			array( 'status' => 500 )
917
		);
918
	}
919
920
	/**
921
	 * Disconnects Jetpack from the WordPress.com Servers
922
	 *
923
	 * @uses Jetpack::disconnect();
924
	 * @since 4.3.0
925
	 *
926
	 * @param WP_REST_Request $request The request sent to the WP REST API.
927
	 *
928
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
929
	 */
930 View Code Duplication
	public static function disconnect_site( $request ) {
931
932
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
933
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
934
		}
935
936
		if ( Jetpack::is_active() ) {
937
			Jetpack::disconnect();
938
			return rest_ensure_response( array( 'code' => 'success' ) );
939
		}
940
941
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
942
	}
943
944
	/**
945
	 * Gets a new connect raw URL with fresh nonce.
946
	 *
947
	 * @uses Jetpack::disconnect();
948
	 * @since 4.3.0
949
	 *
950
	 * @param WP_REST_Request $request The request sent to the WP REST API.
951
	 *
952
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
953
	 */
954
	public static function build_connect_url() {
955
		$url = Jetpack::init()->build_connect_url( true, false, false );
956
		if ( $url ) {
957
			return rest_ensure_response( $url );
958
		}
959
960
		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 ) );
961
	}
962
963
	/**
964
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
965
	 * Information about the master/primary user.
966
	 * Information about the current user.
967
	 *
968
	 * @since 4.3.0
969
	 *
970
	 * @param WP_REST_Request $request The request sent to the WP REST API.
971
	 *
972
	 * @return object
973
	 */
974
	public static function get_user_connection_data() {
975
		require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' );
976
977
		$response = array(
978
//			'othersLinked' => Jetpack::get_other_linked_admins(),
979
			'currentUser'  => jetpack_current_user_data(),
980
		);
981
		return rest_ensure_response( $response );
982
	}
983
984
	/**
985
	 * Change the master user.
986
	 *
987
	 * @since 6.2.0
988
	 *
989
	 * @param WP_REST_Request $request The request sent to the WP REST API.
990
	 *
991
	 * @return bool|WP_Error True if owner successfully changed.
992
	 */
993
	public static function set_connection_owner( $request ) {
994
		if ( ! isset( $request['owner'] ) ) {
995
			return new WP_Error(
996
				'invalid_param',
997
				esc_html__( 'Invalid Parameter', 'jetpack' ),
998
				array( 'status' => 400 )
999
			);
1000
		}
1001
1002
		$new_owner_id = $request['owner'];
1003
		if ( ! user_can( $new_owner_id, 'administrator' ) ) {
1004
			return new WP_Error(
1005
				'new_owner_not_admin',
1006
				esc_html__( 'New owner is not admin', 'jetpack' ),
1007
				array( 'status' => 400 )
1008
			);
1009
		}
1010
1011
		if ( $new_owner_id === get_current_user_id() ) {
1012
			return new WP_Error(
1013
				'new_owner_is_current_user',
1014
				esc_html__( 'New owner is same as current user', 'jetpack' ),
1015
				array( 'status' => 400 )
1016
			);
1017
		}
1018
1019
		if ( ! Jetpack::is_user_connected( $new_owner_id ) ) {
1020
			return new WP_Error(
1021
				'new_owner_not_connected',
1022
				esc_html__( 'New owner is not connected', 'jetpack' ),
1023
				array( 'status' => 400 )
1024
			);
1025
		}
1026
1027
		// Update the master user in Jetpack
1028
		$updated = Jetpack_Options::update_option( 'master_user', $new_owner_id );
1029
1030
		// Notify WPCOM about the master user change
1031
		Jetpack::load_xml_rpc_client();
1032
		$xml = new Jetpack_IXR_Client( array(
1033
			'user_id' => get_current_user_id(),
1034
		) );
1035
		$xml->query( 'jetpack.switchBlogOwner', array(
1036
			'new_blog_owner' => $new_owner_id,
1037
		) );
1038
1039
		if ( $updated && ! $xml->isError() ) {
1040
			return rest_ensure_response(
1041
				array(
1042
					'code' => 'success',
1043
				)
1044
			);
1045
		}
1046
		return new WP_Error(
1047
			'error_setting_new_owner',
1048
			esc_html__( 'Could not confirm new owner.', 'jetpack' ),
1049
			array( 'status' => 500 )
1050
		);
1051
	}
1052
1053
	/**
1054
	 * Unlinks current user from the WordPress.com Servers.
1055
	 *
1056
	 * @since 4.3.0
1057
	 * @uses  Jetpack::unlink_user
1058
	 *
1059
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1060
	 *
1061
	 * @return bool|WP_Error True if user successfully unlinked.
1062
	 */
1063 View Code Duplication
	public static function unlink_user( $request ) {
1064
1065
		if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) {
1066
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1067
		}
1068
1069
		if ( Jetpack::unlink_user() ) {
1070
			return rest_ensure_response(
1071
				array(
1072
					'code' => 'success'
1073
				)
1074
			);
1075
		}
1076
1077
		return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1078
	}
1079
1080
	/**
1081
	 * Gets current user's tracking settings.
1082
	 *
1083
	 * @since 6.0.0
1084
	 *
1085
	 * @param  WP_REST_Request $request The request sent to the WP REST API.
1086
	 *
1087
	 * @return WP_REST_Response|WP_Error Response, else error.
1088
	 */
1089 View Code Duplication
	public static function get_user_tracking_settings( $request ) {
1090
		if ( ! Jetpack::is_user_connected() ) {
1091
			$response = array(
1092
				'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1093
			);
1094
		} else {
1095
			$response = Jetpack_Client::wpcom_json_api_request_as_user(
1096
				'/jetpack-user-tracking',
1097
				'v2',
1098
				array(
1099
					'method'  => 'GET',
1100
					'headers' => array(
1101
						'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1102
					),
1103
				)
1104
			);
1105
			if ( ! is_wp_error( $response ) ) {
1106
				$response = json_decode( wp_remote_retrieve_body( $response ), true );
1107
			}
1108
		}
1109
1110
		return rest_ensure_response( $response );
1111
	}
1112
1113
	/**
1114
	 * Updates current user's tracking settings.
1115
	 *
1116
	 * @since 6.0.0
1117
	 *
1118
	 * @param  WP_REST_Request $request The request sent to the WP REST API.
1119
	 *
1120
	 * @return WP_REST_Response|WP_Error Response, else error.
1121
	 */
1122 View Code Duplication
	public static function update_user_tracking_settings( $request ) {
1123
		if ( ! Jetpack::is_user_connected() ) {
1124
			$response = array(
1125
				'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1126
			);
1127
		} else {
1128
			$response = Jetpack_Client::wpcom_json_api_request_as_user(
1129
				'/jetpack-user-tracking',
1130
				'v2',
1131
				array(
1132
					'method'  => 'PUT',
1133
					'headers' => array(
1134
						'Content-Type'    => 'application/json',
1135
						'X-Forwarded-For' => Jetpack::current_user_ip( true ),
1136
					),
1137
				),
1138
				wp_json_encode( $request->get_params() )
1139
			);
1140
			if ( ! is_wp_error( $response ) ) {
1141
				$response = json_decode( wp_remote_retrieve_body( $response ), true );
1142
			}
1143
		}
1144
1145
		return rest_ensure_response( $response );
1146
	}
1147
1148
	/**
1149
	 * Fetch site data from .com including the site's current plan.
1150
	 *
1151
	 * @since 5.5.0
1152
	 *
1153
	 * @return array Array of site properties.
1154
	 */
1155
	public static function site_data() {
1156
		$site_id = Jetpack_Options::get_option( 'id' );
1157
1158
		if ( ! $site_id ) {
1159
			 new WP_Error( 'site_id_missing' );
1160
		}
1161
1162
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1' );
1163
1164
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1165
			return new WP_Error( 'site_data_fetch_failed' );
1166
		}
1167
1168
		// Save plan details in the database for future use without API calls
1169
		$results = json_decode( $response['body'], true );
1170
1171
		if ( is_array( $results ) && isset( $results['plan'] ) ) {
1172
1173
			// Set flag for newly purchased plan
1174
			$current_plan = Jetpack::get_active_plan();
1175
			if ( $current_plan['product_slug'] !== $results['plan']['product_slug'] && 'jetpack_free' !== $results['plan']['product_slug'] ) {
1176
				update_option( 'show_welcome_for_new_plan', true ) ;
1177
			}
1178
1179
			update_option( 'jetpack_active_plan', $results['plan'] );
1180
		}
1181
		$body = wp_remote_retrieve_body( $response );
1182
1183
		return json_decode( $body );
1184
	}
1185
	/**
1186
	 * Get site data, including for example, the site's current plan.
1187
	 *
1188
	 * @since 4.3.0
1189
	 *
1190
	 * @return array Array of site properties.
1191
	 */
1192
	public static function get_site_data() {
1193
		$site_data = self::site_data();
1194
1195 View Code Duplication
		if ( ! is_wp_error( $site_data ) ) {
1196
			return rest_ensure_response( array(
1197
					'code' => 'success',
1198
					'message' => esc_html__( 'Site data correctly received.', 'jetpack' ),
1199
					'data' => json_encode( $site_data ),
1200
				)
1201
			);
1202
		}
1203 View Code Duplication
		if ( $site_data->get_error_code() === 'site_data_fetch_failed' ) {
1204
			return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1205
		}
1206
1207 View Code Duplication
		if ( $site_data->get_error_code() === 'site_id_missing' ) {
1208
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1209
		}
1210
	}
1211
1212
	/**
1213
	 * Handles identity crisis mitigation, confirming safe mode for this site.
1214
	 *
1215
	 * @since 4.4.0
1216
	 *
1217
	 * @return bool | WP_Error True if option is properly set.
1218
	 */
1219
	public static function confirm_safe_mode() {
1220
		$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
1221
		if ( $updated ) {
1222
			return rest_ensure_response(
1223
				array(
1224
					'code' => 'success'
1225
				)
1226
			);
1227
		}
1228
		return new WP_Error(
1229
			'error_setting_jetpack_safe_mode',
1230
			esc_html__( 'Could not confirm safe mode.', 'jetpack' ),
1231
			array( 'status' => 500 )
1232
		);
1233
	}
1234
1235
	/**
1236
	 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
1237
	 *
1238
	 * @since 4.4.0
1239
	 *
1240
	 * @return bool | WP_Error True if option is properly set.
1241
	 */
1242
	public static function migrate_stats_and_subscribers() {
1243
		if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
1244
			return new WP_Error(
1245
				'error_deleting_sync_error_idc',
1246
				esc_html__( 'Could not delete sync error option.', 'jetpack' ),
1247
				array( 'status' => 500 )
1248
			);
1249
		}
1250
1251
		if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
1252
			return rest_ensure_response(
1253
				array(
1254
					'code' => 'success'
1255
				)
1256
			);
1257
		}
1258
		return new WP_Error(
1259
			'error_setting_jetpack_migrate',
1260
			esc_html__( 'Could not confirm migration.', 'jetpack' ),
1261
			array( 'status' => 500 )
1262
		);
1263
	}
1264
1265
	/**
1266
	 * This IDC resolution will disconnect the site and re-connect to a completely new
1267
	 * and separate shadow site than the original.
1268
	 *
1269
	 * It will first will disconnect the site without phoning home as to not disturb the production site.
1270
	 * It then builds a fresh connection URL and sends it back along with the response.
1271
	 *
1272
	 * @since 4.4.0
1273
	 * @return bool|WP_Error
1274
	 */
1275
	public static function start_fresh_connection() {
1276
		// First clear the options / disconnect.
1277
		Jetpack::disconnect();
1278
		return self::build_connect_url();
1279
	}
1280
1281
	/**
1282
	 * Reset Jetpack options
1283
	 *
1284
	 * @since 4.3.0
1285
	 *
1286
	 * @param WP_REST_Request $request {
1287
	 *     Array of parameters received by request.
1288
	 *
1289
	 *     @type string $options Available options to reset are options|modules
1290
	 * }
1291
	 *
1292
	 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
1293
	 */
1294
	public static function reset_jetpack_options( $request ) {
1295
1296
		if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) {
1297
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1298
		}
1299
1300
		if ( isset( $request['options'] ) ) {
1301
			$data = $request['options'];
1302
1303
			switch( $data ) {
1304
				case ( 'options' ) :
1305
					$options_to_reset = Jetpack::get_jetpack_options_for_reset();
1306
1307
					// Reset the Jetpack options
1308
					foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
1309
						Jetpack_Options::delete_option( $option_to_reset );
1310
					}
1311
1312
					foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
1313
						delete_option( $option_to_reset );
1314
					}
1315
1316
					// Reset to default modules
1317
					$default_modules = Jetpack::get_default_modules();
1318
					Jetpack::update_active_modules( $default_modules );
1319
1320
					// Jumpstart option is special
1321
					Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
1322
					return rest_ensure_response( array(
1323
						'code' 	  => 'success',
1324
						'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ),
1325
					) );
1326
					break;
1327
1328
				case 'modules':
1329
					$default_modules = Jetpack::get_default_modules();
1330
					Jetpack::update_active_modules( $default_modules );
1331
					return rest_ensure_response( array(
1332
						'code' 	  => 'success',
1333
						'message' => esc_html__( 'Modules reset to default.', 'jetpack' ),
1334
					) );
1335
					break;
1336
1337
				default:
1338
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1339
			}
1340
		}
1341
1342
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
1343
	}
1344
1345
	/**
1346
	 * Retrieves the current status of Jumpstart.
1347
	 *
1348
	 * @since 4.5.0
1349
	 *
1350
	 * @return bool
1351
	 */
1352
	public static function jumpstart_status() {
1353
		return array(
1354
			'status' => Jetpack_Options::get_option( 'jumpstart' )
1355
		);
1356
	}
1357
1358
	/**
1359
	 * Toggles activation or deactivation of the JumpStart
1360
	 *
1361
	 * @since 4.3.0
1362
	 *
1363
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1364
	 *
1365
	 * @return bool|WP_Error True if toggling Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
1366
	 */
1367
	public static function jumpstart_toggle( $request ) {
1368
1369
		if ( $request[ 'active' ] ) {
1370
			return self::jumpstart_activate( $request );
1371
		} else {
1372
			return self::jumpstart_deactivate( $request );
1373
		}
1374
	}
1375
1376
	/**
1377
	 * Activates a series of valid Jetpack modules and initializes some options.
1378
	 *
1379
	 * @since 4.3.0
1380
	 *
1381
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1382
	 *
1383
	 * @return bool|WP_Error True if Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
1384
	 */
1385
	public static function jumpstart_activate( $request ) {
1386
		$modules = Jetpack::get_available_modules();
1387
		$activate_modules = array();
1388
		foreach ( $modules as $module ) {
1389
			$module_info = Jetpack::get_module( $module );
1390
			if ( isset( $module_info['feature'] ) && is_array( $module_info['feature'] ) && in_array( 'Jumpstart', $module_info['feature'] ) ) {
1391
				$activate_modules[] = $module;
1392
			}
1393
		}
1394
1395
		// Collect success/error messages like modules that are properly activated.
1396
		$result = array(
1397
			'activated_modules' => array(),
1398
			'failed_modules'    => array(),
1399
		);
1400
1401
		// Update the jumpstart option
1402
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
1403
			$result['jumpstart_activated'] = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' );
1404
		}
1405
1406
		// Check for possible conflicting plugins
1407
		$module_slugs_filtered = Jetpack::init()->filter_default_modules( $activate_modules );
1408
1409
		foreach ( $module_slugs_filtered as $module_slug ) {
1410
			Jetpack::log( 'activate', $module_slug );
1411
			if ( Jetpack::activate_module( $module_slug, false, false ) ) {
1412
				$result['activated_modules'][] = $module_slug;
1413
			} else {
1414
				$result['failed_modules'][] = $module_slug;
1415
			}
1416
		}
1417
1418
		// Set the default sharing buttons and set to display on posts if none have been set.
1419
		$sharing_services = get_option( 'sharing-services' );
1420
		$sharing_options  = get_option( 'sharing-options' );
1421
		if ( empty( $sharing_services['visible'] ) ) {
1422
			// Default buttons to set
1423
			$visible = array(
1424
				'twitter',
1425
				'facebook',
1426
				'google-plus-1',
1427
			);
1428
			$hidden = array();
1429
1430
			// Set some sharing settings
1431
			if ( class_exists( 'Sharing_Service' ) ) {
1432
				$sharing = new Sharing_Service();
1433
				$sharing_options['global'] = array(
1434
					'button_style'  => 'icon',
1435
					'sharing_label' => $sharing->default_sharing_label,
1436
					'open_links'    => 'same',
1437
					'show'          => array( 'post' ),
1438
					'custom'        => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array()
1439
				);
1440
1441
				$result['sharing_options']  = update_option( 'sharing-options', $sharing_options );
1442
				$result['sharing_services'] = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) );
1443
			}
1444
		}
1445
1446
		// If all Jumpstart modules were activated
1447 View Code Duplication
		if ( empty( $result['failed_modules'] ) ) {
1448
			return rest_ensure_response( array(
1449
				'code' 	  => 'success',
1450
				'message' => esc_html__( 'Jumpstart done.', 'jetpack' ),
1451
				'data'    => $result,
1452
			) );
1453
		}
1454
1455
		return new WP_Error( 'jumpstart_failed', esc_html( sprintf( _n( 'Jumpstart failed activating this module: %s.', 'Jumpstart failed activating these modules: %s.', count( $result['failed_modules'] ), 'jetpack' ), join( ', ', $result['failed_modules'] ) ) ), array( 'status' => 400 ) );
1456
	}
1457
1458
	/**
1459
	 * Dismisses Jumpstart so user is not prompted to go through it again.
1460
	 *
1461
	 * @since 4.3.0
1462
	 *
1463
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1464
	 *
1465
	 * @return bool|WP_Error True if Jumpstart was disabled or was nothing to dismiss. Otherwise, a WP_Error instance with a message.
1466
	 */
1467
	public static function jumpstart_deactivate( $request ) {
1468
1469
		// If dismissed, flag the jumpstart option as such.
1470
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
1471
			if ( Jetpack_Options::update_option( 'jumpstart', 'jumpstart_dismissed' ) ) {
1472
				return rest_ensure_response( array(
1473
					'code' 	  => 'success',
1474
					'message' => esc_html__( 'Jumpstart dismissed.', 'jetpack' ),
1475
				) );
1476
			} else {
1477
				return new WP_Error( 'jumpstart_failed_dismiss', esc_html__( 'Jumpstart could not be dismissed.', 'jetpack' ), array( 'status' => 400 ) );
1478
			}
1479
		}
1480
1481
		// If this was not a new connection and there was nothing to dismiss, don't fail.
1482
		return rest_ensure_response( array(
1483
			'code' 	  => 'success',
1484
			'message' => esc_html__( 'Nothing to dismiss. This was not a new connection.', 'jetpack' ),
1485
		) );
1486
	}
1487
1488
	/**
1489
	 * Get the query parameters to update module options or general settings.
1490
	 *
1491
	 * @since 4.3.0
1492
	 * @since 4.4.0 Accepts a $selector parameter.
1493
	 *
1494
	 * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'.
1495
	 *
1496
	 * @return array
1497
	 */
1498
	public static function get_updateable_parameters( $selector = '' ) {
1499
		$parameters = array(
1500
			'context'     => array(
1501
				'default' => 'edit',
1502
			),
1503
		);
1504
1505
		return array_merge( $parameters, self::get_updateable_data_list( $selector ) );
1506
	}
1507
1508
	/**
1509
	 * Returns a list of module options or general settings that can be updated.
1510
	 *
1511
	 * @since 4.3.0
1512
	 * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list.
1513
	 *
1514
	 * @param string|array $selector Module slug, 'any', or an array of parameters.
1515
	 *                               If empty, it's assumed we're updating a module and we'll try to get its slug.
1516
	 *                               If 'any' the full list is returned.
1517
	 *                               If it's an array of parameters, includes the elements by matching keys.
1518
	 *
1519
	 * @return array
1520
	 */
1521
	public static function get_updateable_data_list( $selector = '' ) {
1522
1523
		$options = array(
1524
1525
			// Carousel
1526
			'carousel_background_color' => array(
1527
				'description'       => esc_html__( 'Color scheme.', 'jetpack' ),
1528
				'type'              => 'string',
1529
				'default'           => 'black',
1530
				'enum'              => array(
1531
					'black',
1532
					'white',
1533
				),
1534
				'enum_labels' => array(
1535
					'black' => esc_html__( 'Black', 'jetpack' ),
1536
					'white' => esc_html__( 'White', 'jetpack' ),
1537
				),
1538
				'validate_callback' => __CLASS__ . '::validate_list_item',
1539
				'jp_group'          => 'carousel',
1540
			),
1541
			'carousel_display_exif' => array(
1542
				'description'       => wp_kses( sprintf( __( 'Show photo metadata (<a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ), array( 'a' => array( 'href' => true, 'target' => true ) ) ),
1543
				'type'              => 'boolean',
1544
				'default'           => 0,
1545
				'validate_callback' => __CLASS__ . '::validate_boolean',
1546
				'jp_group'          => 'carousel',
1547
			),
1548
1549
			// Comments
1550
			'highlander_comment_form_prompt' => array(
1551
				'description'       => esc_html__( 'Greeting Text', 'jetpack' ),
1552
				'type'              => 'string',
1553
				'default'           => esc_html__( 'Leave a Reply', 'jetpack' ),
1554
				'sanitize_callback' => 'sanitize_text_field',
1555
				'jp_group'          => 'comments',
1556
			),
1557
			'jetpack_comment_form_color_scheme' => array(
1558
				'description'       => esc_html__( "Color scheme", 'jetpack' ),
1559
				'type'              => 'string',
1560
				'default'           => 'light',
1561
				'enum'              => array(
1562
					'light',
1563
					'dark',
1564
					'transparent',
1565
				),
1566
				'enum_labels' => array(
1567
					'light'       => esc_html__( 'Light', 'jetpack' ),
1568
					'dark'        => esc_html__( 'Dark', 'jetpack' ),
1569
					'transparent' => esc_html__( 'Transparent', 'jetpack' ),
1570
				),
1571
				'validate_callback' => __CLASS__ . '::validate_list_item',
1572
				'jp_group'          => 'comments',
1573
			),
1574
1575
			// Custom Content Types
1576
			'jetpack_portfolio' => array(
1577
				'description'       => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1578
				'type'              => 'boolean',
1579
				'default'           => 0,
1580
				'validate_callback' => __CLASS__ . '::validate_boolean',
1581
				'jp_group'          => 'custom-content-types',
1582
			),
1583
			'jetpack_portfolio_posts_per_page' => array(
1584
				'description'       => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1585
				'type'              => 'integer',
1586
				'default'           => 10,
1587
				'validate_callback' => __CLASS__ . '::validate_posint',
1588
				'jp_group'          => 'custom-content-types',
1589
			),
1590
			'jetpack_testimonial' => array(
1591
				'description'       => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1592
				'type'              => 'boolean',
1593
				'default'           => 0,
1594
				'validate_callback' => __CLASS__ . '::validate_boolean',
1595
				'jp_group'          => 'custom-content-types',
1596
			),
1597
			'jetpack_testimonial_posts_per_page' => array(
1598
				'description'       => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1599
				'type'              => 'integer',
1600
				'default'           => 10,
1601
				'validate_callback' => __CLASS__ . '::validate_posint',
1602
				'jp_group'          => 'custom-content-types',
1603
			),
1604
1605
			// Galleries
1606
			'tiled_galleries' => array(
1607
				'description'       => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1608
				'type'              => 'boolean',
1609
				'default'           => 0,
1610
				'validate_callback' => __CLASS__ . '::validate_boolean',
1611
				'jp_group'          => 'tiled-gallery',
1612
			),
1613
1614
			'gravatar_disable_hovercards' => array(
1615
				'description'       => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1616
				'type'              => 'string',
1617
				'default'           => 'enabled',
1618
				// Not visible. This is used as the checkbox value.
1619
				'enum'              => array(
1620
					'enabled',
1621
					'disabled',
1622
				),
1623
				'enum_labels' => array(
1624
					'enabled'  => esc_html__( 'Enabled', 'jetpack' ),
1625
					'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1626
				),
1627
				'validate_callback' => __CLASS__ . '::validate_list_item',
1628
				'jp_group'          => 'gravatar-hovercards',
1629
			),
1630
1631
			// Infinite Scroll
1632
			'infinite_scroll' => array(
1633
				'description'       => esc_html__( 'To infinity and beyond', 'jetpack' ),
1634
				'type'              => 'boolean',
1635
				'default'           => 1,
1636
				'validate_callback' => __CLASS__ . '::validate_boolean',
1637
				'jp_group'          => 'infinite-scroll',
1638
			),
1639
			'infinite_scroll_google_analytics' => array(
1640
				'description'       => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1641
				'type'              => 'boolean',
1642
				'default'           => 0,
1643
				'validate_callback' => __CLASS__ . '::validate_boolean',
1644
				'jp_group'          => 'infinite-scroll',
1645
			),
1646
1647
			// Likes
1648
			'wpl_default' => array(
1649
				'description'       => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1650
				'type'              => 'string',
1651
				'default'           => 'on',
1652
				'enum'              => array(
1653
					'on',
1654
					'off',
1655
				),
1656
				'enum_labels' => array(
1657
					'on'  => esc_html__( 'On for all posts', 'jetpack' ),
1658
					'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1659
				),
1660
				'validate_callback' => __CLASS__ . '::validate_list_item',
1661
				'jp_group'          => 'likes',
1662
			),
1663
			'social_notifications_like' => array(
1664
				'description'       => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1665
				'type'              => 'boolean',
1666
				'default'           => 1,
1667
				'validate_callback' => __CLASS__ . '::validate_boolean',
1668
				'jp_group'          => 'likes',
1669
			),
1670
1671
			// Markdown
1672
			'wpcom_publish_comments_with_markdown' => array(
1673
				'description'       => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1674
				'type'              => 'boolean',
1675
				'default'           => 0,
1676
				'validate_callback' => __CLASS__ . '::validate_boolean',
1677
				'jp_group'          => 'markdown',
1678
			),
1679
			'wpcom_publish_posts_with_markdown' => array(
1680
				'description'       => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1681
				'type'              => 'boolean',
1682
				'default'           => 0,
1683
				'validate_callback' => __CLASS__ . '::validate_boolean',
1684
				'jp_group'          => 'markdown',
1685
			),
1686
1687
			// Mobile Theme
1688
			'wp_mobile_excerpt' => array(
1689
				'description'       => esc_html__( 'Excerpts', 'jetpack' ),
1690
				'type'              => 'boolean',
1691
				'default'           => 0,
1692
				'validate_callback' => __CLASS__ . '::validate_boolean',
1693
				'jp_group'          => 'minileven',
1694
			),
1695
			'wp_mobile_featured_images' => array(
1696
				'description'       => esc_html__( 'Featured Images', 'jetpack' ),
1697
				'type'              => 'boolean',
1698
				'default'           => 0,
1699
				'validate_callback' => __CLASS__ . '::validate_boolean',
1700
				'jp_group'          => 'minileven',
1701
			),
1702
			'wp_mobile_app_promos' => array(
1703
				'description'       => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ),
1704
				'type'              => 'boolean',
1705
				'default'           => 0,
1706
				'validate_callback' => __CLASS__ . '::validate_boolean',
1707
				'jp_group'          => 'minileven',
1708
			),
1709
1710
			// Monitor
1711
			'monitor_receive_notifications' => array(
1712
				'description'       => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1713
				'type'              => 'boolean',
1714
				'default'           => 0,
1715
				'validate_callback' => __CLASS__ . '::validate_boolean',
1716
				'jp_group'          => 'monitor',
1717
			),
1718
1719
			// Post by Email
1720
			'post_by_email_address' => array(
1721
				'description'       => esc_html__( 'Email Address', 'jetpack' ),
1722
				'type'              => 'string',
1723
				'default'           => 'noop',
1724
				'enum'              => array(
1725
					'noop',
1726
					'create',
1727
					'regenerate',
1728
					'delete',
1729
				),
1730
				'enum_labels' => array(
1731
					'noop'       => '',
1732
					'create'     => esc_html__( 'Create Post by Email address', 'jetpack' ),
1733
					'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1734
					'delete'     => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1735
				),
1736
				'validate_callback' => __CLASS__ . '::validate_list_item',
1737
				'jp_group'          => 'post-by-email',
1738
			),
1739
1740
			// Protect
1741
			'jetpack_protect_key' => array(
1742
				'description'       => esc_html__( 'Protect API key', 'jetpack' ),
1743
				'type'              => 'string',
1744
				'default'           => '',
1745
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1746
				'jp_group'          => 'protect',
1747
			),
1748
			'jetpack_protect_global_whitelist' => array(
1749
				'description'       => esc_html__( 'Protect global whitelist', 'jetpack' ),
1750
				'type'              => 'string',
1751
				'default'           => '',
1752
				'validate_callback' => __CLASS__ . '::validate_string',
1753
				'sanitize_callback' => 'esc_textarea',
1754
				'jp_group'          => 'protect',
1755
			),
1756
1757
			// Sharing
1758
			'sharing_services' => array(
1759
				'description'       => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1760
				'type'              => 'object',
1761
				'default'           => array(
1762
					'visible' => array( 'twitter', 'facebook', 'google-plus-1' ),
1763
					'hidden'  => array(),
1764
				),
1765
				'validate_callback' => __CLASS__ . '::validate_services',
1766
				'jp_group'          => 'sharedaddy',
1767
			),
1768
			'button_style' => array(
1769
				'description'       => esc_html__( 'Button Style', 'jetpack' ),
1770
				'type'              => 'string',
1771
				'default'           => 'icon',
1772
				'enum'              => array(
1773
					'icon-text',
1774
					'icon',
1775
					'text',
1776
					'official',
1777
				),
1778
				'enum_labels' => array(
1779
					'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1780
					'icon'      => esc_html__( 'Icon only', 'jetpack' ),
1781
					'text'      => esc_html__( 'Text only', 'jetpack' ),
1782
					'official'  => esc_html__( 'Official buttons', 'jetpack' ),
1783
				),
1784
				'validate_callback' => __CLASS__ . '::validate_list_item',
1785
				'jp_group'          => 'sharedaddy',
1786
			),
1787
			'sharing_label' => array(
1788
				'description'       => esc_html__( 'Sharing Label', 'jetpack' ),
1789
				'type'              => 'string',
1790
				'default'           => '',
1791
				'validate_callback' => __CLASS__ . '::validate_string',
1792
				'sanitize_callback' => 'esc_html',
1793
				'jp_group'          => 'sharedaddy',
1794
			),
1795
			'show' => array(
1796
				'description'       => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1797
				'type'              => 'array',
1798
				'items'             => array(
1799
					'type' => 'string'
1800
				),
1801
				'default'           => array( 'post' ),
1802
				'validate_callback' => __CLASS__ . '::validate_sharing_show',
1803
				'jp_group'          => 'sharedaddy',
1804
			),
1805
			'jetpack-twitter-cards-site-tag' => array(
1806
				'description'       => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
1807
				'type'              => 'string',
1808
				'default'           => '',
1809
				'validate_callback' => __CLASS__ . '::validate_twitter_username',
1810
				'sanitize_callback' => 'esc_html',
1811
				'jp_group'          => 'sharedaddy',
1812
			),
1813
			'sharedaddy_disable_resources' => array(
1814
				'description'       => esc_html__( 'Disable CSS and JS', 'jetpack' ),
1815
				'type'              => 'boolean',
1816
				'default'           => 0,
1817
				'validate_callback' => __CLASS__ . '::validate_boolean',
1818
				'jp_group'          => 'sharedaddy',
1819
			),
1820
			'custom' => array(
1821
				'description'       => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
1822
				'type'              => 'object',
1823
				'default'           => array(
1824
					'sharing_name' => '',
1825
					'sharing_url'  => '',
1826
					'sharing_icon' => '',
1827
				),
1828
				'validate_callback' => __CLASS__ . '::validate_custom_service',
1829
				'jp_group'          => 'sharedaddy',
1830
			),
1831
			// Not an option, but an action that can be perfomed on the list of custom services passing the service ID.
1832
			'sharing_delete_service' => array(
1833
				'description'       => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
1834
				'type'              => 'string',
1835
				'default'           => '',
1836
				'validate_callback' => __CLASS__ . '::validate_custom_service_id',
1837
				'jp_group'          => 'sharedaddy',
1838
			),
1839
1840
			// SSO
1841
			'jetpack_sso_require_two_step' => array(
1842
				'description'       => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
1843
				'type'              => 'boolean',
1844
				'default'           => 0,
1845
				'validate_callback' => __CLASS__ . '::validate_boolean',
1846
				'jp_group'          => 'sso',
1847
			),
1848
			'jetpack_sso_match_by_email' => array(
1849
				'description'       => esc_html__( 'Match by Email', 'jetpack' ),
1850
				'type'              => 'boolean',
1851
				'default'           => 0,
1852
				'validate_callback' => __CLASS__ . '::validate_boolean',
1853
				'jp_group'          => 'sso',
1854
			),
1855
1856
			// Subscriptions
1857
			'stb_enabled' => array(
1858
				'description'       => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ),
1859
				'type'              => 'boolean',
1860
				'default'           => 1,
1861
				'validate_callback' => __CLASS__ . '::validate_boolean',
1862
				'jp_group'          => 'subscriptions',
1863
			),
1864
			'stc_enabled' => array(
1865
				'description'       => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
1866
				'type'              => 'boolean',
1867
				'default'           => 1,
1868
				'validate_callback' => __CLASS__ . '::validate_boolean',
1869
				'jp_group'          => 'subscriptions',
1870
			),
1871
1872
			// Related Posts
1873
			'show_headline' => array(
1874
				'description'       => esc_html__( 'Highlight related content with a heading', 'jetpack' ),
1875
				'type'              => 'boolean',
1876
				'default'           => 1,
1877
				'validate_callback' => __CLASS__ . '::validate_boolean',
1878
				'jp_group'          => 'related-posts',
1879
			),
1880
			'show_thumbnails' => array(
1881
				'description'       => esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
1882
				'type'              => 'boolean',
1883
				'default'           => 0,
1884
				'validate_callback' => __CLASS__ . '::validate_boolean',
1885
				'jp_group'          => 'related-posts',
1886
			),
1887
1888
			// Spelling and Grammar - After the Deadline
1889
			'onpublish' => array(
1890
				'description'       => esc_html__( 'Proofread when a post or page is first published.', 'jetpack' ),
1891
				'type'              => 'boolean',
1892
				'default'           => 0,
1893
				'validate_callback' => __CLASS__ . '::validate_boolean',
1894
				'jp_group'          => 'after-the-deadline',
1895
			),
1896
			'onupdate' => array(
1897
				'description'       => esc_html__( 'Proofread when a post or page is updated.', 'jetpack' ),
1898
				'type'              => 'boolean',
1899
				'default'           => 0,
1900
				'validate_callback' => __CLASS__ . '::validate_boolean',
1901
				'jp_group'          => 'after-the-deadline',
1902
			),
1903
			'Bias Language' => array(
1904
				'description'       => esc_html__( 'Bias Language', 'jetpack' ),
1905
				'type'              => 'boolean',
1906
				'default'           => 0,
1907
				'validate_callback' => __CLASS__ . '::validate_boolean',
1908
				'jp_group'          => 'after-the-deadline',
1909
			),
1910
			'Cliches' => array(
1911
				'description'       => esc_html__( 'Clichés', 'jetpack' ),
1912
				'type'              => 'boolean',
1913
				'default'           => 0,
1914
				'validate_callback' => __CLASS__ . '::validate_boolean',
1915
				'jp_group'          => 'after-the-deadline',
1916
			),
1917
			'Complex Expression' => array(
1918
				'description'       => esc_html__( 'Complex Phrases', 'jetpack' ),
1919
				'type'              => 'boolean',
1920
				'default'           => 0,
1921
				'validate_callback' => __CLASS__ . '::validate_boolean',
1922
				'jp_group'          => 'after-the-deadline',
1923
			),
1924
			'Diacritical Marks' => array(
1925
				'description'       => esc_html__( 'Diacritical Marks', 'jetpack' ),
1926
				'type'              => 'boolean',
1927
				'default'           => 0,
1928
				'validate_callback' => __CLASS__ . '::validate_boolean',
1929
				'jp_group'          => 'after-the-deadline',
1930
			),
1931
			'Double Negative' => array(
1932
				'description'       => esc_html__( 'Double Negatives', 'jetpack' ),
1933
				'type'              => 'boolean',
1934
				'default'           => 0,
1935
				'validate_callback' => __CLASS__ . '::validate_boolean',
1936
				'jp_group'          => 'after-the-deadline',
1937
			),
1938
			'Hidden Verbs' => array(
1939
				'description'       => esc_html__( 'Hidden Verbs', 'jetpack' ),
1940
				'type'              => 'boolean',
1941
				'default'           => 0,
1942
				'validate_callback' => __CLASS__ . '::validate_boolean',
1943
				'jp_group'          => 'after-the-deadline',
1944
			),
1945
			'Jargon Language' => array(
1946
				'description'       => esc_html__( 'Jargon', 'jetpack' ),
1947
				'type'              => 'boolean',
1948
				'default'           => 0,
1949
				'validate_callback' => __CLASS__ . '::validate_boolean',
1950
				'jp_group'          => 'after-the-deadline',
1951
			),
1952
			'Passive voice' => array(
1953
				'description'       => esc_html__( 'Passive Voice', 'jetpack' ),
1954
				'type'              => 'boolean',
1955
				'default'           => 0,
1956
				'validate_callback' => __CLASS__ . '::validate_boolean',
1957
				'jp_group'          => 'after-the-deadline',
1958
			),
1959
			'Phrases to Avoid' => array(
1960
				'description'       => esc_html__( 'Phrases to Avoid', 'jetpack' ),
1961
				'type'              => 'boolean',
1962
				'default'           => 0,
1963
				'validate_callback' => __CLASS__ . '::validate_boolean',
1964
				'jp_group'          => 'after-the-deadline',
1965
			),
1966
			'Redundant Expression' => array(
1967
				'description'       => esc_html__( 'Redundant Phrases', 'jetpack' ),
1968
				'type'              => 'boolean',
1969
				'default'           => 0,
1970
				'validate_callback' => __CLASS__ . '::validate_boolean',
1971
				'jp_group'          => 'after-the-deadline',
1972
			),
1973
			'guess_lang' => array(
1974
				'description'       => esc_html__( 'Use automatically detected language to proofread posts and pages', 'jetpack' ),
1975
				'type'              => 'boolean',
1976
				'default'           => 0,
1977
				'validate_callback' => __CLASS__ . '::validate_boolean',
1978
				'jp_group'          => 'after-the-deadline',
1979
			),
1980
			'ignored_phrases' => array(
1981
				'description'       => esc_html__( 'Add Phrase to be ignored', 'jetpack' ),
1982
				'type'              => 'string',
1983
				'default'           => '',
1984
				'sanitize_callback' => 'esc_html',
1985
				'jp_group'          => 'after-the-deadline',
1986
			),
1987
			'unignore_phrase' => array(
1988
				'description'       => esc_html__( 'Remove Phrase from being ignored', 'jetpack' ),
1989
				'type'              => 'string',
1990
				'default'           => '',
1991
				'sanitize_callback' => 'esc_html',
1992
				'jp_group'          => 'after-the-deadline',
1993
			),
1994
1995
			// Verification Tools
1996
			'google' => array(
1997
				'description'       => esc_html__( 'Google Search Console', 'jetpack' ),
1998
				'type'              => 'string',
1999
				'default'           => '',
2000
				'validate_callback' => __CLASS__ . '::validate_verification_service',
2001
				'jp_group'          => 'verification-tools',
2002
			),
2003
			'bing' => array(
2004
				'description'       => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
2005
				'type'              => 'string',
2006
				'default'           => '',
2007
				'validate_callback' => __CLASS__ . '::validate_verification_service',
2008
				'jp_group'          => 'verification-tools',
2009
			),
2010
			'pinterest' => array(
2011
				'description'       => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
2012
				'type'              => 'string',
2013
				'default'           => '',
2014
				'validate_callback' => __CLASS__ . '::validate_verification_service',
2015
				'jp_group'          => 'verification-tools',
2016
			),
2017
			'yandex' => array(
2018
				'description'       => esc_html__( 'Yandex Site Verification', 'jetpack' ),
2019
				'type'              => 'string',
2020
				'default'           => '',
2021
				'validate_callback' => __CLASS__ . '::validate_verification_service',
2022
				'jp_group'          => 'verification-tools',
2023
			),
2024
			'enable_header_ad' => array(
2025
				'description'        => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
2026
				'type'               => 'boolean',
2027
				'default'            => 1,
2028
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2029
				'jp_group'           => 'wordads',
2030
			),
2031
			'wordads_approved' => array(
2032
				'description'        => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
2033
				'type'               => 'boolean',
2034
				'default'            => 0,
2035
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2036
				'jp_group'           => 'wordads',
2037
			),
2038
			'wordads_second_belowpost' => array(
2039
				'description'        => esc_html__( 'Display second ad below post?', 'jetpack' ),
2040
				'type'               => 'boolean',
2041
				'default'            => 1,
2042
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2043
				'jp_group'           => 'wordads',
2044
			),
2045
			'wordads_display_front_page' => array(
2046
				'description'        => esc_html__( 'Display ads on the front page?', 'jetpack' ),
2047
				'type'               => 'boolean',
2048
				'default'            => 1,
2049
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2050
				'jp_group'           => 'wordads',
2051
			),
2052
			'wordads_display_post' => array(
2053
				'description'        => esc_html__( 'Display ads on posts?', 'jetpack' ),
2054
				'type'               => 'boolean',
2055
				'default'            => 1,
2056
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2057
				'jp_group'           => 'wordads',
2058
			),
2059
			'wordads_display_page' => array(
2060
				'description'        => esc_html__( 'Display ads on pages?', 'jetpack' ),
2061
				'type'               => 'boolean',
2062
				'default'            => 1,
2063
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2064
				'jp_group'           => 'wordads',
2065
			),
2066
			'wordads_display_archive' => array(
2067
				'description'        => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
2068
				'type'               => 'boolean',
2069
				'default'            => 1,
2070
				'validate_callback'  => __CLASS__ . '::validate_boolean',
2071
				'jp_group'           => 'wordads',
2072
			),
2073
			'wordads_custom_adstxt' => array(
2074
				'description'        => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
2075
				'type'               => 'string',
2076
				'default'            => '',
2077
				'validate_callback'  => __CLASS__ . '::validate_string',
2078
				'sanitize_callback'  => 'sanitize_textarea_field',
2079
				'jp_group'           => 'wordads',
2080
			),
2081
2082
			// Google Analytics
2083
			'google_analytics_tracking_id' => array(
2084
				'description'        => esc_html__( 'Google Analytics', 'jetpack' ),
2085
				'type'               => 'string',
2086
				'default'            => '',
2087
				'validate_callback'  => __CLASS__ . '::validate_alphanum',
2088
				'jp_group'           => 'google-analytics',
2089
			),
2090
2091
			// Stats
2092
			'admin_bar' => array(
2093
				'description'       => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ),
2094
				'type'              => 'boolean',
2095
				'default'           => 1,
2096
				'validate_callback' => __CLASS__ . '::validate_boolean',
2097
				'jp_group'          => 'stats',
2098
			),
2099
			'roles' => array(
2100
				'description'       => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
2101
				'type'              => 'array',
2102
				'items'             => array(
2103
					'type' => 'string'
2104
				),
2105
				'default'           => array( 'administrator' ),
2106
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
2107
				'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
2108
				'jp_group'          => 'stats',
2109
			),
2110
			'count_roles' => array(
2111
				'description'       => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
2112
				'type'              => 'array',
2113
				'items'             => array(
2114
					'type' => 'string'
2115
				),
2116
				'default'           => array( 'administrator' ),
2117
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
2118
				'jp_group'          => 'stats',
2119
			),
2120
			'blog_id' => array(
2121
				'description'       => esc_html__( 'Blog ID.', 'jetpack' ),
2122
				'type'              => 'boolean',
2123
				'default'           => 0,
2124
				'validate_callback' => __CLASS__ . '::validate_boolean',
2125
				'jp_group'          => 'stats',
2126
			),
2127
			'do_not_track' => array(
2128
				'description'       => esc_html__( 'Do not track.', 'jetpack' ),
2129
				'type'              => 'boolean',
2130
				'default'           => 1,
2131
				'validate_callback' => __CLASS__ . '::validate_boolean',
2132
				'jp_group'          => 'stats',
2133
			),
2134
			'hide_smile' => array(
2135
				'description'       => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ),
2136
				'type'              => 'boolean',
2137
				'default'           => 1,
2138
				'validate_callback' => __CLASS__ . '::validate_boolean',
2139
				'jp_group'          => 'stats',
2140
			),
2141
			'version' => array(
2142
				'description'       => esc_html__( 'Version.', 'jetpack' ),
2143
				'type'              => 'integer',
2144
				'default'           => 9,
2145
				'validate_callback' => __CLASS__ . '::validate_posint',
2146
				'jp_group'          => 'stats',
2147
			),
2148
2149
			// Akismet - Not a module, but a plugin. The options can be passed and handled differently.
2150
			'akismet_show_user_comments_approved' => array(
2151
				'description'       => '',
2152
				'type'              => 'boolean',
2153
				'default'           => 0,
2154
				'validate_callback' => __CLASS__ . '::validate_boolean',
2155
				'jp_group'          => 'settings',
2156
			),
2157
2158
			'wordpress_api_key' => array(
2159
				'description'       => '',
2160
				'type'              => 'string',
2161
				'default'           => '',
2162
				'validate_callback' => __CLASS__ . '::validate_alphanum',
2163
				'jp_group'          => 'settings',
2164
			),
2165
2166
			// Apps card on dashboard
2167
			'dismiss_dash_app_card' => array(
2168
				'description'       => '',
2169
				'type'              => 'boolean',
2170
				'default'           => 0,
2171
				'validate_callback' => __CLASS__ . '::validate_boolean',
2172
				'jp_group'          => 'settings',
2173
			),
2174
2175
			// Empty stats card dismiss
2176
			'dismiss_empty_stats_card' => array(
2177
				'description'       => '',
2178
				'type'              => 'boolean',
2179
				'default'           => 0,
2180
				'validate_callback' => __CLASS__ . '::validate_boolean',
2181
				'jp_group'          => 'settings',
2182
			),
2183
2184
			'lang_id' => array(
2185
				'description' => esc_html__( 'Primary language for the site.', 'jetpack' ),
2186
				'type' => 'string',
2187
				'default' => 'en_US',
2188
				'jp_group' => 'settings',
2189
			),
2190
2191
			'onboarding' => array(
2192
				'description'       => '',
2193
				'type'              => 'object',
2194
				'default'           => array(
2195
					'siteTitle'          => '',
2196
					'siteDescription'    => '',
2197
					'siteType'           => 'personal',
2198
					'homepageFormat'     => 'posts',
2199
					'addContactForm'     => 0,
2200
					'businessAddress'    => array(
2201
						'name'   => '',
2202
						'street' => '',
2203
						'city'   => '',
2204
						'state'  => '',
2205
						'zip'    => '',
2206
					),
2207
					'installWooCommerce' => false,
2208
				),
2209
				'validate_callback' => __CLASS__ . '::validate_onboarding',
2210
				'jp_group'          => 'settings',
2211
			),
2212
2213
			// Show welcome for newly purchased plan
2214
			'show_welcome_for_new_plan' => array(
2215
				'description'       => '',
2216
				'type'              => 'boolean',
2217
				'default'           => 0,
2218
				'validate_callback' => __CLASS__ . '::validate_boolean',
2219
				'jp_group'          => 'settings',
2220
			),
2221
2222
		);
2223
2224
		// Add modules to list so they can be toggled
2225
		$modules = Jetpack::get_available_modules();
2226
		if ( is_array( $modules ) && ! empty( $modules ) ) {
2227
			$module_args = array(
2228
				'description'       => '',
2229
				'type'              => 'boolean',
2230
				'default'           => 0,
2231
				'validate_callback' => __CLASS__ . '::validate_boolean',
2232
				'jp_group'          => 'modules',
2233
			);
2234
			foreach( $modules as $module ) {
2235
				$options[ $module ] = $module_args;
2236
			}
2237
		}
2238
2239
		if ( is_array( $selector ) ) {
2240
2241
			// Return only those options whose keys match $selector keys
2242
			return array_intersect_key( $options, $selector );
2243
		}
2244
2245
		if ( 'any' === $selector ) {
2246
2247
			// Toggle module or update any module option or any general setting
2248
			return $options;
2249
		}
2250
2251
		// We're updating the options for a single module.
2252
		if ( empty( $selector ) ) {
2253
			$selector = self::get_module_requested();
2254
		}
2255
		$selected = array();
2256
		foreach ( $options as $option => $attributes ) {
2257
2258
			// Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
2259
			if ( $selector === $attributes['jp_group'] ) {
2260
				$selected[ $option ] = $attributes;
2261
			}
2262
		}
2263
		return $selected;
2264
	}
2265
2266
	/**
2267
	 * Validates that the parameters are proper values that can be set during Jetpack onboarding.
2268
	 *
2269
	 * @since 5.4.0
2270
	 *
2271
	 * @param array           $onboarding_data Values to check.
2272
	 * @param WP_REST_Request $request         The request sent to the WP REST API.
2273
	 * @param string          $param           Name of the parameter passed to endpoint holding $value.
2274
	 *
2275
	 * @return bool|WP_Error
2276
	 */
2277
	public static function validate_onboarding( $onboarding_data, $request, $param ) {
2278
		if ( ! is_array( $onboarding_data ) ) {
2279
			return new WP_Error( 'invalid_param', esc_html__( 'Not valid onboarding data.', 'jetpack' ) );
2280
		}
2281
		foreach ( $onboarding_data as $value ) {
2282
			if ( is_string( $value ) ) {
2283
				$onboarding_choice = self::validate_string( $value, $request, $param );
2284
			} elseif ( is_array( $value ) ) {
2285
				$onboarding_choice = self::validate_onboarding( $value, $request, $param );
2286
			} else {
2287
				$onboarding_choice = self::validate_boolean( $value, $request, $param );
2288
			}
2289
			if ( is_wp_error( $onboarding_choice ) ) {
2290
				return $onboarding_choice;
2291
			}
2292
		}
2293
		return true;
2294
	}
2295
2296
	/**
2297
	 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
2298
	 *
2299
	 * @since 4.3.0
2300
	 *
2301
	 * @param string|bool $value Value to check.
2302
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2303
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2304
	 *
2305
	 * @return bool|WP_Error
2306
	 */
2307
	public static function validate_boolean( $value, $request, $param ) {
2308
		if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) {
2309
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) );
2310
		}
2311
		return true;
2312
	}
2313
2314
	/**
2315
	 * Validates that the parameter is a positive integer.
2316
	 *
2317
	 * @since 4.3.0
2318
	 *
2319
	 * @param int $value Value to check.
2320
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2321
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2322
	 *
2323
	 * @return bool|WP_Error
2324
	 */
2325
	public static function validate_posint( $value = 0, $request, $param ) {
2326
		if ( ! is_numeric( $value ) || $value <= 0 ) {
2327
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a positive integer.', 'jetpack' ), $param ) );
2328
		}
2329
		return true;
2330
	}
2331
2332
	/**
2333
	 * Validates that the parameter belongs to a list of admitted values.
2334
	 *
2335
	 * @since 4.3.0
2336
	 *
2337
	 * @param string $value Value to check.
2338
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2339
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2340
	 *
2341
	 * @return bool|WP_Error
2342
	 */
2343
	public static function validate_list_item( $value = '', $request, $param ) {
2344
		$attributes = $request->get_attributes();
2345
		if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
2346
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s not recognized', 'jetpack' ), $param ) );
2347
		}
2348
		$args = $attributes['args'][ $param ];
2349
		if ( ! empty( $args['enum'] ) ) {
2350
2351
			// If it's an associative array, use the keys to check that the value is among those admitted.
2352
			$enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) ? array_keys( $args['enum'] ) : $args['enum'];
2353 View Code Duplication
			if ( ! in_array( $value, $enum ) ) {
2354
				return new WP_Error( 'invalid_param_value', sprintf(
2355
					/* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
2356
					esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum )
2357
				) );
2358
			}
2359
		}
2360
		return true;
2361
	}
2362
2363
	/**
2364
	 * Validates that the parameter belongs to a list of admitted values.
2365
	 *
2366
	 * @since 4.3.0
2367
	 *
2368
	 * @param string $value Value to check.
2369
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2370
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2371
	 *
2372
	 * @return bool|WP_Error
2373
	 */
2374
	public static function validate_module_list( $value = '', $request, $param ) {
2375 View Code Duplication
		if ( ! is_array( $value ) ) {
2376
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be an array', 'jetpack' ), $param ) );
2377
		}
2378
2379
		$modules = Jetpack::get_available_modules();
2380
2381 View Code Duplication
		if ( count( array_intersect( $value, $modules ) ) != count( $value ) ) {
2382
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be a list of valid modules', 'jetpack' ), $param ) );
2383
		}
2384
2385
		return true;
2386
	}
2387
2388
	/**
2389
	 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
2390
	 *
2391
	 * @since 4.3.0
2392
	 *
2393
	 * @param string $value Value to check.
2394
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2395
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2396
	 *
2397
	 * @return bool|WP_Error
2398
	 */
2399
	public static function validate_alphanum( $value = '', $request, $param ) {
2400 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
2401
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) );
2402
		}
2403
		return true;
2404
	}
2405
2406
	/**
2407
	 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
2408
	 *
2409
	 * @since 4.6.0
2410
	 *
2411
	 * @param string $value Value to check.
2412
	 * @param WP_REST_Request $request
2413
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2414
	 *
2415
	 * @return bool|WP_Error
2416
	 */
2417
	public static function validate_verification_service( $value = '', $request, $param ) {
2418
		if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) {
2419
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
2420
		}
2421
		return true;
2422
	}
2423
2424
	/**
2425
	 * Validates that the parameter is among the roles allowed for Stats.
2426
	 *
2427
	 * @since 4.3.0
2428
	 *
2429
	 * @param string|bool $value Value to check.
2430
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2431
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2432
	 *
2433
	 * @return bool|WP_Error
2434
	 */
2435
	public static function validate_stats_roles( $value, $request, $param ) {
2436
		if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) {
2437
			return new WP_Error( 'invalid_param', sprintf(
2438
				/* 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. */
2439
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', self::$stats_roles )
2440
			) );
2441
		}
2442
		return true;
2443
	}
2444
2445
	/**
2446
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2447
	 *
2448
	 * @since 4.3.0
2449
	 *
2450
	 * @param string|bool $value Value to check.
2451
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2452
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2453
	 *
2454
	 * @return bool|WP_Error
2455
	 */
2456
	public static function validate_sharing_show( $value, $request, $param ) {
2457
		$views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
2458 View Code Duplication
		if ( ! is_array( $value ) ) {
2459
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) );
2460
		}
2461
		if ( ! array_intersect( $views, $value ) ) {
2462
			return new WP_Error( 'invalid_param', sprintf(
2463
				/* 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 */
2464
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views )
2465
			) );
2466
		}
2467
		return true;
2468
	}
2469
2470
	/**
2471
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2472
	 *
2473
	 * @since 4.3.0
2474
	 *
2475
	 * @param string|bool $value {
2476
	 *     Value to check received by request.
2477
	 *
2478
	 *     @type array $visible List of slug of services to share to that are displayed directly in the page.
2479
	 *     @type array $hidden  List of slug of services to share to that are concealed in a folding menu.
2480
	 * }
2481
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2482
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2483
	 *
2484
	 * @return bool|WP_Error
2485
	 */
2486
	public static function validate_services( $value, $request, $param ) {
2487 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
2488
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), $param ) );
2489
		}
2490
2491
		// Allow to clear everything.
2492
		if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
2493
			return true;
2494
		}
2495
2496 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2497
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2498
		}
2499
		$sharer = new Sharing_Service();
2500
		$services = array_keys( $sharer->get_all_services() );
2501
2502
		if (
2503
			( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
2504
			||
2505
			( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) )
2506
		{
2507
			return new WP_Error( 'invalid_param', sprintf(
2508
				/* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
2509
				esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services )
2510
			) );
2511
		}
2512
		return true;
2513
	}
2514
2515
	/**
2516
	 * Validates that the parameter has enough information to build a custom sharing button.
2517
	 *
2518
	 * @since 4.3.0
2519
	 *
2520
	 * @param string|bool $value Value to check.
2521
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2522
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2523
	 *
2524
	 * @return bool|WP_Error
2525
	 */
2526
	public static function validate_custom_service( $value, $request, $param ) {
2527 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
2528
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), $param ) );
2529
		}
2530
2531
		// Allow to clear everything.
2532
		if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
2533
			return true;
2534
		}
2535
2536 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2537
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2538
		}
2539
2540
		if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
2541
		|| ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
2542
		|| ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
2543
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), $param ) );
2544
		}
2545
		return true;
2546
	}
2547
2548
	/**
2549
	 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
2550
	 *
2551
	 * @since 4.3.0
2552
	 *
2553
	 * @param string $value Value to check.
2554
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2555
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2556
	 *
2557
	 * @return bool|WP_Error
2558
	 */
2559
	public static function validate_custom_service_id( $value = '', $request, $param ) {
2560 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
2561
			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 ) );
2562
		}
2563
2564 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2565
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2566
		}
2567
		$sharer = new Sharing_Service();
2568
		$services = array_keys( $sharer->get_all_services() );
2569
2570 View Code Duplication
		if ( ! empty( $value ) && ! in_array( $value, $services ) ) {
2571
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), $param ) );
2572
		}
2573
2574
		return true;
2575
	}
2576
2577
	/**
2578
	 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
2579
	 *
2580
	 * @since 4.3.0
2581
	 *
2582
	 * @param string $value Value to check.
2583
	 * @param WP_REST_Request $request
2584
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2585
	 *
2586
	 * @return bool|WP_Error
2587
	 */
2588
	public static function validate_twitter_username( $value = '', $request, $param ) {
2589 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
2590
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a Twitter username.', 'jetpack' ), $param ) );
2591
		}
2592
		return true;
2593
	}
2594
2595
	/**
2596
	 * Validates that the parameter is a string.
2597
	 *
2598
	 * @since 4.3.0
2599
	 *
2600
	 * @param string $value Value to check.
2601
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2602
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2603
	 *
2604
	 * @return bool|WP_Error
2605
	 */
2606
	public static function validate_string( $value = '', $request, $param ) {
2607
		if ( ! is_string( $value ) ) {
2608
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack' ), $param ) );
2609
		}
2610
		return true;
2611
	}
2612
2613
	/**
2614
	 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
2615
	 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
2616
	 *
2617
	 * @since 4.3.0
2618
	 *
2619
	 * @param string|bool $value Value to check.
2620
	 *
2621
	 * @return bool|array
2622
	 */
2623
	public static function sanitize_stats_allowed_roles( $value ) {
2624
		if ( empty( $value ) ) {
2625
			return array( 'administrator' );
2626
		}
2627
		return $value;
2628
	}
2629
2630
	/**
2631
	 * Get the currently accessed route and return the module slug in it.
2632
	 *
2633
	 * @since 4.3.0
2634
	 *
2635
	 * @param string $route Regular expression for the endpoint with the module slug to return.
2636
	 *
2637
	 * @return array|string
2638
	 */
2639
	public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
2640
2641
		if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
2642
			return '';
2643
		}
2644
2645
		preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
2646
2647
		if ( empty( $module['slug'] ) ) {
2648
			return '';
2649
		}
2650
2651
		return $module['slug'];
2652
	}
2653
2654
	/**
2655
	 * Adds extra information for modules.
2656
	 *
2657
	 * @since 4.3.0
2658
	 *
2659
	 * @param string|array $modules Can be a single module or a list of modules.
2660
	 * @param null|string  $slug    Slug of the module in the first parameter.
2661
	 *
2662
	 * @return array|string
2663
	 */
2664
	public static function prepare_modules_for_response( $modules = '', $slug = null ) {
2665
		global $wp_rewrite;
2666
2667
		/** This filter is documented in modules/sitemaps/sitemaps.php */
2668
		$location = apply_filters( 'jetpack_sitemap_location', '' );
2669
2670
		if ( $wp_rewrite->using_index_permalinks() ) {
2671
			$sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
2672
			$news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
2673
		} else if ( $wp_rewrite->using_permalinks() ) {
2674
			$sitemap_url = home_url( $location . '/sitemap.xml' );
2675
			$news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
2676
		} else {
2677
			$sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
2678
			$news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
2679
		}
2680
2681
		if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) {
2682
			// Is a list of modules
2683
			$modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
2684
			$modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
2685
		} elseif ( 'sitemaps' == $slug ) {
2686
			// It's a single module
2687
			$modules['extra']['sitemap_url'] = $sitemap_url;
2688
			$modules['extra']['news_sitemap_url'] = $news_sitemap_url;
2689
		}
2690
		return $modules;
2691
	}
2692
2693
	/**
2694
	 * Remove 'validate_callback' item from options available for module.
2695
	 * Fetch current option value and add to array of module options.
2696
	 * Prepare values of module options that need special handling, like those saved in wpcom.
2697
	 *
2698
	 * @since 4.3.0
2699
	 *
2700
	 * @param string $module Module slug.
2701
	 * @return array
2702
	 */
2703
	public static function prepare_options_for_response( $module = '' ) {
2704
		$options = self::get_updateable_data_list( $module );
2705
2706
		if ( ! is_array( $options ) || empty( $options ) ) {
2707
			return $options;
2708
		}
2709
2710
		// Some modules need special treatment.
2711
		switch ( $module ) {
2712
2713
			case 'monitor':
2714
				// Status of user notifications
2715
				$options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
2716
				break;
2717
2718
			case 'post-by-email':
2719
				// Email address
2720
				$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'] );
2721
				break;
2722
2723
			case 'protect':
2724
				// Protect
2725
				$options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
2726
				if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) {
2727
					include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' );
2728
				}
2729
				$options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist();
2730
				break;
2731
2732
			case 'related-posts':
2733
				// It's local, but it must be broken apart since it's saved as an array.
2734
				$options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
2735
				break;
2736
2737
			case 'verification-tools':
2738
				// It's local, but it must be broken apart since it's saved as an array.
2739
				$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
2740
				break;
2741
2742
			case 'google-analytics':
2743
				$wga = get_option( 'jetpack_wga' );
2744
				$code = '';
2745
				if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
2746
					 $code = $wga[ 'code' ];
2747
				}
2748
				$options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
2749
				break;
2750
2751
			case 'sharedaddy':
2752
				// It's local, but it must be broken apart since it's saved as an array.
2753
				if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2754
					break;
2755
				}
2756
				$sharer = new Sharing_Service();
2757
				$options = self::split_options( $options, $sharer->get_global_options() );
2758
				$options['sharing_services']['current_value'] = $sharer->get_blog_services();
2759
				$other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
2760 View Code Duplication
				foreach ( $other_sharedaddy_options as $key ) {
2761
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2762
					$current_value = get_option( $key, $default_value );
2763
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2764
				}
2765
				break;
2766
2767
			case 'after-the-deadline':
2768
				if ( ! function_exists( 'AtD_get_options' ) ) {
2769
					include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' );
2770
				}
2771
				$atd_options = array_merge( AtD_get_options( get_current_user_id(), 'AtD_options' ), AtD_get_options( get_current_user_id(), 'AtD_check_when' ) );
2772
				unset( $atd_options['name'] );
2773
				foreach ( $atd_options as $key => $value ) {
2774
					$options[ $key ]['current_value'] = self::cast_value( $value, $options[ $key ] );
2775
				}
2776
				$atd_options = AtD_get_options( get_current_user_id(), 'AtD_guess_lang' );
2777
				$options['guess_lang']['current_value'] = self::cast_value( isset( $atd_options['true'] ), $options[ 'guess_lang' ] );
2778
				$options['ignored_phrases']['current_value'] = AtD_get_setting( get_current_user_id(), 'AtD_ignored_phrases' );
2779
				unset( $options['unignore_phrase'] );
2780
				break;
2781
2782
			case 'stats':
2783
				// It's local, but it must be broken apart since it's saved as an array.
2784
				if ( ! function_exists( 'stats_get_options' ) ) {
2785
					include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
2786
				}
2787
				$options = self::split_options( $options, stats_get_options() );
2788
				break;
2789
			default:
2790
				// These option are just stored as plain WordPress options.
2791 View Code Duplication
				foreach ( $options as $key => $value ) {
2792
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2793
					$current_value = get_option( $key, $default_value );
2794
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2795
				}
2796
		}
2797
		// At this point some options have current_value not set because they're options
2798
		// that only get written on update, so we set current_value to the default one.
2799
		foreach ( $options as $key => $value ) {
2800
			// We don't need validate_callback in the response
2801
			if ( isset( $options[ $key ]['validate_callback'] ) ) {
2802
				unset( $options[ $key ]['validate_callback'] );
2803
			}
2804
			$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2805
			if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
2806
				$options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
2807
			}
2808
		}
2809
		return $options;
2810
	}
2811
2812
	/**
2813
	 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
2814
	 *
2815
	 * @since 4.3.0
2816
	 *
2817
	 * @param array  $separate_options Array of options admitted by the module.
2818
	 * @param array  $grouped_options Option saved as array to be splitted.
2819
	 * @param string $prefix Optional prefix for the separate option keys.
2820
	 *
2821
	 * @return array
2822
	 */
2823
	public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
2824
		if ( is_array( $grouped_options ) ) {
2825
			foreach ( $grouped_options as $key => $value ) {
2826
				$option_key = $prefix . $key;
2827
				if ( isset( $separate_options[ $option_key ] ) ) {
2828
					$separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
2829
				}
2830
			}
2831
		}
2832
		return $separate_options;
2833
	}
2834
2835
	/**
2836
	 * Perform a casting to the value specified in the option definition.
2837
	 *
2838
	 * @since 4.3.0
2839
	 *
2840
	 * @param mixed $value Value to cast to the proper type.
2841
	 * @param array $definition Type to cast the value to.
2842
	 *
2843
	 * @return bool|float|int|string
2844
	 */
2845
	public static function cast_value( $value, $definition ) {
2846
		if ( $value === 'NULL' ) {
2847
			return null;
2848
		}
2849
2850
		if ( isset( $definition['type'] ) ) {
2851
			switch ( $definition['type'] ) {
2852
				case 'boolean':
2853
					if ( 'true' === $value ) {
2854
						return true;
2855
					} elseif ( 'false' === $value ) {
2856
						return false;
2857
					}
2858
					return (bool) $value;
2859
					break;
2860
2861
				case 'integer':
2862
					return (int) $value;
2863
					break;
2864
2865
				case 'float':
2866
					return (float) $value;
2867
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2868
2869
				case 'string':
2870
					return (string) $value;
2871
					break;
2872
			}
2873
		}
2874
		return $value;
2875
	}
2876
2877
	/**
2878
	 * Get a value not saved locally.
2879
	 *
2880
	 * @since 4.3.0
2881
	 *
2882
	 * @param string $module Module slug.
2883
	 * @param string $option Option name.
2884
	 *
2885
	 * @return bool Whether user is receiving notifications or not.
2886
	 */
2887
	public static function get_remote_value( $module, $option ) {
2888
2889
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
2890
			$option .= get_current_user_id();
2891
		}
2892
2893
		// If option doesn't exist, 'does_not_exist' will be returned.
2894
		$value = get_option( $option, 'does_not_exist' );
2895
2896
		// If option exists, just return it.
2897
		if ( 'does_not_exist' !== $value ) {
2898
			return $value;
2899
		}
2900
2901
		// Only check a remote option if Jetpack is connected.
2902
		if ( ! Jetpack::is_active() ) {
2903
			return false;
2904
		}
2905
2906
		// Do what is necessary for each module.
2907
		switch ( $module ) {
2908
			case 'monitor':
2909
				// Load the class to use the method. If class can't be found, do nothing.
2910
				if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2911
					return false;
2912
				}
2913
				$value = Jetpack_Monitor::user_receives_notifications( false );
2914
				break;
2915
2916
			case 'post-by-email':
2917
				// Load the class to use the method. If class can't be found, do nothing.
2918
				if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2919
					return false;
2920
				}
2921
				$post_by_email = new Jetpack_Post_By_Email();
2922
				$value = $post_by_email->get_post_by_email_address();
2923
				if ( $value === null ) {
2924
					$value = 'NULL'; // sentinel value so it actually gets set
2925
				}
2926
				break;
2927
		}
2928
2929
		// Normalize value to boolean.
2930
		if ( is_wp_error( $value ) || is_null( $value ) ) {
2931
			$value = false;
2932
		}
2933
2934
		// Save option to use it next time.
2935
		update_option( $option, $value );
2936
2937
		return $value;
2938
	}
2939
2940
	/**
2941
	 * Get number of plugin updates available.
2942
	 *
2943
	 * @since 4.3.0
2944
	 *
2945
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
2946
	 */
2947
	public static function get_plugin_update_count() {
2948
		$updates = wp_get_update_data();
2949
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
2950
			$count = $updates['counts']['plugins'];
2951
			if ( 0 == $count ) {
2952
				$response = array(
2953
					'code'    => 'success',
2954
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
2955
					'count'   => 0,
2956
				);
2957
			} else {
2958
				$response = array(
2959
					'code'    => 'updates-available',
2960
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
2961
					'count'   => $count,
2962
				);
2963
			}
2964
			return rest_ensure_response( $response );
2965
		}
2966
2967
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
2968
	}
2969
2970
2971
	/**
2972
	 * Returns a list of all plugins in the site.
2973
	 *
2974
	 * @since 4.2.0
2975
	 * @uses get_plugins()
2976
	 *
2977
	 * @return array
2978
	 */
2979
	private static function core_get_plugins() {
2980
		if ( ! function_exists( 'get_plugins' ) ) {
2981
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2982
		}
2983
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2984
		$plugins = apply_filters( 'all_plugins', get_plugins() );
2985
2986
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
2987
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
2988
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
2989
			}
2990
			return $plugins;
2991
		}
2992
2993
		return array();
2994
	}
2995
2996
	/**
2997
	 * Checks if the queried plugin is active.
2998
	 *
2999
	 * @since 4.2.0
3000
	 * @uses is_plugin_active()
3001
	 *
3002
	 * @return bool
3003
	 */
3004
	private static function core_is_plugin_active( $plugin ) {
3005
		if ( ! function_exists( 'is_plugin_active' ) ) {
3006
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
3007
		}
3008
3009
		return is_plugin_active( $plugin );
3010
	}
3011
3012
	/**
3013
	 * Get plugins data in site.
3014
	 *
3015
	 * @since 4.2.0
3016
	 *
3017
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
3018
	 */
3019
	public static function get_plugins() {
3020
		$plugins = self::core_get_plugins();
3021
3022
		if ( ! empty( $plugins ) ) {
3023
			return rest_ensure_response( $plugins );
3024
		}
3025
3026
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
3027
	}
3028
3029
	/**
3030
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
3031
	 *
3032
	 * @since 4.2.0
3033
	 *
3034
	 * @param WP_REST_Request $request {
3035
	 *     Array of parameters received by request.
3036
	 *
3037
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3038
	 * }
3039
	 *
3040
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
3041
	 */
3042
	public static function get_plugin( $request ) {
3043
3044
		$plugins = self::core_get_plugins();
3045
3046
		if ( empty( $plugins ) ) {
3047
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3048
		}
3049
3050
		$plugin = stripslashes( $request['plugin'] );
3051
3052
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
3053
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
3054
		}
3055
3056
		$plugin_data = $plugins[ $plugin ];
3057
3058
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
3059
3060
		return rest_ensure_response( array(
3061
			'code'    => 'success',
3062
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
3063
			'data'    => $plugin_data
3064
		) );
3065
	}
3066
3067
} // class end
3068