Completed
Push — add/7777-ga-anonymize-ip ( 94dfd5 )
by
unknown
10:21
created

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

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
21
/**
22
 * Class Jetpack_Core_Json_Api_Endpoints
23
 *
24
 * @since 4.3.0
25
 */
26
class Jetpack_Core_Json_Api_Endpoints {
27
28
	/**
29
	 * @var string Generic error message when user is not allowed to perform an action.
30
	 */
31
	public static $user_permissions_error_msg;
32
33
	/**
34
	 * @var array Roles that can access Stats once they're granted access.
35
	 */
36
	public static $stats_roles;
37
38
	/**
39
	 * Declare the Jetpack REST API endpoints.
40
	 *
41
	 * @since 4.3.0
42
	 */
43
	public static function register_endpoints() {
44
45
		// Load API endpoint base classes
46
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
47
48
		// Load API endpoints
49
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
50
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
51
52
		self::$user_permissions_error_msg = esc_html__(
53
			'You do not have the correct user permissions to perform this action.
54
			Please contact your site admin if you think this is a mistake.',
55
			'jetpack'
56
		);
57
58
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
59
60
		Jetpack::load_xml_rpc_client();
61
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
62
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
63
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
64
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
65
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
66
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
67
68
		register_rest_route( 'jetpack/v4', '/jitm', array(
69
			'methods'  => WP_REST_Server::READABLE,
70
			'callback' => __CLASS__ . '::get_jitm_message',
71
		) );
72
73
		register_rest_route( 'jetpack/v4', '/jitm', array(
74
			'methods'  => WP_REST_Server::CREATABLE,
75
			'callback' => __CLASS__ . '::delete_jitm_message'
76
		) );
77
78
		// Register a site
79
		register_rest_route( 'jetpack/v4', '/verify_registration', array(
80
			'methods' => WP_REST_Server::EDITABLE,
81
			'callback' => __CLASS__ . '::verify_registration',
82
		) );
83
84
		// Authorize a remote user
85
		register_rest_route( 'jetpack/v4', '/remote_authorize', array(
86
			'methods' => WP_REST_Server::EDITABLE,
87
			'callback' => __CLASS__ . '::remote_authorize',
88
		) );
89
90
		// Get current connection status of Jetpack
91
		register_rest_route( 'jetpack/v4', '/connection', array(
92
			'methods' => WP_REST_Server::READABLE,
93
			'callback' => __CLASS__ . '::jetpack_connection_status',
94
		) );
95
96
		// Fetches a fresh connect URL
97
		register_rest_route( 'jetpack/v4', '/connection/url', array(
98
			'methods' => WP_REST_Server::READABLE,
99
			'callback' => __CLASS__ . '::build_connect_url',
100
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
101
		) );
102
103
		// Get current user connection data
104
		register_rest_route( 'jetpack/v4', '/connection/data', array(
105
			'methods' => WP_REST_Server::READABLE,
106
			'callback' => __CLASS__ . '::get_user_connection_data',
107
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
108
		) );
109
110
		// Disconnect site from WordPress.com servers
111
		register_rest_route( 'jetpack/v4', '/connection', array(
112
			'methods' => WP_REST_Server::EDITABLE,
113
			'callback' => __CLASS__ . '::disconnect_site',
114
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
115
		) );
116
117
		// Disconnect/unlink user from WordPress.com servers
118
		register_rest_route( 'jetpack/v4', '/connection/user', array(
119
			'methods' => WP_REST_Server::EDITABLE,
120
			'callback' => __CLASS__ . '::unlink_user',
121
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
122
		) );
123
124
		// Get current site data
125
		register_rest_route( 'jetpack/v4', '/site', array(
126
			'methods' => WP_REST_Server::READABLE,
127
			'callback' => __CLASS__ . '::get_site_data',
128
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
129
		) );
130
131
		// Get current site data
132
		register_rest_route( 'jetpack/v4', '/site/features', array(
133
			'methods' => WP_REST_Server::READABLE,
134
			'callback' => array( $site_endpoint, 'get_features' ),
135
			'permission_callback' => array( $site_endpoint , 'can_request' ),
136
		) );
137
138
		// Confirm that a site in identity crisis should be in staging mode
139
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
140
			'methods' => WP_REST_Server::EDITABLE,
141
			'callback' => __CLASS__ . '::confirm_safe_mode',
142
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
143
		) );
144
145
		// IDC resolve: create an entirely new shadow site for this URL.
146
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
147
			'methods' => WP_REST_Server::EDITABLE,
148
			'callback' => __CLASS__ . '::start_fresh_connection',
149
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
150
		) );
151
152
		// Handles the request to migrate stats and subscribers during an identity crisis.
153
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
154
			'methods' => WP_REST_Server::EDITABLE,
155
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
156
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
157
		) );
158
159
		// Return all modules
160
		register_rest_route( 'jetpack/v4', '/module/all', array(
161
			'methods' => WP_REST_Server::READABLE,
162
			'callback' => array( $module_list_endpoint, 'process' ),
163
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
164
		) );
165
166
		// Activate many modules
167
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
168
			'methods' => WP_REST_Server::EDITABLE,
169
			'callback' => array( $module_list_endpoint, 'process' ),
170
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
171
			'args' => array(
172
				'modules' => array(
173
					'default'           => '',
174
					'type'              => 'array',
175
					'items'             => array(
176
						'type'          => 'string',
177
					),
178
					'required'          => true,
179
					'validate_callback' => __CLASS__ . '::validate_module_list',
180
				),
181
				'active' => array(
182
					'default'           => true,
183
					'type'              => 'boolean',
184
					'required'          => false,
185
					'validate_callback' => __CLASS__ . '::validate_boolean',
186
				),
187
			)
188
		) );
189
190
		// Return a single module and update it when needed
191
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
192
			'methods' => WP_REST_Server::READABLE,
193
			'callback' => array( $core_api_endpoint, 'process' ),
194
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
195
		) );
196
197
		// Activate and deactivate a module
198
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
199
			'methods' => WP_REST_Server::EDITABLE,
200
			'callback' => array( $module_toggle_endpoint, 'process' ),
201
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
202
			'args' => array(
203
				'active' => array(
204
					'default'           => true,
205
					'type'              => 'boolean',
206
					'required'          => true,
207
					'validate_callback' => __CLASS__ . '::validate_boolean',
208
				),
209
			)
210
		) );
211
212
		// Update a module
213
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
214
			'methods' => WP_REST_Server::EDITABLE,
215
			'callback' => array( $core_api_endpoint, 'process' ),
216
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
217
			'args' => self::get_updateable_parameters( 'any' )
218
		) );
219
220
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
221
		// Akismet spam count, etc.
222
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
223
			'methods' => WP_REST_Server::READABLE,
224
			'callback' => array( $module_data_endpoint, 'process' ),
225
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
226
			'args' => array(
227
				'range' => array(
228
					'default'           => 'day',
229
					'type'              => 'string',
230
					'required'          => false,
231
					'validate_callback' => __CLASS__ . '::validate_string',
232
				),
233
			)
234
		) );
235
236
		// Check if the API key for a specific service is valid or not
237
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
238
			'methods' => WP_REST_Server::READABLE,
239
			'callback' => array( $module_data_endpoint, 'key_check' ),
240
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
241
			'sanitize_callback' => 'sanitize_text_field',
242
		) );
243
244
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
245
			'methods' => WP_REST_Server::EDITABLE,
246
			'callback' => array( $module_data_endpoint, 'key_check' ),
247
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
248
			'sanitize_callback' => 'sanitize_text_field',
249
			'args' => array(
250
				'api_key' => array(
251
					'default'           => '',
252
					'type'              => 'string',
253
					'validate_callback' => __CLASS__ . '::validate_alphanum',
254
				),
255
			)
256
		) );
257
258
		// Update any Jetpack module option or setting
259
		register_rest_route( 'jetpack/v4', '/settings', array(
260
			'methods' => WP_REST_Server::EDITABLE,
261
			'callback' => array( $core_api_endpoint, 'process' ),
262
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
263
			'args' => self::get_updateable_parameters( 'any' )
264
		) );
265
266
		// Update a module
267
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
268
			'methods' => WP_REST_Server::EDITABLE,
269
			'callback' => array( $core_api_endpoint, 'process' ),
270
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
271
			'args' => self::get_updateable_parameters()
272
		) );
273
274
		// Return all module settings
275
		register_rest_route( 'jetpack/v4', '/settings/', array(
276
			'methods' => WP_REST_Server::READABLE,
277
			'callback' => array( $core_api_endpoint, 'process' ),
278
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
279
		) );
280
281
		// Reset all Jetpack options
282
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
283
			'methods' => WP_REST_Server::EDITABLE,
284
			'callback' => __CLASS__ . '::reset_jetpack_options',
285
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
286
		) );
287
288
		// Return current Jumpstart status
289
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
290
			'methods'             => WP_REST_Server::READABLE,
291
			'callback'            => __CLASS__ . '::jumpstart_status',
292
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
293
		) );
294
295
		// Update Jumpstart
296
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
297
			'methods'             => WP_REST_Server::EDITABLE,
298
			'callback'            => __CLASS__ . '::jumpstart_toggle',
299
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
300
			'args'                => array(
301
				'active' => array(
302
					'required'          => true,
303
					'validate_callback' => __CLASS__  . '::validate_boolean',
304
				),
305
			),
306
		) );
307
308
		// Updates: get number of plugin updates available
309
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
310
			'methods' => WP_REST_Server::READABLE,
311
			'callback' => __CLASS__ . '::get_plugin_update_count',
312
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
313
		) );
314
315
		// Dismiss Jetpack Notices
316
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
317
			'methods' => WP_REST_Server::EDITABLE,
318
			'callback' => __CLASS__ . '::dismiss_notice',
319
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
320
		) );
321
322
		// Plugins: get list of all plugins.
323
		register_rest_route( 'jetpack/v4', '/plugins', array(
324
			'methods' => WP_REST_Server::READABLE,
325
			'callback' => __CLASS__ . '::get_plugins',
326
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
327
		) );
328
329
		// Plugins: check if the plugin is active.
330
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
331
			'methods' => WP_REST_Server::READABLE,
332
			'callback' => __CLASS__ . '::get_plugin',
333
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
334
		) );
335
	}
336
337
	/**
338
	 * @param $request WP_REST_Request
339
	 *
340
	 * @return array
341
	 */
342
	public static function get_jitm_message( $request ) {
343
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
344
345
		$jitm = Jetpack_JITM::init();
346
347
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) );
348
	}
349
350
	public static function delete_jitm_message( $request ) {
351
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
352
353
		$jitm = Jetpack_JITM::init();
354
355
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
356
	}
357
358
	/**
359
	 * Handles verification that a site is registered
360
	 *
361
	 * @since 5.4.0
362
	 *
363
	 * @param WP_REST_Request $request The request sent to the WP REST API.
364
	 *
365
	 * @return array|wp-error
0 ignored issues
show
The doc-type array|wp-error could not be parsed: Unknown type name "wp-error" at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
366
	 */
367
	public static function verify_registration( $request ) {
368
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
369
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
370
		$result = $xmlrpc_server->verify_registration( array( $request['secret_1'], $request['state'] ) );
371
372
		if ( is_a( $result, 'IXR_Error' ) ) {
373
			$result = new WP_Error( $result->code, $result->message );
374
		}
375
376
		return $result;
377
	}
378
379
	/**
380
	 * Handles verification that a site is registered
381
	 *
382
	 * @since 5.4.0
383
	 *
384
	 * @param WP_REST_Request $request The request sent to the WP REST API.
385
	 *
386
	 * @return array|wp-error
387
	 */
388
	 public static function remote_authorize( $request ) {
389
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
390
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
391
		$result = $xmlrpc_server->remote_authorize( $request );
392
393
		if ( is_a( $result, 'IXR_Error' ) ) {
394
			$result = new WP_Error( $result->code, $result->message );
395
		}
396
397
		return $result;
398
	 }
399
400
	/**
401
	 * Handles dismissing of Jetpack Notices
402
	 *
403
	 * @since 4.3.0
404
	 *
405
	 * @param WP_REST_Request $request The request sent to the WP REST API.
406
	 *
407
	 * @return array|wp-error
0 ignored issues
show
The doc-type array|wp-error could not be parsed: Unknown type name "wp-error" at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
408
	 */
409
	public static function dismiss_notice( $request ) {
410
		$notice = $request['notice'];
411
412
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
413
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
414
		}
415
416
		if ( isset( $notice ) && ! empty( $notice ) ) {
417
			switch( $notice ) {
418
				case 'feedback_dash_request':
419
				case 'welcome':
420
					$notices = get_option( 'jetpack_dismissed_notices', array() );
421
					$notices[ $notice ] = true;
422
					update_option( 'jetpack_dismissed_notices', $notices );
423
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
424
425
				default:
426
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
427
			}
428
		}
429
430
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
431
	}
432
433
	/**
434
	 * Verify that the user can disconnect the site.
435
	 *
436
	 * @since 4.3.0
437
	 *
438
	 * @return bool|WP_Error True if user is able to disconnect the site.
439
	 */
440 View Code Duplication
	public static function disconnect_site_permission_callback() {
441
		if ( current_user_can( 'jetpack_disconnect' ) ) {
442
			return true;
443
		}
444
445
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
446
447
	}
448
449
	/**
450
	 * Verify that the user can get a connect/link URL
451
	 *
452
	 * @since 4.3.0
453
	 *
454
	 * @return bool|WP_Error True if user is able to disconnect the site.
455
	 */
456 View Code Duplication
	public static function connect_url_permission_callback() {
457
		if ( current_user_can( 'jetpack_connect_user' ) ) {
458
			return true;
459
		}
460
461
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
462
463
	}
464
465
	/**
466
	 * Verify that a user can get the data about the current user.
467
	 * Only those who can connect.
468
	 *
469
	 * @since 4.3.0
470
	 *
471
	 * @uses Jetpack::is_user_connected();
472
	 *
473
	 * @return bool|WP_Error True if user is able to unlink.
474
	 */
475 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
476
		if ( current_user_can( 'jetpack_connect_user' ) ) {
477
			return true;
478
		}
479
480
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
481
	}
482
483
	/**
484
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
485
	 *
486
	 * @since 4.3.0
487
	 *
488
	 * @uses Jetpack::is_user_connected();
489
	 *
490
	 * @return bool|WP_Error True if user is able to unlink.
491
	 */
492
	public static function unlink_user_permission_callback() {
493
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
494
			return true;
495
		}
496
497
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
498
	}
499
500
	/**
501
	 * Verify that user can manage Jetpack modules.
502
	 *
503
	 * @since 4.3.0
504
	 *
505
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
506
	 */
507
	public static function manage_modules_permission_check() {
508
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
509
			return true;
510
		}
511
512
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
513
	}
514
515
	/**
516
	 * Verify that user can update Jetpack modules.
517
	 *
518
	 * @since 4.3.0
519
	 *
520
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
521
	 */
522 View Code Duplication
	public static function configure_modules_permission_check() {
523
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
524
			return true;
525
		}
526
527
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
528
	}
529
530
	/**
531
	 * Verify that user can view Jetpack admin page.
532
	 *
533
	 * @since 4.3.0
534
	 *
535
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
536
	 */
537 View Code Duplication
	public static function view_admin_page_permission_check() {
538
		if ( current_user_can( 'jetpack_admin_page' ) ) {
539
			return true;
540
		}
541
542
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
543
	}
544
545
	/**
546
	 * Verify that user can mitigate an identity crisis.
547
	 *
548
	 * @since 4.4.0
549
	 *
550
	 * @return bool Whether user has capability 'jetpack_disconnect'.
551
	 */
552 View Code Duplication
	public static function identity_crisis_mitigation_permission_check() {
553
		if ( current_user_can( 'jetpack_disconnect' ) ) {
554
			return true;
555
		}
556
557
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
558
	}
559
560
	/**
561
	 * Verify that user can update Jetpack general settings.
562
	 *
563
	 * @since 4.3.0
564
	 *
565
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
566
	 */
567 View Code Duplication
	public static function update_settings_permission_check() {
568
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
569
			return true;
570
		}
571
572
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
573
	}
574
575
	/**
576
	 * Verify that user can view Jetpack admin page and can activate plugins.
577
	 *
578
	 * @since 4.3.0
579
	 *
580
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
581
	 */
582 View Code Duplication
	public static function activate_plugins_permission_check() {
583
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
584
			return true;
585
		}
586
587
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
588
	}
589
590
	/**
591
	 * Contextual HTTP error code for authorization failure.
592
	 *
593
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
594
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
595
	 *
596
	 * @since 4.3.0
597
	 *
598
	 * @return int
599
	 */
600
	public static function rest_authorization_required_code() {
601
		return is_user_logged_in() ? 403 : 401;
602
	}
603
604
	/**
605
	 * Get connection status for this Jetpack site.
606
	 *
607
	 * @since 4.3.0
608
	 *
609
	 * @return bool True if site is connected
610
	 */
611
	public static function jetpack_connection_status() {
612
		return rest_ensure_response( array(
613
				'isActive'  => Jetpack::is_active(),
614
				'isStaging' => Jetpack::is_staging_site(),
615
				'devMode'   => array(
616
					'isActive' => Jetpack::is_development_mode(),
617
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
618
					'url'      => site_url() && false === strpos( site_url(), '.' ),
619
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
620
				),
621
			)
622
		);
623
	}
624
625
	/**
626
	 * Disconnects Jetpack from the WordPress.com Servers
627
	 *
628
	 * @uses Jetpack::disconnect();
629
	 * @since 4.3.0
630
	 *
631
	 * @param WP_REST_Request $request The request sent to the WP REST API.
632
	 *
633
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
634
	 */
635 View Code Duplication
	public static function disconnect_site( $request ) {
636
637
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
638
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
639
		}
640
641
		if ( Jetpack::is_active() ) {
642
			Jetpack::disconnect();
643
			return rest_ensure_response( array( 'code' => 'success' ) );
644
		}
645
646
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
647
	}
648
649
	/**
650
	 * Gets a new connect raw URL with fresh nonce.
651
	 *
652
	 * @uses Jetpack::disconnect();
653
	 * @since 4.3.0
654
	 *
655
	 * @param WP_REST_Request $request The request sent to the WP REST API.
656
	 *
657
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
658
	 */
659
	public static function build_connect_url() {
660
		$url = Jetpack::init()->build_connect_url( true, false, false );
661
		if ( $url ) {
662
			return rest_ensure_response( $url );
663
		}
664
665
		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 ) );
666
	}
667
668
	/**
669
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
670
	 * Information about the master/primary user.
671
	 * Information about the current user.
672
	 *
673
	 * @since 4.3.0
674
	 *
675
	 * @param WP_REST_Request $request The request sent to the WP REST API.
676
	 *
677
	 * @return object
678
	 */
679
	public static function get_user_connection_data() {
680
		require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' );
681
682
		$response = array(
683
//			'othersLinked' => Jetpack::get_other_linked_admins(),
684
			'currentUser'  => jetpack_current_user_data(),
685
		);
686
		return rest_ensure_response( $response );
687
	}
688
689
	/**
690
	 * Returns the proper name for Jetpack Holiday Snow setting.
691
	 * When the REST route starts, the holiday-snow.php file where jetpack_holiday_snow_option_name() function is defined is not loaded,
692
	 * so where using this to replicate it and have the same functionality.
693
	 *
694
	 * @since 4.4.0
695
	 *
696
	 * @return string
697
	 */
698
	public static function holiday_snow_option_name() {
699
		/** This filter is documented in modules/holiday-snow.php */
700
		return apply_filters( 'jetpack_holiday_snow_option_name', 'jetpack_holiday_snow_enabled' );
701
	}
702
703
	/**
704
	 * Update a single miscellaneous setting for this Jetpack installation, like Holiday Snow.
705
	 *
706
	 * @since 4.3.0
707
	 *
708
	 * @param WP_REST_Request $request The request sent to the WP REST API.
709
	 *
710
	 * @return object Jetpack miscellaneous settings.
711
	 */
712
	public static function update_setting( $request ) {
713
		// Get parameters to update the module.
714
		$param = $request->get_params();
715
716
		// Exit if no parameters were passed.
717 View Code Duplication
		if ( ! is_array( $param ) ) {
718
			return new WP_Error( 'missing_setting', esc_html__( 'Missing setting.', 'jetpack' ), array( 'status' => 404 ) );
719
		}
720
721
		// Get option name and value.
722
		$option = key( $param );
723
		$value  = current( $param );
724
725
		// Log success or not
726
		$updated = false;
727
728
		switch ( $option ) {
729
			case self::holiday_snow_option_name():
730
				$updated = update_option( $option, ( true == (bool) $value ) ? 'letitsnow' : '' );
731
				break;
732
		}
733
734
		if ( $updated ) {
735
			return rest_ensure_response( array(
736
				'code' 	  => 'success',
737
				'message' => esc_html__( 'Setting updated.', 'jetpack' ),
738
				'value'   => $value,
739
			) );
740
		}
741
742
		return new WP_Error( 'setting_not_updated', esc_html__( 'The setting was not updated.', 'jetpack' ), array( 'status' => 400 ) );
743
	}
744
745
	/**
746
	 * Unlinks current user from the WordPress.com Servers.
747
	 *
748
	 * @since 4.3.0
749
	 * @uses  Jetpack::unlink_user
750
	 *
751
	 * @param WP_REST_Request $request The request sent to the WP REST API.
752
	 *
753
	 * @return bool|WP_Error True if user successfully unlinked.
754
	 */
755 View Code Duplication
	public static function unlink_user( $request ) {
756
757
		if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) {
758
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
759
		}
760
761
		if ( Jetpack::unlink_user() ) {
762
			return rest_ensure_response(
763
				array(
764
					'code' => 'success'
765
				)
766
			);
767
		}
768
769
		return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
770
	}
771
772
	/**
773
	 * Get site data, including for example, the site's current plan.
774
	 *
775
	 * @since 4.3.0
776
	 *
777
	 * @return array Array of Jetpack modules.
778
	 */
779
	public static function get_site_data() {
780
781
		if ( $site_id = Jetpack_Options::get_option( 'id' ) ) {
782
783
			$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1' );
784
785
			if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
786
				return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
787
			}
788
789
			// Save plan details in the database for future use without API calls
790
			$results = json_decode( $response['body'], true );
791
792
			if ( is_array( $results ) && isset( $results['plan'] ) ) {
793
				update_option( 'jetpack_active_plan', $results['plan'] );
794
			}
795
796
			return rest_ensure_response( array(
797
					'code' => 'success',
798
					'message' => esc_html__( 'Site data correctly received.', 'jetpack' ),
799
					'data' => wp_remote_retrieve_body( $response ),
800
				)
801
			);
802
		}
803
804
		return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
805
	}
806
807
	/**
808
	 * Handles identity crisis mitigation, confirming safe mode for this site.
809
	 *
810
	 * @since 4.4.0
811
	 *
812
	 * @return bool | WP_Error True if option is properly set.
813
	 */
814
	public static function confirm_safe_mode() {
815
		$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
816
		if ( $updated ) {
817
			return rest_ensure_response(
818
				array(
819
					'code' => 'success'
820
				)
821
			);
822
		}
823
		return new WP_Error(
824
			'error_setting_jetpack_safe_mode',
825
			esc_html__( 'Could not confirm safe mode.', 'jetpack' ),
826
			array( 'status' => 500 )
827
		);
828
	}
829
830
	/**
831
	 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
832
	 *
833
	 * @since 4.4.0
834
	 *
835
	 * @return bool | WP_Error True if option is properly set.
836
	 */
837
	public static function migrate_stats_and_subscribers() {
838
		if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
839
			return new WP_Error(
840
				'error_deleting_sync_error_idc',
841
				esc_html__( 'Could not delete sync error option.', 'jetpack' ),
842
				array( 'status' => 500 )
843
			);
844
		}
845
846
		if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
847
			return rest_ensure_response(
848
				array(
849
					'code' => 'success'
850
				)
851
			);
852
		}
853
		return new WP_Error(
854
			'error_setting_jetpack_migrate',
855
			esc_html__( 'Could not confirm migration.', 'jetpack' ),
856
			array( 'status' => 500 )
857
		);
858
	}
859
860
	/**
861
	 * This IDC resolution will disconnect the site and re-connect to a completely new
862
	 * and separate shadow site than the original.
863
	 *
864
	 * It will first will disconnect the site without phoning home as to not disturb the production site.
865
	 * It then builds a fresh connection URL and sends it back along with the response.
866
	 *
867
	 * @since 4.4.0
868
	 * @return bool|WP_Error
869
	 */
870
	public static function start_fresh_connection() {
871
		// First clear the options / disconnect.
872
		Jetpack::disconnect();
873
		return self::build_connect_url();
874
	}
875
876
	/**
877
	 * Reset Jetpack options
878
	 *
879
	 * @since 4.3.0
880
	 *
881
	 * @param WP_REST_Request $request {
882
	 *     Array of parameters received by request.
883
	 *
884
	 *     @type string $options Available options to reset are options|modules
885
	 * }
886
	 *
887
	 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
888
	 */
889
	public static function reset_jetpack_options( $request ) {
890
891
		if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) {
892
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
893
		}
894
895
		if ( isset( $request['options'] ) ) {
896
			$data = $request['options'];
897
898
			switch( $data ) {
899
				case ( 'options' ) :
900
					$options_to_reset = Jetpack::get_jetpack_options_for_reset();
901
902
					// Reset the Jetpack options
903
					foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
904
						Jetpack_Options::delete_option( $option_to_reset );
905
					}
906
907
					foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
908
						delete_option( $option_to_reset );
909
					}
910
911
					// Reset to default modules
912
					$default_modules = Jetpack::get_default_modules();
913
					Jetpack::update_active_modules( $default_modules );
914
915
					// Jumpstart option is special
916
					Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
917
					return rest_ensure_response( array(
918
						'code' 	  => 'success',
919
						'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ),
920
					) );
921
					break;
922
923
				case 'modules':
924
					$default_modules = Jetpack::get_default_modules();
925
					Jetpack::update_active_modules( $default_modules );
926
					return rest_ensure_response( array(
927
						'code' 	  => 'success',
928
						'message' => esc_html__( 'Modules reset to default.', 'jetpack' ),
929
					) );
930
					break;
931
932
				default:
933
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
934
			}
935
		}
936
937
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
938
	}
939
940
	/**
941
	 * Retrieves the current status of Jumpstart.
942
	 *
943
	 * @since 4.5.0
944
	 *
945
	 * @return bool
946
	 */
947
	public static function jumpstart_status() {
948
		return array(
949
			'status' => Jetpack_Options::get_option( 'jumpstart' )
950
		);
951
	}
952
953
	/**
954
	 * Toggles activation or deactivation of the JumpStart
955
	 *
956
	 * @since 4.3.0
957
	 *
958
	 * @param WP_REST_Request $request The request sent to the WP REST API.
959
	 *
960
	 * @return bool|WP_Error True if toggling Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
961
	 */
962
	public static function jumpstart_toggle( $request ) {
963
964
		if ( $request[ 'active' ] ) {
965
			return self::jumpstart_activate( $request );
966
		} else {
967
			return self::jumpstart_deactivate( $request );
968
		}
969
	}
970
971
	/**
972
	 * Activates a series of valid Jetpack modules and initializes some options.
973
	 *
974
	 * @since 4.3.0
975
	 *
976
	 * @param WP_REST_Request $request The request sent to the WP REST API.
977
	 *
978
	 * @return bool|WP_Error True if Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
979
	 */
980
	public static function jumpstart_activate( $request ) {
981
		$modules = Jetpack::get_available_modules();
982
		$activate_modules = array();
983
		foreach ( $modules as $module ) {
984
			$module_info = Jetpack::get_module( $module );
985
			if ( isset( $module_info['feature'] ) && is_array( $module_info['feature'] ) && in_array( 'Jumpstart', $module_info['feature'] ) ) {
986
				$activate_modules[] = $module;
987
			}
988
		}
989
990
		// Collect success/error messages like modules that are properly activated.
991
		$result = array(
992
			'activated_modules' => array(),
993
			'failed_modules'    => array(),
994
		);
995
996
		// Update the jumpstart option
997
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
998
			$result['jumpstart_activated'] = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' );
999
		}
1000
1001
		// Check for possible conflicting plugins
1002
		$module_slugs_filtered = Jetpack::init()->filter_default_modules( $activate_modules );
1003
1004
		foreach ( $module_slugs_filtered as $module_slug ) {
1005
			Jetpack::log( 'activate', $module_slug );
1006
			if ( Jetpack::activate_module( $module_slug, false, false ) ) {
1007
				$result['activated_modules'][] = $module_slug;
1008
			} else {
1009
				$result['failed_modules'][] = $module_slug;
1010
			}
1011
		}
1012
1013
		// Set the default sharing buttons and set to display on posts if none have been set.
1014
		$sharing_services = get_option( 'sharing-services' );
1015
		$sharing_options  = get_option( 'sharing-options' );
1016
		if ( empty( $sharing_services['visible'] ) ) {
1017
			// Default buttons to set
1018
			$visible = array(
1019
				'twitter',
1020
				'facebook',
1021
				'google-plus-1',
1022
			);
1023
			$hidden = array();
1024
1025
			// Set some sharing settings
1026
			if ( class_exists( 'Sharing_Service' ) ) {
1027
				$sharing = new Sharing_Service();
1028
				$sharing_options['global'] = array(
1029
					'button_style'  => 'icon',
1030
					'sharing_label' => $sharing->default_sharing_label,
1031
					'open_links'    => 'same',
1032
					'show'          => array( 'post' ),
1033
					'custom'        => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array()
1034
				);
1035
1036
				$result['sharing_options']  = update_option( 'sharing-options', $sharing_options );
1037
				$result['sharing_services'] = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) );
1038
			}
1039
		}
1040
1041
		// If all Jumpstart modules were activated
1042
		if ( empty( $result['failed_modules'] ) ) {
1043
			return rest_ensure_response( array(
1044
				'code' 	  => 'success',
1045
				'message' => esc_html__( 'Jumpstart done.', 'jetpack' ),
1046
				'data'    => $result,
1047
			) );
1048
		}
1049
1050
		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 ) );
1051
	}
1052
1053
	/**
1054
	 * Dismisses Jumpstart so user is not prompted to go through it again.
1055
	 *
1056
	 * @since 4.3.0
1057
	 *
1058
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1059
	 *
1060
	 * @return bool|WP_Error True if Jumpstart was disabled or was nothing to dismiss. Otherwise, a WP_Error instance with a message.
1061
	 */
1062
	public static function jumpstart_deactivate( $request ) {
1063
1064
		// If dismissed, flag the jumpstart option as such.
1065
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
1066
			if ( Jetpack_Options::update_option( 'jumpstart', 'jumpstart_dismissed' ) ) {
1067
				return rest_ensure_response( array(
1068
					'code' 	  => 'success',
1069
					'message' => esc_html__( 'Jumpstart dismissed.', 'jetpack' ),
1070
				) );
1071
			} else {
1072
				return new WP_Error( 'jumpstart_failed_dismiss', esc_html__( 'Jumpstart could not be dismissed.', 'jetpack' ), array( 'status' => 400 ) );
1073
			}
1074
		}
1075
1076
		// If this was not a new connection and there was nothing to dismiss, don't fail.
1077
		return rest_ensure_response( array(
1078
			'code' 	  => 'success',
1079
			'message' => esc_html__( 'Nothing to dismiss. This was not a new connection.', 'jetpack' ),
1080
		) );
1081
	}
1082
1083
	/**
1084
	 * Get the query parameters to update module options or general settings.
1085
	 *
1086
	 * @since 4.3.0
1087
	 * @since 4.4.0 Accepts a $selector parameter.
1088
	 *
1089
	 * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'.
1090
	 *
1091
	 * @return array
1092
	 */
1093
	public static function get_updateable_parameters( $selector = '' ) {
1094
		$parameters = array(
1095
			'context'     => array(
1096
				'default' => 'edit',
1097
			),
1098
		);
1099
1100
		return array_merge( $parameters, self::get_updateable_data_list( $selector ) );
1101
	}
1102
1103
	/**
1104
	 * Returns a list of module options or general settings that can be updated.
1105
	 *
1106
	 * @since 4.3.0
1107
	 * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list.
1108
	 *
1109
	 * @param string|array $selector Module slug, 'any', or an array of parameters.
1110
	 *                               If empty, it's assumed we're updating a module and we'll try to get its slug.
1111
	 *                               If 'any' the full list is returned.
1112
	 *                               If it's an array of parameters, includes the elements by matching keys.
1113
	 *
1114
	 * @return array
1115
	 */
1116
	public static function get_updateable_data_list( $selector = '' ) {
1117
1118
		$options = array(
1119
1120
			// Carousel
1121
			'carousel_background_color' => array(
1122
				'description'       => esc_html__( 'Color scheme.', 'jetpack' ),
1123
				'type'              => 'string',
1124
				'default'           => 'black',
1125
				'enum'              => array(
1126
					'black',
1127
					'white',
1128
				),
1129
				'enum_labels' => array(
1130
					'black' => esc_html__( 'Black', 'jetpack' ),
1131
					'white' => esc_html__( 'White', 'jetpack' ),
1132
				),
1133
				'validate_callback' => __CLASS__ . '::validate_list_item',
1134
				'jp_group'          => 'carousel',
1135
			),
1136
			'carousel_display_exif' => array(
1137
				'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 ) ) ),
1138
				'type'              => 'boolean',
1139
				'default'           => 0,
1140
				'validate_callback' => __CLASS__ . '::validate_boolean',
1141
				'jp_group'          => 'carousel',
1142
			),
1143
1144
			// Comments
1145
			'highlander_comment_form_prompt' => array(
1146
				'description'       => esc_html__( 'Greeting Text', 'jetpack' ),
1147
				'type'              => 'string',
1148
				'default'           => esc_html__( 'Leave a Reply', 'jetpack' ),
1149
				'sanitize_callback' => 'sanitize_text_field',
1150
				'jp_group'          => 'comments',
1151
			),
1152
			'jetpack_comment_form_color_scheme' => array(
1153
				'description'       => esc_html__( "Color scheme", 'jetpack' ),
1154
				'type'              => 'string',
1155
				'default'           => 'light',
1156
				'enum'              => array(
1157
					'light',
1158
					'dark',
1159
					'transparent',
1160
				),
1161
				'enum_labels' => array(
1162
					'light'       => esc_html__( 'Light', 'jetpack' ),
1163
					'dark'        => esc_html__( 'Dark', 'jetpack' ),
1164
					'transparent' => esc_html__( 'Transparent', 'jetpack' ),
1165
				),
1166
				'validate_callback' => __CLASS__ . '::validate_list_item',
1167
				'jp_group'          => 'comments',
1168
			),
1169
1170
			// Custom Content Types
1171
			'jetpack_portfolio' => array(
1172
				'description'       => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1173
				'type'              => 'boolean',
1174
				'default'           => 0,
1175
				'validate_callback' => __CLASS__ . '::validate_boolean',
1176
				'jp_group'          => 'custom-content-types',
1177
			),
1178
			'jetpack_portfolio_posts_per_page' => array(
1179
				'description'       => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1180
				'type'              => 'integer',
1181
				'default'           => 10,
1182
				'validate_callback' => __CLASS__ . '::validate_posint',
1183
				'jp_group'          => 'custom-content-types',
1184
			),
1185
			'jetpack_testimonial' => array(
1186
				'description'       => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1187
				'type'              => 'boolean',
1188
				'default'           => 0,
1189
				'validate_callback' => __CLASS__ . '::validate_boolean',
1190
				'jp_group'          => 'custom-content-types',
1191
			),
1192
			'jetpack_testimonial_posts_per_page' => array(
1193
				'description'       => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1194
				'type'              => 'integer',
1195
				'default'           => 10,
1196
				'validate_callback' => __CLASS__ . '::validate_posint',
1197
				'jp_group'          => 'custom-content-types',
1198
			),
1199
1200
			// Galleries
1201
			'tiled_galleries' => array(
1202
				'description'       => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1203
				'type'              => 'boolean',
1204
				'default'           => 0,
1205
				'validate_callback' => __CLASS__ . '::validate_boolean',
1206
				'jp_group'          => 'tiled-gallery',
1207
			),
1208
1209
			'gravatar_disable_hovercards' => array(
1210
				'description'       => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1211
				'type'              => 'string',
1212
				'default'           => 'enabled',
1213
				// Not visible. This is used as the checkbox value.
1214
				'enum'              => array(
1215
					'enabled',
1216
					'disabled',
1217
				),
1218
				'enum_labels' => array(
1219
					'enabled'  => esc_html__( 'Enabled', 'jetpack' ),
1220
					'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1221
				),
1222
				'validate_callback' => __CLASS__ . '::validate_list_item',
1223
				'jp_group'          => 'gravatar-hovercards',
1224
			),
1225
1226
			// Infinite Scroll
1227
			'infinite_scroll' => array(
1228
				'description'       => esc_html__( 'To infinity and beyond', 'jetpack' ),
1229
				'type'              => 'boolean',
1230
				'default'           => 1,
1231
				'validate_callback' => __CLASS__ . '::validate_boolean',
1232
				'jp_group'          => 'infinite-scroll',
1233
			),
1234
			'infinite_scroll_google_analytics' => array(
1235
				'description'       => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1236
				'type'              => 'boolean',
1237
				'default'           => 0,
1238
				'validate_callback' => __CLASS__ . '::validate_boolean',
1239
				'jp_group'          => 'infinite-scroll',
1240
			),
1241
1242
			// Likes
1243
			'wpl_default' => array(
1244
				'description'       => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1245
				'type'              => 'string',
1246
				'default'           => 'on',
1247
				'enum'              => array(
1248
					'on',
1249
					'off',
1250
				),
1251
				'enum_labels' => array(
1252
					'on'  => esc_html__( 'On for all posts', 'jetpack' ),
1253
					'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1254
				),
1255
				'validate_callback' => __CLASS__ . '::validate_list_item',
1256
				'jp_group'          => 'likes',
1257
			),
1258
			'social_notifications_like' => array(
1259
				'description'       => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1260
				'type'              => 'boolean',
1261
				'default'           => 1,
1262
				'validate_callback' => __CLASS__ . '::validate_boolean',
1263
				'jp_group'          => 'likes',
1264
			),
1265
1266
			// Markdown
1267
			'wpcom_publish_comments_with_markdown' => array(
1268
				'description'       => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1269
				'type'              => 'boolean',
1270
				'default'           => 0,
1271
				'validate_callback' => __CLASS__ . '::validate_boolean',
1272
				'jp_group'          => 'markdown',
1273
			),
1274
			'wpcom_publish_posts_with_markdown' => array(
1275
				'description'       => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1276
				'type'              => 'boolean',
1277
				'default'           => 0,
1278
				'validate_callback' => __CLASS__ . '::validate_boolean',
1279
				'jp_group'          => 'markdown',
1280
			),
1281
1282
			// Mobile Theme
1283
			'wp_mobile_excerpt' => array(
1284
				'description'       => esc_html__( 'Excerpts', 'jetpack' ),
1285
				'type'              => 'boolean',
1286
				'default'           => 0,
1287
				'validate_callback' => __CLASS__ . '::validate_boolean',
1288
				'jp_group'          => 'minileven',
1289
			),
1290
			'wp_mobile_featured_images' => array(
1291
				'description'       => esc_html__( 'Featured Images', 'jetpack' ),
1292
				'type'              => 'boolean',
1293
				'default'           => 0,
1294
				'validate_callback' => __CLASS__ . '::validate_boolean',
1295
				'jp_group'          => 'minileven',
1296
			),
1297
			'wp_mobile_app_promos' => array(
1298
				'description'       => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ),
1299
				'type'              => 'boolean',
1300
				'default'           => 0,
1301
				'validate_callback' => __CLASS__ . '::validate_boolean',
1302
				'jp_group'          => 'minileven',
1303
			),
1304
1305
			// Monitor
1306
			'monitor_receive_notifications' => array(
1307
				'description'       => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1308
				'type'              => 'boolean',
1309
				'default'           => 0,
1310
				'validate_callback' => __CLASS__ . '::validate_boolean',
1311
				'jp_group'          => 'monitor',
1312
			),
1313
1314
			// Post by Email
1315
			'post_by_email_address' => array(
1316
				'description'       => esc_html__( 'Email Address', 'jetpack' ),
1317
				'type'              => 'string',
1318
				'default'           => 'noop',
1319
				'enum'              => array(
1320
					'noop',
1321
					'create',
1322
					'regenerate',
1323
					'delete',
1324
				),
1325
				'enum_labels' => array(
1326
					'noop'       => '',
1327
					'create'     => esc_html__( 'Create Post by Email address', 'jetpack' ),
1328
					'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1329
					'delete'     => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1330
				),
1331
				'validate_callback' => __CLASS__ . '::validate_list_item',
1332
				'jp_group'          => 'post-by-email',
1333
			),
1334
1335
			// Protect
1336
			'jetpack_protect_key' => array(
1337
				'description'       => esc_html__( 'Protect API key', 'jetpack' ),
1338
				'type'              => 'string',
1339
				'default'           => '',
1340
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1341
				'jp_group'          => 'protect',
1342
			),
1343
			'jetpack_protect_global_whitelist' => array(
1344
				'description'       => esc_html__( 'Protect global whitelist', 'jetpack' ),
1345
				'type'              => 'string',
1346
				'default'           => '',
1347
				'validate_callback' => __CLASS__ . '::validate_string',
1348
				'sanitize_callback' => 'esc_textarea',
1349
				'jp_group'          => 'protect',
1350
			),
1351
1352
			// Sharing
1353
			'sharing_services' => array(
1354
				'description'       => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1355
				'type'              => 'object',
1356
				'default'           => array(
1357
					'visible' => array( 'twitter', 'facebook', 'google-plus-1' ),
1358
					'hidden'  => array(),
1359
				),
1360
				'validate_callback' => __CLASS__ . '::validate_services',
1361
				'jp_group'          => 'sharedaddy',
1362
			),
1363
			'button_style' => array(
1364
				'description'       => esc_html__( 'Button Style', 'jetpack' ),
1365
				'type'              => 'string',
1366
				'default'           => 'icon',
1367
				'enum'              => array(
1368
					'icon-text',
1369
					'icon',
1370
					'text',
1371
					'official',
1372
				),
1373
				'enum_labels' => array(
1374
					'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1375
					'icon'      => esc_html__( 'Icon only', 'jetpack' ),
1376
					'text'      => esc_html__( 'Text only', 'jetpack' ),
1377
					'official'  => esc_html__( 'Official buttons', 'jetpack' ),
1378
				),
1379
				'validate_callback' => __CLASS__ . '::validate_list_item',
1380
				'jp_group'          => 'sharedaddy',
1381
			),
1382
			'sharing_label' => array(
1383
				'description'       => esc_html__( 'Sharing Label', 'jetpack' ),
1384
				'type'              => 'string',
1385
				'default'           => '',
1386
				'validate_callback' => __CLASS__ . '::validate_string',
1387
				'sanitize_callback' => 'esc_html',
1388
				'jp_group'          => 'sharedaddy',
1389
			),
1390
			'show' => array(
1391
				'description'       => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1392
				'type'              => 'array',
1393
				'items'             => array(
1394
					'type' => 'string'
1395
				),
1396
				'default'           => array( 'post' ),
1397
				'validate_callback' => __CLASS__ . '::validate_sharing_show',
1398
				'jp_group'          => 'sharedaddy',
1399
			),
1400
			'jetpack-twitter-cards-site-tag' => array(
1401
				'description'       => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
1402
				'type'              => 'string',
1403
				'default'           => '',
1404
				'validate_callback' => __CLASS__ . '::validate_twitter_username',
1405
				'sanitize_callback' => 'esc_html',
1406
				'jp_group'          => 'sharedaddy',
1407
			),
1408
			'sharedaddy_disable_resources' => array(
1409
				'description'       => esc_html__( 'Disable CSS and JS', 'jetpack' ),
1410
				'type'              => 'boolean',
1411
				'default'           => 0,
1412
				'validate_callback' => __CLASS__ . '::validate_boolean',
1413
				'jp_group'          => 'sharedaddy',
1414
			),
1415
			'custom' => array(
1416
				'description'       => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
1417
				'type'              => 'object',
1418
				'default'           => array(
1419
					'sharing_name' => '',
1420
					'sharing_url'  => '',
1421
					'sharing_icon' => '',
1422
				),
1423
				'validate_callback' => __CLASS__ . '::validate_custom_service',
1424
				'jp_group'          => 'sharedaddy',
1425
			),
1426
			// Not an option, but an action that can be perfomed on the list of custom services passing the service ID.
1427
			'sharing_delete_service' => array(
1428
				'description'       => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
1429
				'type'              => 'string',
1430
				'default'           => '',
1431
				'validate_callback' => __CLASS__ . '::validate_custom_service_id',
1432
				'jp_group'          => 'sharedaddy',
1433
			),
1434
1435
			// SSO
1436
			'jetpack_sso_require_two_step' => array(
1437
				'description'       => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
1438
				'type'              => 'boolean',
1439
				'default'           => 0,
1440
				'validate_callback' => __CLASS__ . '::validate_boolean',
1441
				'jp_group'          => 'sso',
1442
			),
1443
			'jetpack_sso_match_by_email' => array(
1444
				'description'       => esc_html__( 'Match by Email', 'jetpack' ),
1445
				'type'              => 'boolean',
1446
				'default'           => 0,
1447
				'validate_callback' => __CLASS__ . '::validate_boolean',
1448
				'jp_group'          => 'sso',
1449
			),
1450
1451
			// Subscriptions
1452
			'stb_enabled' => array(
1453
				'description'       => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ),
1454
				'type'              => 'boolean',
1455
				'default'           => 1,
1456
				'validate_callback' => __CLASS__ . '::validate_boolean',
1457
				'jp_group'          => 'subscriptions',
1458
			),
1459
			'stc_enabled' => array(
1460
				'description'       => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
1461
				'type'              => 'boolean',
1462
				'default'           => 1,
1463
				'validate_callback' => __CLASS__ . '::validate_boolean',
1464
				'jp_group'          => 'subscriptions',
1465
			),
1466
1467
			// Related Posts
1468
			'show_headline' => array(
1469
				'description'       => esc_html__( 'Show a "Related" header to more clearly separate the related section from posts', 'jetpack' ),
1470
				'type'              => 'boolean',
1471
				'default'           => 1,
1472
				'validate_callback' => __CLASS__ . '::validate_boolean',
1473
				'jp_group'          => 'related-posts',
1474
			),
1475
			'show_thumbnails' => array(
1476
				'description'       => esc_html__( 'Use a large and visually striking layout', 'jetpack' ),
1477
				'type'              => 'boolean',
1478
				'default'           => 0,
1479
				'validate_callback' => __CLASS__ . '::validate_boolean',
1480
				'jp_group'          => 'related-posts',
1481
			),
1482
1483
			// Spelling and Grammar - After the Deadline
1484
			'onpublish' => array(
1485
				'description'       => esc_html__( 'Proofread when a post or page is first published.', 'jetpack' ),
1486
				'type'              => 'boolean',
1487
				'default'           => 0,
1488
				'validate_callback' => __CLASS__ . '::validate_boolean',
1489
				'jp_group'          => 'after-the-deadline',
1490
			),
1491
			'onupdate' => array(
1492
				'description'       => esc_html__( 'Proofread when a post or page is updated.', 'jetpack' ),
1493
				'type'              => 'boolean',
1494
				'default'           => 0,
1495
				'validate_callback' => __CLASS__ . '::validate_boolean',
1496
				'jp_group'          => 'after-the-deadline',
1497
			),
1498
			'Bias Language' => array(
1499
				'description'       => esc_html__( 'Bias Language', 'jetpack' ),
1500
				'type'              => 'boolean',
1501
				'default'           => 0,
1502
				'validate_callback' => __CLASS__ . '::validate_boolean',
1503
				'jp_group'          => 'after-the-deadline',
1504
			),
1505
			'Cliches' => array(
1506
				'description'       => esc_html__( 'Clichés', 'jetpack' ),
1507
				'type'              => 'boolean',
1508
				'default'           => 0,
1509
				'validate_callback' => __CLASS__ . '::validate_boolean',
1510
				'jp_group'          => 'after-the-deadline',
1511
			),
1512
			'Complex Expression' => array(
1513
				'description'       => esc_html__( 'Complex Phrases', 'jetpack' ),
1514
				'type'              => 'boolean',
1515
				'default'           => 0,
1516
				'validate_callback' => __CLASS__ . '::validate_boolean',
1517
				'jp_group'          => 'after-the-deadline',
1518
			),
1519
			'Diacritical Marks' => array(
1520
				'description'       => esc_html__( 'Diacritical Marks', 'jetpack' ),
1521
				'type'              => 'boolean',
1522
				'default'           => 0,
1523
				'validate_callback' => __CLASS__ . '::validate_boolean',
1524
				'jp_group'          => 'after-the-deadline',
1525
			),
1526
			'Double Negative' => array(
1527
				'description'       => esc_html__( 'Double Negatives', 'jetpack' ),
1528
				'type'              => 'boolean',
1529
				'default'           => 0,
1530
				'validate_callback' => __CLASS__ . '::validate_boolean',
1531
				'jp_group'          => 'after-the-deadline',
1532
			),
1533
			'Hidden Verbs' => array(
1534
				'description'       => esc_html__( 'Hidden Verbs', 'jetpack' ),
1535
				'type'              => 'boolean',
1536
				'default'           => 0,
1537
				'validate_callback' => __CLASS__ . '::validate_boolean',
1538
				'jp_group'          => 'after-the-deadline',
1539
			),
1540
			'Jargon Language' => array(
1541
				'description'       => esc_html__( 'Jargon', 'jetpack' ),
1542
				'type'              => 'boolean',
1543
				'default'           => 0,
1544
				'validate_callback' => __CLASS__ . '::validate_boolean',
1545
				'jp_group'          => 'after-the-deadline',
1546
			),
1547
			'Passive voice' => array(
1548
				'description'       => esc_html__( 'Passive Voice', 'jetpack' ),
1549
				'type'              => 'boolean',
1550
				'default'           => 0,
1551
				'validate_callback' => __CLASS__ . '::validate_boolean',
1552
				'jp_group'          => 'after-the-deadline',
1553
			),
1554
			'Phrases to Avoid' => array(
1555
				'description'       => esc_html__( 'Phrases to Avoid', 'jetpack' ),
1556
				'type'              => 'boolean',
1557
				'default'           => 0,
1558
				'validate_callback' => __CLASS__ . '::validate_boolean',
1559
				'jp_group'          => 'after-the-deadline',
1560
			),
1561
			'Redundant Expression' => array(
1562
				'description'       => esc_html__( 'Redundant Phrases', 'jetpack' ),
1563
				'type'              => 'boolean',
1564
				'default'           => 0,
1565
				'validate_callback' => __CLASS__ . '::validate_boolean',
1566
				'jp_group'          => 'after-the-deadline',
1567
			),
1568
			'guess_lang' => array(
1569
				'description'       => esc_html__( 'Use automatically detected language to proofread posts and pages', 'jetpack' ),
1570
				'type'              => 'boolean',
1571
				'default'           => 0,
1572
				'validate_callback' => __CLASS__ . '::validate_boolean',
1573
				'jp_group'          => 'after-the-deadline',
1574
			),
1575
			'ignored_phrases' => array(
1576
				'description'       => esc_html__( 'Add Phrase to be ignored', 'jetpack' ),
1577
				'type'              => 'string',
1578
				'default'           => '',
1579
				'sanitize_callback' => 'esc_html',
1580
				'jp_group'          => 'after-the-deadline',
1581
			),
1582
			'unignore_phrase' => array(
1583
				'description'       => esc_html__( 'Remove Phrase from being ignored', 'jetpack' ),
1584
				'type'              => 'string',
1585
				'default'           => '',
1586
				'sanitize_callback' => 'esc_html',
1587
				'jp_group'          => 'after-the-deadline',
1588
			),
1589
1590
			// Verification Tools
1591
			'google' => array(
1592
				'description'       => esc_html__( 'Google Search Console', 'jetpack' ),
1593
				'type'              => 'string',
1594
				'default'           => '',
1595
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1596
				'jp_group'          => 'verification-tools',
1597
			),
1598
			'bing' => array(
1599
				'description'       => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
1600
				'type'              => 'string',
1601
				'default'           => '',
1602
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1603
				'jp_group'          => 'verification-tools',
1604
			),
1605
			'pinterest' => array(
1606
				'description'       => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
1607
				'type'              => 'string',
1608
				'default'           => '',
1609
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1610
				'jp_group'          => 'verification-tools',
1611
			),
1612
			'yandex' => array(
1613
				'description'       => esc_html__( 'Yandex Site Verification', 'jetpack' ),
1614
				'type'              => 'string',
1615
				'default'           => '',
1616
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1617
				'jp_group'          => 'verification-tools',
1618
			),
1619
			'enable_header_ad' => array(
1620
				'description'        => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
1621
				'type'               => 'boolean',
1622
				'default'            => 0,
1623
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1624
				'jp_group'           => 'wordads',
1625
			),
1626
			'wordads_approved' => array(
1627
				'description'        => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
1628
				'type'               => 'boolean',
1629
				'default'            => 0,
1630
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1631
				'jp_group'           => 'wordads',
1632
			),
1633
			'wordads_second_belowpost' => array(
1634
				'description'        => esc_html__( 'Display second ad below post?', 'jetpack' ),
1635
				'type'               => 'boolean',
1636
				'default'            => 1,
1637
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1638
				'jp_group'           => 'wordads',
1639
			),
1640
			'wordads_display_front_page' => array(
1641
				'description'        => esc_html__( 'Display ads on the front page?', 'jetpack' ),
1642
				'type'               => 'boolean',
1643
				'default'            => 1,
1644
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1645
				'jp_group'           => 'wordads',
1646
			),
1647
			'wordads_display_post' => array(
1648
				'description'        => esc_html__( 'Display ads on posts?', 'jetpack' ),
1649
				'type'               => 'boolean',
1650
				'default'            => 1,
1651
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1652
				'jp_group'           => 'wordads',
1653
			),
1654
			'wordads_display_page' => array(
1655
				'description'        => esc_html__( 'Display ads on pages?', 'jetpack' ),
1656
				'type'               => 'boolean',
1657
				'default'            => 1,
1658
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1659
				'jp_group'           => 'wordads',
1660
			),
1661
			'wordads_display_archive' => array(
1662
				'description'        => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
1663
				'type'               => 'boolean',
1664
				'default'            => 1,
1665
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1666
				'jp_group'           => 'wordads',
1667
			),
1668
1669
			// Google Analytics
1670
			'google_analytics_tracking_id' => array(
1671
				'description'        => esc_html__( 'Google Analytics', 'jetpack' ),
1672
				'type'               => 'string',
1673
				'default'            => '',
1674
				'validate_callback'  => __CLASS__ . '::validate_alphanum',
1675
				'jp_group'           => 'google-analytics',
1676
			),
1677
1678
			// Stats
1679
			'admin_bar' => array(
1680
				'description'       => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ),
1681
				'type'              => 'boolean',
1682
				'default'           => 1,
1683
				'validate_callback' => __CLASS__ . '::validate_boolean',
1684
				'jp_group'          => 'stats',
1685
			),
1686
			'roles' => array(
1687
				'description'       => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
1688
				'type'              => 'array',
1689
				'items'             => array(
1690
					'type' => 'string'
1691
				),
1692
				'default'           => array( 'administrator' ),
1693
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
1694
				'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
1695
				'jp_group'          => 'stats',
1696
			),
1697
			'count_roles' => array(
1698
				'description'       => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
1699
				'type'              => 'array',
1700
				'items'             => array(
1701
					'type' => 'string'
1702
				),
1703
				'default'           => array( 'administrator' ),
1704
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
1705
				'jp_group'          => 'stats',
1706
			),
1707
			'blog_id' => array(
1708
				'description'       => esc_html__( 'Blog ID.', 'jetpack' ),
1709
				'type'              => 'boolean',
1710
				'default'           => 0,
1711
				'validate_callback' => __CLASS__ . '::validate_boolean',
1712
				'jp_group'          => 'stats',
1713
			),
1714
			'do_not_track' => array(
1715
				'description'       => esc_html__( 'Do not track.', 'jetpack' ),
1716
				'type'              => 'boolean',
1717
				'default'           => 1,
1718
				'validate_callback' => __CLASS__ . '::validate_boolean',
1719
				'jp_group'          => 'stats',
1720
			),
1721
			'hide_smile' => array(
1722
				'description'       => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ),
1723
				'type'              => 'boolean',
1724
				'default'           => 1,
1725
				'validate_callback' => __CLASS__ . '::validate_boolean',
1726
				'jp_group'          => 'stats',
1727
			),
1728
			'version' => array(
1729
				'description'       => esc_html__( 'Version.', 'jetpack' ),
1730
				'type'              => 'integer',
1731
				'default'           => 9,
1732
				'validate_callback' => __CLASS__ . '::validate_posint',
1733
				'jp_group'          => 'stats',
1734
			),
1735
1736
			// Settings - Not a module
1737
			self::holiday_snow_option_name() => array(
1738
				'description'       => '',
1739
				'type'              => 'boolean',
1740
				'default'           => 0,
1741
				'validate_callback' => __CLASS__ . '::validate_boolean',
1742
				'jp_group'          => 'settings',
1743
			),
1744
1745
			// Akismet - Not a module, but a plugin. The options can be passed and handled differently.
1746
			'akismet_show_user_comments_approved' => array(
1747
				'description'       => '',
1748
				'type'              => 'boolean',
1749
				'default'           => 0,
1750
				'validate_callback' => __CLASS__ . '::validate_boolean',
1751
				'jp_group'          => 'settings',
1752
			),
1753
1754
			'wordpress_api_key' => array(
1755
				'description'       => '',
1756
				'type'              => 'string',
1757
				'default'           => '',
1758
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1759
				'jp_group'          => 'settings',
1760
			),
1761
1762
			// Apps card on dashboard
1763
			'dismiss_dash_app_card' => array(
1764
				'description'       => '',
1765
				'type'              => 'boolean',
1766
				'default'           => 0,
1767
				'validate_callback' => __CLASS__ . '::validate_boolean',
1768
				'jp_group'          => 'settings',
1769
			),
1770
1771
			// Empty stats card dismiss
1772
			'dismiss_empty_stats_card' => array(
1773
				'description'       => '',
1774
				'type'              => 'boolean',
1775
				'default'           => 0,
1776
				'validate_callback' => __CLASS__ . '::validate_boolean',
1777
				'jp_group'          => 'settings',
1778
			),
1779
1780
		);
1781
1782
		// Add modules to list so they can be toggled
1783
		$modules = Jetpack::get_available_modules();
1784
		if ( is_array( $modules ) && ! empty( $modules ) ) {
1785
			$module_args = array(
1786
				'description'       => '',
1787
				'type'              => 'boolean',
1788
				'default'           => 0,
1789
				'validate_callback' => __CLASS__ . '::validate_boolean',
1790
				'jp_group'          => 'modules',
1791
			);
1792
			foreach( $modules as $module ) {
1793
				$options[ $module ] = $module_args;
1794
			}
1795
		}
1796
1797
		if ( is_array( $selector ) ) {
1798
1799
			// Return only those options whose keys match $selector keys
1800
			return array_intersect_key( $options, $selector );
1801
		}
1802
1803
		if ( 'any' === $selector ) {
1804
1805
			// Toggle module or update any module option or any general setting
1806
			return $options;
1807
		}
1808
1809
		// We're updating the options for a single module.
1810
		if ( empty( $selector ) ) {
1811
			$selector = self::get_module_requested();
1812
		}
1813
		$selected = array();
1814
		foreach ( $options as $option => $attributes ) {
1815
1816
			// Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
1817
			if ( $selector === $attributes['jp_group'] ) {
1818
				$selected[ $option ] = $attributes;
1819
			}
1820
		}
1821
		return $selected;
1822
	}
1823
1824
	/**
1825
	 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
1826
	 *
1827
	 * @since 4.3.0
1828
	 *
1829
	 * @param string|bool $value Value to check.
1830
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1831
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1832
	 *
1833
	 * @return bool
1834
	 */
1835
	public static function validate_boolean( $value, $request, $param ) {
1836
		if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) {
1837
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) );
1838
		}
1839
		return true;
1840
	}
1841
1842
	/**
1843
	 * Validates that the parameter is a positive integer.
1844
	 *
1845
	 * @since 4.3.0
1846
	 *
1847
	 * @param int $value Value to check.
1848
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1849
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1850
	 *
1851
	 * @return bool
1852
	 */
1853
	public static function validate_posint( $value = 0, $request, $param ) {
1854
		if ( ! is_numeric( $value ) || $value <= 0 ) {
1855
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a positive integer.', 'jetpack' ), $param ) );
1856
		}
1857
		return true;
1858
	}
1859
1860
	/**
1861
	 * Validates that the parameter belongs to a list of admitted values.
1862
	 *
1863
	 * @since 4.3.0
1864
	 *
1865
	 * @param string $value Value to check.
1866
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1867
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1868
	 *
1869
	 * @return bool
1870
	 */
1871
	public static function validate_list_item( $value = '', $request, $param ) {
1872
		$attributes = $request->get_attributes();
1873
		if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1874
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s not recognized', 'jetpack' ), $param ) );
1875
		}
1876
		$args = $attributes['args'][ $param ];
1877
		if ( ! empty( $args['enum'] ) ) {
1878
1879
			// If it's an associative array, use the keys to check that the value is among those admitted.
1880
			$enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) ? array_keys( $args['enum'] ) : $args['enum'];
1881 View Code Duplication
			if ( ! in_array( $value, $enum ) ) {
1882
				return new WP_Error( 'invalid_param_value', sprintf(
1883
					/* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
1884
					esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum )
1885
				) );
1886
			}
1887
		}
1888
		return true;
1889
	}
1890
1891
	/**
1892
	 * Validates that the parameter belongs to a list of admitted values.
1893
	 *
1894
	 * @since 4.3.0
1895
	 *
1896
	 * @param string $value Value to check.
1897
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1898
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1899
	 *
1900
	 * @return bool
1901
	 */
1902
	public static function validate_module_list( $value = '', $request, $param ) {
1903
		if ( ! is_array( $value ) ) {
1904
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be an array', 'jetpack' ), $param ) );
1905
		}
1906
1907
		$modules = Jetpack::get_available_modules();
1908
1909
		if ( count( array_intersect( $value, $modules ) ) != count( $value ) ) {
1910
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be a list of valid modules', 'jetpack' ), $param ) );
1911
		}
1912
1913
		return true;
1914
	}
1915
1916
	/**
1917
	 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
1918
	 *
1919
	 * @since 4.3.0
1920
	 *
1921
	 * @param string $value Value to check.
1922
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1923
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1924
	 *
1925
	 * @return bool
1926
	 */
1927
	public static function validate_alphanum( $value = '', $request, $param ) {
1928 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
1929
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) );
1930
		}
1931
		return true;
1932
	}
1933
1934
	/**
1935
	 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
1936
	 *
1937
	 * @since 4.6.0
1938
	 *
1939
	 * @param string $value Value to check.
1940
	 * @param WP_REST_Request $request
1941
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1942
	 *
1943
	 * @return bool
1944
	 */
1945
	public static function validate_verification_service( $value = '', $request, $param ) {
1946 View Code Duplication
		if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || preg_match( '#^<meta name="([a-z0-9_\-.:]+)?" content="([a-z0-9_-]+)?" />$#i', $value ) ) ) ) {
1947
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
1948
		}
1949
		return true;
1950
	}
1951
1952
	/**
1953
	 * Validates that the parameter is among the roles allowed for Stats.
1954
	 *
1955
	 * @since 4.3.0
1956
	 *
1957
	 * @param string|bool $value Value to check.
1958
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1959
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1960
	 *
1961
	 * @return bool
1962
	 */
1963
	public static function validate_stats_roles( $value, $request, $param ) {
1964
		if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) {
1965
			return new WP_Error( 'invalid_param', sprintf(
1966
				/* 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. */
1967
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', self::$stats_roles )
1968
			) );
1969
		}
1970
		return true;
1971
	}
1972
1973
	/**
1974
	 * Validates that the parameter is among the views where the Sharing can be displayed.
1975
	 *
1976
	 * @since 4.3.0
1977
	 *
1978
	 * @param string|bool $value Value to check.
1979
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1980
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1981
	 *
1982
	 * @return bool
1983
	 */
1984
	public static function validate_sharing_show( $value, $request, $param ) {
1985
		$views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
1986 View Code Duplication
		if ( ! is_array( $value ) ) {
1987
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) );
1988
		}
1989 View Code Duplication
		if ( ! array_intersect( $views, $value ) ) {
1990
			return new WP_Error( 'invalid_param', sprintf(
1991
				/* 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 */
1992
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views )
1993
			) );
1994
		}
1995
		return true;
1996
	}
1997
1998
	/**
1999
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2000
	 *
2001
	 * @since 4.3.0
2002
	 *
2003
	 * @param string|bool $value {
2004
	 *     Value to check received by request.
2005
	 *
2006
	 *     @type array $visible List of slug of services to share to that are displayed directly in the page.
2007
	 *     @type array $hidden  List of slug of services to share to that are concealed in a folding menu.
2008
	 * }
2009
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2010
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2011
	 *
2012
	 * @return bool
2013
	 */
2014
	public static function validate_services( $value, $request, $param ) {
2015 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
2016
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), $param ) );
2017
		}
2018
2019
		// Allow to clear everything.
2020
		if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
2021
			return true;
2022
		}
2023
2024 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2025
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2026
		}
2027
		$sharer = new Sharing_Service();
2028
		$services = array_keys( $sharer->get_all_services() );
2029
2030
		if (
2031
			( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
2032
			||
2033
			( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) )
2034
		{
2035
			return new WP_Error( 'invalid_param', sprintf(
2036
				/* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
2037
				esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services )
2038
			) );
2039
		}
2040
		return true;
2041
	}
2042
2043
	/**
2044
	 * Validates that the parameter has enough information to build a custom sharing button.
2045
	 *
2046
	 * @since 4.3.0
2047
	 *
2048
	 * @param string|bool $value Value to check.
2049
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2050
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2051
	 *
2052
	 * @return bool
2053
	 */
2054
	public static function validate_custom_service( $value, $request, $param ) {
2055 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
2056
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), $param ) );
2057
		}
2058
2059
		// Allow to clear everything.
2060
		if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
2061
			return true;
2062
		}
2063
2064 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2065
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2066
		}
2067
2068
		if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
2069
		|| ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
2070
		|| ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
2071
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), $param ) );
2072
		}
2073
		return true;
2074
	}
2075
2076
	/**
2077
	 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
2078
	 *
2079
	 * @since 4.3.0
2080
	 *
2081
	 * @param string $value Value to check.
2082
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2083
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2084
	 *
2085
	 * @return bool
2086
	 */
2087
	public static function validate_custom_service_id( $value = '', $request, $param ) {
2088 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
2089
			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 ) );
2090
		}
2091
2092 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2093
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2094
		}
2095
		$sharer = new Sharing_Service();
2096
		$services = array_keys( $sharer->get_all_services() );
2097
2098 View Code Duplication
		if ( ! empty( $value ) && ! in_array( $value, $services ) ) {
2099
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), $param ) );
2100
		}
2101
2102
		return true;
2103
	}
2104
2105
	/**
2106
	 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
2107
	 *
2108
	 * @since 4.3.0
2109
	 *
2110
	 * @param string $value Value to check.
2111
	 * @param WP_REST_Request $request
2112
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2113
	 *
2114
	 * @return bool
2115
	 */
2116
	public static function validate_twitter_username( $value = '', $request, $param ) {
2117 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
2118
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a Twitter username.', 'jetpack' ), $param ) );
2119
		}
2120
		return true;
2121
	}
2122
2123
	/**
2124
	 * Validates that the parameter is a string.
2125
	 *
2126
	 * @since 4.3.0
2127
	 *
2128
	 * @param string $value Value to check.
2129
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2130
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2131
	 *
2132
	 * @return bool
2133
	 */
2134
	public static function validate_string( $value = '', $request, $param ) {
2135
		if ( ! is_string( $value ) ) {
2136
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack' ), $param ) );
2137
		}
2138
		return true;
2139
	}
2140
2141
	/**
2142
	 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
2143
	 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
2144
	 *
2145
	 * @since 4.3.0
2146
	 *
2147
	 * @param string|bool $value Value to check.
2148
	 *
2149
	 * @return bool
2150
	 */
2151
	public static function sanitize_stats_allowed_roles( $value ) {
2152
		if ( empty( $value ) ) {
2153
			return array( 'administrator' );
2154
		}
2155
		return $value;
2156
	}
2157
2158
	/**
2159
	 * Get the currently accessed route and return the module slug in it.
2160
	 *
2161
	 * @since 4.3.0
2162
	 *
2163
	 * @param string $route Regular expression for the endpoint with the module slug to return.
2164
	 *
2165
	 * @return array
2166
	 */
2167
	public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
2168
2169
		if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
2170
			return '';
2171
		}
2172
2173
		preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
2174
2175
		if ( empty( $module['slug'] ) ) {
2176
			return '';
2177
		}
2178
2179
		return $module['slug'];
2180
	}
2181
2182
	/**
2183
	 * Adds extra information for modules.
2184
	 *
2185
	 * @since 4.3.0
2186
	 *
2187
	 * @param string      $modules Can be a single module or a list of modules.
2188
	 * @param null|string $slug    Slug of the module in the first parameter.
2189
	 *
2190
	 * @return array
2191
	 */
2192
	public static function prepare_modules_for_response( $modules = '', $slug = null ) {
2193
		global $wp_rewrite;
2194
2195
		/** This filter is documented in modules/sitemaps/sitemaps.php */
2196
		$location = apply_filters( 'jetpack_sitemap_location', '' );
2197
2198
		if ( $wp_rewrite->using_index_permalinks() ) {
2199
			$sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
2200
			$news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
2201
		} else if ( $wp_rewrite->using_permalinks() ) {
2202
			$sitemap_url = home_url( $location . '/sitemap.xml' );
2203
			$news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
2204
		} else {
2205
			$sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
2206
			$news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
2207
		}
2208
2209
		if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) {
2210
			// Is a list of modules
2211
			$modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
2212
			$modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
2213
		} elseif ( 'sitemaps' == $slug ) {
2214
			// It's a single module
2215
			$modules['extra']['sitemap_url'] = $sitemap_url;
2216
			$modules['extra']['news_sitemap_url'] = $news_sitemap_url;
2217
		}
2218
		return $modules;
2219
	}
2220
2221
	/**
2222
	 * Remove 'validate_callback' item from options available for module.
2223
	 * Fetch current option value and add to array of module options.
2224
	 * Prepare values of module options that need special handling, like those saved in wpcom.
2225
	 *
2226
	 * @since 4.3.0
2227
	 *
2228
	 * @param string $module Module slug.
2229
	 * @return array
2230
	 */
2231
	public static function prepare_options_for_response( $module = '' ) {
2232
		$options = self::get_updateable_data_list( $module );
2233
2234
		if ( ! is_array( $options ) || empty( $options ) ) {
2235
			return $options;
2236
		}
2237
2238
		foreach ( $options as $key => $value ) {
2239
2240
			if ( isset( $options[ $key ]['validate_callback'] ) ) {
2241
				unset( $options[ $key ]['validate_callback'] );
2242
			}
2243
2244
			$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2245
2246
			$current_value = get_option( $key, $default_value );
2247
2248
			$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2249
		}
2250
2251
		// Some modules need special treatment.
2252
		switch ( $module ) {
2253
2254
			case 'monitor':
2255
				// Status of user notifications
2256
				$options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
2257
				break;
2258
2259
			case 'post-by-email':
2260
				// Email address
2261
				$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'] );
2262
				break;
2263
2264
			case 'protect':
2265
				// Protect
2266
				$options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
2267
				if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) {
2268
					include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' );
2269
				}
2270
				$options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist();
2271
				break;
2272
2273
			case 'related-posts':
2274
				// It's local, but it must be broken apart since it's saved as an array.
2275
				$options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
2276
				break;
2277
2278
			case 'verification-tools':
2279
				// It's local, but it must be broken apart since it's saved as an array.
2280
				$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
2281
				break;
2282
2283
			case 'google-analytics':
2284
				$wga = get_option( 'jetpack_wga' );
2285
				$code = '';
2286
				if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
2287
					 $code = $wga[ 'code' ];
2288
				}
2289
				$options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
2290
				break;
2291
2292 View Code Duplication
			case 'sharedaddy':
2293
				// It's local, but it must be broken apart since it's saved as an array.
2294
				if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2295
					break;
2296
				}
2297
				$sharer = new Sharing_Service();
2298
				$options = self::split_options( $options, $sharer->get_global_options() );
2299
				$options['sharing_services']['current_value'] = $sharer->get_blog_services();
2300
				break;
2301
2302
			case 'after-the-deadline':
2303
				if ( ! function_exists( 'AtD_get_options' ) ) {
2304
					include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' );
2305
				}
2306
				$atd_options = array_merge( AtD_get_options( get_current_user_id(), 'AtD_options' ), AtD_get_options( get_current_user_id(), 'AtD_check_when' ) );
2307
				unset( $atd_options['name'] );
2308
				foreach ( $atd_options as $key => $value ) {
2309
					$options[ $key ]['current_value'] = self::cast_value( $value, $options[ $key ] );
2310
				}
2311
				$atd_options = AtD_get_options( get_current_user_id(), 'AtD_guess_lang' );
2312
				$options['guess_lang']['current_value'] = self::cast_value( isset( $atd_options['true'] ), $options[ 'guess_lang' ] );
2313
				$options['ignored_phrases']['current_value'] = AtD_get_setting( get_current_user_id(), 'AtD_ignored_phrases' );
2314
				unset( $options['unignore_phrase'] );
2315
				break;
2316
2317
			case 'stats':
2318
				// It's local, but it must be broken apart since it's saved as an array.
2319
				if ( ! function_exists( 'stats_get_options' ) ) {
2320
					include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
2321
				}
2322
				$options = self::split_options( $options, stats_get_options() );
2323
				break;
2324
		}
2325
2326
		return $options;
2327
	}
2328
2329
	/**
2330
	 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
2331
	 *
2332
	 * @since 4.3.0
2333
	 *
2334
	 * @param array  $separate_options Array of options admitted by the module.
2335
	 * @param array  $grouped_options Option saved as array to be splitted.
2336
	 * @param string $prefix Optional prefix for the separate option keys.
2337
	 *
2338
	 * @return array
2339
	 */
2340
	public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
2341
		if ( is_array( $grouped_options ) ) {
2342
			foreach ( $grouped_options as $key => $value ) {
2343
				$option_key = $prefix . $key;
2344
				if ( isset( $separate_options[ $option_key ] ) ) {
2345
					$separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
2346
				}
2347
			}
2348
		}
2349
		return $separate_options;
2350
	}
2351
2352
	/**
2353
	 * Perform a casting to the value specified in the option definition.
2354
	 *
2355
	 * @since 4.3.0
2356
	 *
2357
	 * @param mixed $value Value to cast to the proper type.
2358
	 * @param array $definition Type to cast the value to.
2359
	 *
2360
	 * @return bool|float|int|string
2361
	 */
2362
	public static function cast_value( $value, $definition ) {
2363
		if ( $value === 'NULL' ) {
2364
			return null;
2365
		}
2366
2367
		if ( isset( $definition['type'] ) ) {
2368
			switch ( $definition['type'] ) {
2369
				case 'boolean':
2370
					if ( 'true' === $value ) {
2371
						return true;
2372
					} elseif ( 'false' === $value ) {
2373
						return false;
2374
					}
2375
					return (bool) $value;
2376
					break;
2377
2378
				case 'integer':
2379
					return (int) $value;
2380
					break;
2381
2382
				case 'float':
2383
					return (float) $value;
2384
					break;
2385
2386
				case 'string':
2387
					return (string) $value;
2388
					break;
2389
			}
2390
		}
2391
		return $value;
2392
	}
2393
2394
	/**
2395
	 * Get a value not saved locally.
2396
	 *
2397
	 * @since 4.3.0
2398
	 *
2399
	 * @param string $module Module slug.
2400
	 * @param string $option Option name.
2401
	 *
2402
	 * @return bool Whether user is receiving notifications or not.
2403
	 */
2404
	public static function get_remote_value( $module, $option ) {
2405
2406
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
2407
			$option .= get_current_user_id();
2408
		}
2409
2410
		// If option doesn't exist, 'does_not_exist' will be returned.
2411
		$value = get_option( $option, 'does_not_exist' );
2412
2413
		// If option exists, just return it.
2414
		if ( 'does_not_exist' !== $value ) {
2415
			return $value;
2416
		}
2417
2418
		// Only check a remote option if Jetpack is connected.
2419
		if ( ! Jetpack::is_active() ) {
2420
			return false;
2421
		}
2422
2423
		// Do what is necessary for each module.
2424
		switch ( $module ) {
2425
			case 'monitor':
2426
				// Load the class to use the method. If class can't be found, do nothing.
2427
				if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2428
					return false;
2429
				}
2430
				$value = Jetpack_Monitor::user_receives_notifications( false );
2431
				break;
2432
2433
			case 'post-by-email':
2434
				// Load the class to use the method. If class can't be found, do nothing.
2435
				if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2436
					return false;
2437
				}
2438
				$post_by_email = new Jetpack_Post_By_Email();
2439
				$value = $post_by_email->get_post_by_email_address();
2440
				if ( $value === null ) {
2441
					$value = 'NULL'; // sentinel value so it actually gets set
2442
				}
2443
				break;
2444
		}
2445
2446
		// Normalize value to boolean.
2447
		if ( is_wp_error( $value ) || is_null( $value ) ) {
2448
			$value = false;
2449
		}
2450
2451
		// Save option to use it next time.
2452
		update_option( $option, $value );
2453
2454
		return $value;
2455
	}
2456
2457
	/**
2458
	 * Get number of plugin updates available.
2459
	 *
2460
	 * @since 4.3.0
2461
	 *
2462
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
2463
	 */
2464
	public static function get_plugin_update_count() {
2465
		$updates = wp_get_update_data();
2466
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
2467
			$count = $updates['counts']['plugins'];
2468
			if ( 0 == $count ) {
2469
				$response = array(
2470
					'code'    => 'success',
2471
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
2472
					'count'   => 0,
2473
				);
2474
			} else {
2475
				$response = array(
2476
					'code'    => 'updates-available',
2477
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
2478
					'count'   => $count,
2479
				);
2480
			}
2481
			return rest_ensure_response( $response );
2482
		}
2483
2484
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
2485
	}
2486
2487
2488
	/**
2489
	 * Returns a list of all plugins in the site.
2490
	 *
2491
	 * @since 4.2.0
2492
	 * @uses get_plugins()
2493
	 *
2494
	 * @return array
2495
	 */
2496
	private static function core_get_plugins() {
2497
		if ( ! function_exists( 'get_plugins' ) ) {
2498
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2499
		}
2500
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2501
		$plugins = apply_filters( 'all_plugins', get_plugins() );
2502
2503
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
2504
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
2505
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
2506
			}
2507
			return $plugins;
2508
		}
2509
2510
		return array();
2511
	}
2512
2513
	/**
2514
	 * Checks if the queried plugin is active.
2515
	 *
2516
	 * @since 4.2.0
2517
	 * @uses is_plugin_active()
2518
	 *
2519
	 * @return bool
2520
	 */
2521
	private static function core_is_plugin_active( $plugin ) {
2522
		if ( ! function_exists( 'is_plugin_active' ) ) {
2523
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2524
		}
2525
2526
		return is_plugin_active( $plugin );
2527
	}
2528
2529
	/**
2530
	 * Get plugins data in site.
2531
	 *
2532
	 * @since 4.2.0
2533
	 *
2534
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
2535
	 */
2536
	public static function get_plugins() {
2537
		$plugins = self::core_get_plugins();
2538
2539
		if ( ! empty( $plugins ) ) {
2540
			return rest_ensure_response( $plugins );
2541
		}
2542
2543
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
2544
	}
2545
2546
	/**
2547
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
2548
	 *
2549
	 * @since 4.2.0
2550
	 *
2551
	 * @param WP_REST_Request $request {
2552
	 *     Array of parameters received by request.
2553
	 *
2554
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2555
	 * }
2556
	 *
2557
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
2558
	 */
2559
	public static function get_plugin( $request ) {
2560
2561
		$plugins = self::core_get_plugins();
2562
2563
		if ( empty( $plugins ) ) {
2564
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
2565
		}
2566
2567
		$plugin = stripslashes( $request['plugin'] );
2568
2569
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
2570
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
2571
		}
2572
2573
		$plugin_data = $plugins[ $plugin ];
2574
2575
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
2576
2577
		return rest_ensure_response( array(
2578
			'code'    => 'success',
2579
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
2580
			'data'    => $plugin_data
2581
		) );
2582
	}
2583
2584
} // class end
2585