Completed
Push — master-stable ( a82972...9baba8 )
by
unknown
27:19 queued 17:47
created

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

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * 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
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
52
53
		self::$user_permissions_error_msg = esc_html__(
54
			'You do not have the correct user permissions to perform this action.
55
			Please contact your site admin if you think this is a mistake.',
56
			'jetpack'
57
		);
58
59
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
60
61
		Jetpack::load_xml_rpc_client();
62
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
63
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
64
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
65
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
66
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
67
		$site_endpoint = new Jetpack_Core_API_Site_Endpoint();
68
		$widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
69
70
		register_rest_route( 'jetpack/v4', '/jitm', array(
71
			'methods'  => WP_REST_Server::READABLE,
72
			'callback' => __CLASS__ . '::get_jitm_message',
73
		) );
74
75
		register_rest_route( 'jetpack/v4', '/jitm', array(
76
			'methods'  => WP_REST_Server::CREATABLE,
77
			'callback' => __CLASS__ . '::delete_jitm_message'
78
		) );
79
80
		// Register a site
81
		register_rest_route( 'jetpack/v4', '/verify_registration', array(
82
			'methods' => WP_REST_Server::EDITABLE,
83
			'callback' => __CLASS__ . '::verify_registration',
84
		) );
85
86
		// Authorize a remote user
87
		register_rest_route( 'jetpack/v4', '/remote_authorize', array(
88
			'methods' => WP_REST_Server::EDITABLE,
89
			'callback' => __CLASS__ . '::remote_authorize',
90
		) );
91
92
		// Get current connection status of Jetpack
93
		register_rest_route( 'jetpack/v4', '/connection', array(
94
			'methods' => WP_REST_Server::READABLE,
95
			'callback' => __CLASS__ . '::jetpack_connection_status',
96
		) );
97
98
		// Fetches a fresh connect URL
99
		register_rest_route( 'jetpack/v4', '/connection/url', array(
100
			'methods' => WP_REST_Server::READABLE,
101
			'callback' => __CLASS__ . '::build_connect_url',
102
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
103
		) );
104
105
		// Get current user connection data
106
		register_rest_route( 'jetpack/v4', '/connection/data', array(
107
			'methods' => WP_REST_Server::READABLE,
108
			'callback' => __CLASS__ . '::get_user_connection_data',
109
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
110
		) );
111
112
		// Disconnect site from WordPress.com servers
113
		register_rest_route( 'jetpack/v4', '/connection', array(
114
			'methods' => WP_REST_Server::EDITABLE,
115
			'callback' => __CLASS__ . '::disconnect_site',
116
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
117
		) );
118
119
		// Disconnect/unlink user from WordPress.com servers
120
		register_rest_route( 'jetpack/v4', '/connection/user', array(
121
			'methods' => WP_REST_Server::EDITABLE,
122
			'callback' => __CLASS__ . '::unlink_user',
123
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
124
		) );
125
126
		// Get current site data
127
		register_rest_route( 'jetpack/v4', '/site', array(
128
			'methods' => WP_REST_Server::READABLE,
129
			'callback' => __CLASS__ . '::get_site_data',
130
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
131
		) );
132
133
		// Get current site data
134
		register_rest_route( 'jetpack/v4', '/site/features', array(
135
			'methods' => WP_REST_Server::READABLE,
136
			'callback' => array( $site_endpoint, 'get_features' ),
137
			'permission_callback' => array( $site_endpoint , 'can_request' ),
138
		) );
139
140
		// Confirm that a site in identity crisis should be in staging mode
141
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
142
			'methods' => WP_REST_Server::EDITABLE,
143
			'callback' => __CLASS__ . '::confirm_safe_mode',
144
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
145
		) );
146
147
		// IDC resolve: create an entirely new shadow site for this URL.
148
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
149
			'methods' => WP_REST_Server::EDITABLE,
150
			'callback' => __CLASS__ . '::start_fresh_connection',
151
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
152
		) );
153
154
		// Handles the request to migrate stats and subscribers during an identity crisis.
155
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
156
			'methods' => WP_REST_Server::EDITABLE,
157
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
158
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
159
		) );
160
161
		// Return all modules
162
		register_rest_route( 'jetpack/v4', '/module/all', array(
163
			'methods' => WP_REST_Server::READABLE,
164
			'callback' => array( $module_list_endpoint, 'process' ),
165
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
166
		) );
167
168
		// Activate many modules
169
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
170
			'methods' => WP_REST_Server::EDITABLE,
171
			'callback' => array( $module_list_endpoint, 'process' ),
172
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
173
			'args' => array(
174
				'modules' => array(
175
					'default'           => '',
176
					'type'              => 'array',
177
					'items'             => array(
178
						'type'          => 'string',
179
					),
180
					'required'          => true,
181
					'validate_callback' => __CLASS__ . '::validate_module_list',
182
				),
183
				'active' => array(
184
					'default'           => true,
185
					'type'              => 'boolean',
186
					'required'          => false,
187
					'validate_callback' => __CLASS__ . '::validate_boolean',
188
				),
189
			)
190
		) );
191
192
		// Return a single module and update it when needed
193
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
194
			'methods' => WP_REST_Server::READABLE,
195
			'callback' => array( $core_api_endpoint, 'process' ),
196
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
197
		) );
198
199
		// Activate and deactivate a module
200
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
201
			'methods' => WP_REST_Server::EDITABLE,
202
			'callback' => array( $module_toggle_endpoint, 'process' ),
203
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
204
			'args' => array(
205
				'active' => array(
206
					'default'           => true,
207
					'type'              => 'boolean',
208
					'required'          => true,
209
					'validate_callback' => __CLASS__ . '::validate_boolean',
210
				),
211
			)
212
		) );
213
214
		// Update a module
215
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
216
			'methods' => WP_REST_Server::EDITABLE,
217
			'callback' => array( $core_api_endpoint, 'process' ),
218
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
219
			'args' => self::get_updateable_parameters( 'any' )
220
		) );
221
222
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
223
		// Akismet spam count, etc.
224
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
225
			'methods' => WP_REST_Server::READABLE,
226
			'callback' => array( $module_data_endpoint, 'process' ),
227
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
228
			'args' => array(
229
				'range' => array(
230
					'default'           => 'day',
231
					'type'              => 'string',
232
					'required'          => false,
233
					'validate_callback' => __CLASS__ . '::validate_string',
234
				),
235
			)
236
		) );
237
238
		// Check if the API key for a specific service is valid or not
239
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
240
			'methods' => WP_REST_Server::READABLE,
241
			'callback' => array( $module_data_endpoint, 'key_check' ),
242
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
243
			'sanitize_callback' => 'sanitize_text_field',
244
		) );
245
246
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
247
			'methods' => WP_REST_Server::EDITABLE,
248
			'callback' => array( $module_data_endpoint, 'key_check' ),
249
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
250
			'sanitize_callback' => 'sanitize_text_field',
251
			'args' => array(
252
				'api_key' => array(
253
					'default'           => '',
254
					'type'              => 'string',
255
					'validate_callback' => __CLASS__ . '::validate_alphanum',
256
				),
257
			)
258
		) );
259
260
		// Update any Jetpack module option or setting
261
		register_rest_route( 'jetpack/v4', '/settings', array(
262
			'methods' => WP_REST_Server::EDITABLE,
263
			'callback' => array( $core_api_endpoint, 'process' ),
264
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
265
			'args' => self::get_updateable_parameters( 'any' )
266
		) );
267
268
		// Update a module
269
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
270
			'methods' => WP_REST_Server::EDITABLE,
271
			'callback' => array( $core_api_endpoint, 'process' ),
272
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
273
			'args' => self::get_updateable_parameters()
274
		) );
275
276
		// Return all module settings
277
		register_rest_route( 'jetpack/v4', '/settings/', array(
278
			'methods' => WP_REST_Server::READABLE,
279
			'callback' => array( $core_api_endpoint, 'process' ),
280
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
281
		) );
282
283
		// Reset all Jetpack options
284
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
285
			'methods' => WP_REST_Server::EDITABLE,
286
			'callback' => __CLASS__ . '::reset_jetpack_options',
287
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
288
		) );
289
290
		// Return current Jumpstart status
291
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
292
			'methods'             => WP_REST_Server::READABLE,
293
			'callback'            => __CLASS__ . '::jumpstart_status',
294
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
295
		) );
296
297
		// Update Jumpstart
298
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
299
			'methods'             => WP_REST_Server::EDITABLE,
300
			'callback'            => __CLASS__ . '::jumpstart_toggle',
301
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
302
			'args'                => array(
303
				'active' => array(
304
					'required'          => true,
305
					'validate_callback' => __CLASS__  . '::validate_boolean',
306
				),
307
			),
308
		) );
309
310
		// Updates: get number of plugin updates available
311
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
312
			'methods' => WP_REST_Server::READABLE,
313
			'callback' => __CLASS__ . '::get_plugin_update_count',
314
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
315
		) );
316
317
		// Dismiss Jetpack Notices
318
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
319
			'methods' => WP_REST_Server::EDITABLE,
320
			'callback' => __CLASS__ . '::dismiss_notice',
321
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
322
		) );
323
324
		// Plugins: get list of all plugins.
325
		register_rest_route( 'jetpack/v4', '/plugins', array(
326
			'methods' => WP_REST_Server::READABLE,
327
			'callback' => __CLASS__ . '::get_plugins',
328
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
329
		) );
330
331
		// Plugins: check if the plugin is active.
332
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
333
			'methods' => WP_REST_Server::READABLE,
334
			'callback' => __CLASS__ . '::get_plugin',
335
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
336
		) );
337
338
		// Widgets: get information about a widget that supports it.
339
		register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array(
340
			'methods' => WP_REST_Server::READABLE,
341
			'callback' => array( $widget_endpoint, 'process' ),
342
			'permission_callback' => array( $widget_endpoint, 'can_request' ),
343
		) );
344
	}
345
346
	/**
347
	 * Asks for a jitm, unless they've been disabled, in which case it returns an empty array
348
	 *
349
	 * @param $request WP_REST_Request
350
	 *
351
	 * @return array An array of jitms
352
	 */
353
	public static function get_jitm_message( $request ) {
354
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
355
356
		$jitm = Jetpack_JITM::init();
357
358
		if ( ! $jitm ) {
359
			return array();
360
		}
361
362
		return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) );
363
	}
364
365
	/**
366
	 * Dismisses a jitm
367
	 * @param $request WP_REST_Request The request
368
	 *
369
	 * @return bool Always True
370
	 */
371
	public static function delete_jitm_message( $request ) {
372
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' );
373
374
		$jitm = Jetpack_JITM::init();
375
376
		if ( ! $jitm ) {
377
			return true;
378
		}
379
380
		return $jitm->dismiss( $request['id'], $request['feature_class'] );
381
	}
382
383
	/**
384
	 * Handles verification that a site is registered
385
	 *
386
	 * @since 5.4.0
387
	 *
388
	 * @param WP_REST_Request $request The request sent to the WP REST API.
389
	 *
390
	 * @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...
391
	 */
392
	public static function verify_registration( $request ) {
393
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
394
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
395
		$result = $xmlrpc_server->verify_registration( array( $request['secret_1'], $request['state'] ) );
396
397
		if ( is_a( $result, 'IXR_Error' ) ) {
398
			$result = new WP_Error( $result->code, $result->message );
399
		}
400
401
		return $result;
402
	}
403
404
	/**
405
	 * Handles verification that a site is registered
406
	 *
407
	 * @since 5.4.0
408
	 *
409
	 * @param WP_REST_Request $request The request sent to the WP REST API.
410
	 *
411
	 * @return array|wp-error
412
	 */
413
	 public static function remote_authorize( $request ) {
414
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
415
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
416
		$result = $xmlrpc_server->remote_authorize( $request );
417
418
		if ( is_a( $result, 'IXR_Error' ) ) {
419
			$result = new WP_Error( $result->code, $result->message );
420
		}
421
422
		return $result;
423
	 }
424
425
	/**
426
	 * Handles dismissing of Jetpack Notices
427
	 *
428
	 * @since 4.3.0
429
	 *
430
	 * @param WP_REST_Request $request The request sent to the WP REST API.
431
	 *
432
	 * @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...
433
	 */
434
	public static function dismiss_notice( $request ) {
435
		$notice = $request['notice'];
436
437
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
438
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
439
		}
440
441
		if ( isset( $notice ) && ! empty( $notice ) ) {
442
			switch( $notice ) {
443
				case 'feedback_dash_request':
444
				case 'welcome':
445
					$notices = get_option( 'jetpack_dismissed_notices', array() );
446
					$notices[ $notice ] = true;
447
					update_option( 'jetpack_dismissed_notices', $notices );
448
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
449
450
				default:
451
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
452
			}
453
		}
454
455
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
456
	}
457
458
	/**
459
	 * Verify that the user can disconnect the site.
460
	 *
461
	 * @since 4.3.0
462
	 *
463
	 * @return bool|WP_Error True if user is able to disconnect the site.
464
	 */
465 View Code Duplication
	public static function disconnect_site_permission_callback() {
466
		if ( current_user_can( 'jetpack_disconnect' ) ) {
467
			return true;
468
		}
469
470
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
471
472
	}
473
474
	/**
475
	 * Verify that the user can get a connect/link URL
476
	 *
477
	 * @since 4.3.0
478
	 *
479
	 * @return bool|WP_Error True if user is able to disconnect the site.
480
	 */
481 View Code Duplication
	public static function connect_url_permission_callback() {
482
		if ( current_user_can( 'jetpack_connect_user' ) ) {
483
			return true;
484
		}
485
486
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
487
488
	}
489
490
	/**
491
	 * Verify that a user can get the data about the current user.
492
	 * Only those who can connect.
493
	 *
494
	 * @since 4.3.0
495
	 *
496
	 * @uses Jetpack::is_user_connected();
497
	 *
498
	 * @return bool|WP_Error True if user is able to unlink.
499
	 */
500 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
501
		if ( current_user_can( 'jetpack_connect_user' ) ) {
502
			return true;
503
		}
504
505
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
506
	}
507
508
	/**
509
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
510
	 *
511
	 * @since 4.3.0
512
	 *
513
	 * @uses Jetpack::is_user_connected();
514
	 *
515
	 * @return bool|WP_Error True if user is able to unlink.
516
	 */
517
	public static function unlink_user_permission_callback() {
518
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
519
			return true;
520
		}
521
522
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
523
	}
524
525
	/**
526
	 * Verify that user can manage Jetpack modules.
527
	 *
528
	 * @since 4.3.0
529
	 *
530
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
531
	 */
532
	public static function manage_modules_permission_check() {
533
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
534
			return true;
535
		}
536
537
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
538
	}
539
540
	/**
541
	 * Verify that user can update Jetpack modules.
542
	 *
543
	 * @since 4.3.0
544
	 *
545
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
546
	 */
547 View Code Duplication
	public static function configure_modules_permission_check() {
548
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
549
			return true;
550
		}
551
552
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
553
	}
554
555
	/**
556
	 * Verify that user can view Jetpack admin page.
557
	 *
558
	 * @since 4.3.0
559
	 *
560
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
561
	 */
562 View Code Duplication
	public static function view_admin_page_permission_check() {
563
		if ( current_user_can( 'jetpack_admin_page' ) ) {
564
			return true;
565
		}
566
567
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
568
	}
569
570
	/**
571
	 * Verify that user can mitigate an identity crisis.
572
	 *
573
	 * @since 4.4.0
574
	 *
575
	 * @return bool Whether user has capability 'jetpack_disconnect'.
576
	 */
577 View Code Duplication
	public static function identity_crisis_mitigation_permission_check() {
578
		if ( current_user_can( 'jetpack_disconnect' ) ) {
579
			return true;
580
		}
581
582
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
583
	}
584
585
	/**
586
	 * Verify that user can update Jetpack general settings.
587
	 *
588
	 * @since 4.3.0
589
	 *
590
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
591
	 */
592 View Code Duplication
	public static function update_settings_permission_check() {
593
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
594
			return true;
595
		}
596
597
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
598
	}
599
600
	/**
601
	 * Verify that user can view Jetpack admin page and can activate plugins.
602
	 *
603
	 * @since 4.3.0
604
	 *
605
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
606
	 */
607 View Code Duplication
	public static function activate_plugins_permission_check() {
608
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
609
			return true;
610
		}
611
612
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
613
	}
614
615
	/**
616
	 * Contextual HTTP error code for authorization failure.
617
	 *
618
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
619
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
620
	 *
621
	 * @since 4.3.0
622
	 *
623
	 * @return int
624
	 */
625
	public static function rest_authorization_required_code() {
626
		return is_user_logged_in() ? 403 : 401;
627
	}
628
629
	/**
630
	 * Get connection status for this Jetpack site.
631
	 *
632
	 * @since 4.3.0
633
	 *
634
	 * @return bool True if site is connected
635
	 */
636
	public static function jetpack_connection_status() {
637
		return rest_ensure_response( array(
638
				'isActive'  => Jetpack::is_active(),
639
				'isStaging' => Jetpack::is_staging_site(),
640
				'devMode'   => array(
641
					'isActive' => Jetpack::is_development_mode(),
642
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
643
					'url'      => site_url() && false === strpos( site_url(), '.' ),
644
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
645
				),
646
			)
647
		);
648
	}
649
650
	/**
651
	 * Disconnects Jetpack from the WordPress.com Servers
652
	 *
653
	 * @uses Jetpack::disconnect();
654
	 * @since 4.3.0
655
	 *
656
	 * @param WP_REST_Request $request The request sent to the WP REST API.
657
	 *
658
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
659
	 */
660 View Code Duplication
	public static function disconnect_site( $request ) {
661
662
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
663
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
664
		}
665
666
		if ( Jetpack::is_active() ) {
667
			Jetpack::disconnect();
668
			return rest_ensure_response( array( 'code' => 'success' ) );
669
		}
670
671
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
672
	}
673
674
	/**
675
	 * Gets a new connect raw URL with fresh nonce.
676
	 *
677
	 * @uses Jetpack::disconnect();
678
	 * @since 4.3.0
679
	 *
680
	 * @param WP_REST_Request $request The request sent to the WP REST API.
681
	 *
682
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
683
	 */
684
	public static function build_connect_url() {
685
		$url = Jetpack::init()->build_connect_url( true, false, false );
686
		if ( $url ) {
687
			return rest_ensure_response( $url );
688
		}
689
690
		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 ) );
691
	}
692
693
	/**
694
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
695
	 * Information about the master/primary user.
696
	 * Information about the current user.
697
	 *
698
	 * @since 4.3.0
699
	 *
700
	 * @param WP_REST_Request $request The request sent to the WP REST API.
701
	 *
702
	 * @return object
703
	 */
704
	public static function get_user_connection_data() {
705
		require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' );
706
707
		$response = array(
708
//			'othersLinked' => Jetpack::get_other_linked_admins(),
709
			'currentUser'  => jetpack_current_user_data(),
710
		);
711
		return rest_ensure_response( $response );
712
	}
713
714
	/**
715
	 * Returns the proper name for Jetpack Holiday Snow setting.
716
	 * When the REST route starts, the holiday-snow.php file where jetpack_holiday_snow_option_name() function is defined is not loaded,
717
	 * so where using this to replicate it and have the same functionality.
718
	 *
719
	 * @since 4.4.0
720
	 *
721
	 * @return string
722
	 */
723
	public static function holiday_snow_option_name() {
724
		/** This filter is documented in modules/holiday-snow.php */
725
		return apply_filters( 'jetpack_holiday_snow_option_name', 'jetpack_holiday_snow_enabled' );
726
	}
727
728
	/**
729
	 * Update a single miscellaneous setting for this Jetpack installation, like Holiday Snow.
730
	 *
731
	 * @since 4.3.0
732
	 *
733
	 * @param WP_REST_Request $request The request sent to the WP REST API.
734
	 *
735
	 * @return object Jetpack miscellaneous settings.
736
	 */
737
	public static function update_setting( $request ) {
738
		// Get parameters to update the module.
739
		$param = $request->get_params();
740
741
		// Exit if no parameters were passed.
742 View Code Duplication
		if ( ! is_array( $param ) ) {
743
			return new WP_Error( 'missing_setting', esc_html__( 'Missing setting.', 'jetpack' ), array( 'status' => 404 ) );
744
		}
745
746
		// Get option name and value.
747
		$option = key( $param );
748
		$value  = current( $param );
749
750
		// Log success or not
751
		$updated = false;
752
753
		switch ( $option ) {
754
			case self::holiday_snow_option_name():
755
				$updated = update_option( $option, ( true == (bool) $value ) ? 'letitsnow' : '' );
756
				break;
757
		}
758
759
		if ( $updated ) {
760
			return rest_ensure_response( array(
761
				'code' 	  => 'success',
762
				'message' => esc_html__( 'Setting updated.', 'jetpack' ),
763
				'value'   => $value,
764
			) );
765
		}
766
767
		return new WP_Error( 'setting_not_updated', esc_html__( 'The setting was not updated.', 'jetpack' ), array( 'status' => 400 ) );
768
	}
769
770
	/**
771
	 * Unlinks current user from the WordPress.com Servers.
772
	 *
773
	 * @since 4.3.0
774
	 * @uses  Jetpack::unlink_user
775
	 *
776
	 * @param WP_REST_Request $request The request sent to the WP REST API.
777
	 *
778
	 * @return bool|WP_Error True if user successfully unlinked.
779
	 */
780 View Code Duplication
	public static function unlink_user( $request ) {
781
782
		if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) {
783
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
784
		}
785
786
		if ( Jetpack::unlink_user() ) {
787
			return rest_ensure_response(
788
				array(
789
					'code' => 'success'
790
				)
791
			);
792
		}
793
794
		return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
795
	}
796
797
	/**
798
	 * Fetch site data from .com including the site's current plan.
799
	 *
800
	 * @since 5.5.0
801
	 *
802
	 * @return array Array of site properties.
803
	 */
804
	public static function site_data() {
805
		$site_id = Jetpack_Options::get_option( 'id' );
806
807
		if ( ! $site_id ) {
808
			 new WP_Error( 'site_id_missing' );
809
		}
810
811
		$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1' );
812
813
		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
814
			return new WP_Error( 'site_data_fetch_failed' );
815
		}
816
817
		// Save plan details in the database for future use without API calls
818
		$results = json_decode( $response['body'], true );
819
820
		if ( is_array( $results ) && isset( $results['plan'] ) ) {
821
822
			// Set flag for newly purchased plan
823
			$current_plan = Jetpack::get_active_plan();
824
			if ( $current_plan['product_slug'] !== $results['plan']['product_slug'] && 'jetpack_free' !== $results['plan']['product_slug'] ) {
825
				update_option( 'show_welcome_for_new_plan', true ) ;
826
			}
827
828
			update_option( 'jetpack_active_plan', $results['plan'] );
829
		}
830
		$body = wp_remote_retrieve_body( $response );
831
832
		return json_decode( $body );
833
	}
834
	/**
835
	 * Get site data, including for example, the site's current plan.
836
	 *
837
	 * @since 4.3.0
838
	 *
839
	 * @return array Array of site properties.
840
	 */
841
	public static function get_site_data() {
842
		$site_data = self::site_data();
843
844
		if ( ! is_wp_error( $site_data ) ) {
845
			return rest_ensure_response( array(
846
					'code' => 'success',
847
					'message' => esc_html__( 'Site data correctly received.', 'jetpack' ),
848
					'data' => json_encode( $site_data ),
849
				)
850
			);
851
		}
852 View Code Duplication
		if ( $site_data->get_error_code() === 'site_data_fetch_failed' ) {
0 ignored issues
show
The method get_error_code cannot be called on $site_data (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
853
			return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
854
		}
855
856 View Code Duplication
		if ( $site_data->get_error_code() === 'site_id_missing' ) {
0 ignored issues
show
The method get_error_code cannot be called on $site_data (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
857
			return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
858
		}
859
	}
860
861
	/**
862
	 * Handles identity crisis mitigation, confirming safe mode for this site.
863
	 *
864
	 * @since 4.4.0
865
	 *
866
	 * @return bool | WP_Error True if option is properly set.
867
	 */
868
	public static function confirm_safe_mode() {
869
		$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
870
		if ( $updated ) {
871
			return rest_ensure_response(
872
				array(
873
					'code' => 'success'
874
				)
875
			);
876
		}
877
		return new WP_Error(
878
			'error_setting_jetpack_safe_mode',
879
			esc_html__( 'Could not confirm safe mode.', 'jetpack' ),
880
			array( 'status' => 500 )
881
		);
882
	}
883
884
	/**
885
	 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
886
	 *
887
	 * @since 4.4.0
888
	 *
889
	 * @return bool | WP_Error True if option is properly set.
890
	 */
891
	public static function migrate_stats_and_subscribers() {
892
		if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
893
			return new WP_Error(
894
				'error_deleting_sync_error_idc',
895
				esc_html__( 'Could not delete sync error option.', 'jetpack' ),
896
				array( 'status' => 500 )
897
			);
898
		}
899
900
		if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
901
			return rest_ensure_response(
902
				array(
903
					'code' => 'success'
904
				)
905
			);
906
		}
907
		return new WP_Error(
908
			'error_setting_jetpack_migrate',
909
			esc_html__( 'Could not confirm migration.', 'jetpack' ),
910
			array( 'status' => 500 )
911
		);
912
	}
913
914
	/**
915
	 * This IDC resolution will disconnect the site and re-connect to a completely new
916
	 * and separate shadow site than the original.
917
	 *
918
	 * It will first will disconnect the site without phoning home as to not disturb the production site.
919
	 * It then builds a fresh connection URL and sends it back along with the response.
920
	 *
921
	 * @since 4.4.0
922
	 * @return bool|WP_Error
923
	 */
924
	public static function start_fresh_connection() {
925
		// First clear the options / disconnect.
926
		Jetpack::disconnect();
927
		return self::build_connect_url();
928
	}
929
930
	/**
931
	 * Reset Jetpack options
932
	 *
933
	 * @since 4.3.0
934
	 *
935
	 * @param WP_REST_Request $request {
936
	 *     Array of parameters received by request.
937
	 *
938
	 *     @type string $options Available options to reset are options|modules
939
	 * }
940
	 *
941
	 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
942
	 */
943
	public static function reset_jetpack_options( $request ) {
944
945
		if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) {
946
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
947
		}
948
949
		if ( isset( $request['options'] ) ) {
950
			$data = $request['options'];
951
952
			switch( $data ) {
953
				case ( 'options' ) :
954
					$options_to_reset = Jetpack::get_jetpack_options_for_reset();
955
956
					// Reset the Jetpack options
957
					foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
958
						Jetpack_Options::delete_option( $option_to_reset );
959
					}
960
961
					foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
962
						delete_option( $option_to_reset );
963
					}
964
965
					// Reset to default modules
966
					$default_modules = Jetpack::get_default_modules();
967
					Jetpack::update_active_modules( $default_modules );
968
969
					// Jumpstart option is special
970
					Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
971
					return rest_ensure_response( array(
972
						'code' 	  => 'success',
973
						'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ),
974
					) );
975
					break;
976
977
				case 'modules':
978
					$default_modules = Jetpack::get_default_modules();
979
					Jetpack::update_active_modules( $default_modules );
980
					return rest_ensure_response( array(
981
						'code' 	  => 'success',
982
						'message' => esc_html__( 'Modules reset to default.', 'jetpack' ),
983
					) );
984
					break;
985
986
				default:
987
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
988
			}
989
		}
990
991
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
992
	}
993
994
	/**
995
	 * Retrieves the current status of Jumpstart.
996
	 *
997
	 * @since 4.5.0
998
	 *
999
	 * @return bool
1000
	 */
1001
	public static function jumpstart_status() {
1002
		return array(
1003
			'status' => Jetpack_Options::get_option( 'jumpstart' )
1004
		);
1005
	}
1006
1007
	/**
1008
	 * Toggles activation or deactivation of the JumpStart
1009
	 *
1010
	 * @since 4.3.0
1011
	 *
1012
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1013
	 *
1014
	 * @return bool|WP_Error True if toggling Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
1015
	 */
1016
	public static function jumpstart_toggle( $request ) {
1017
1018
		if ( $request[ 'active' ] ) {
1019
			return self::jumpstart_activate( $request );
1020
		} else {
1021
			return self::jumpstart_deactivate( $request );
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Activates a series of valid Jetpack modules and initializes some options.
1027
	 *
1028
	 * @since 4.3.0
1029
	 *
1030
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1031
	 *
1032
	 * @return bool|WP_Error True if Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
1033
	 */
1034
	public static function jumpstart_activate( $request ) {
1035
		$modules = Jetpack::get_available_modules();
1036
		$activate_modules = array();
1037
		foreach ( $modules as $module ) {
1038
			$module_info = Jetpack::get_module( $module );
1039
			if ( isset( $module_info['feature'] ) && is_array( $module_info['feature'] ) && in_array( 'Jumpstart', $module_info['feature'] ) ) {
1040
				$activate_modules[] = $module;
1041
			}
1042
		}
1043
1044
		// Collect success/error messages like modules that are properly activated.
1045
		$result = array(
1046
			'activated_modules' => array(),
1047
			'failed_modules'    => array(),
1048
		);
1049
1050
		// Update the jumpstart option
1051
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
1052
			$result['jumpstart_activated'] = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' );
1053
		}
1054
1055
		// Check for possible conflicting plugins
1056
		$module_slugs_filtered = Jetpack::init()->filter_default_modules( $activate_modules );
1057
1058
		foreach ( $module_slugs_filtered as $module_slug ) {
1059
			Jetpack::log( 'activate', $module_slug );
1060
			if ( Jetpack::activate_module( $module_slug, false, false ) ) {
1061
				$result['activated_modules'][] = $module_slug;
1062
			} else {
1063
				$result['failed_modules'][] = $module_slug;
1064
			}
1065
		}
1066
1067
		// Set the default sharing buttons and set to display on posts if none have been set.
1068
		$sharing_services = get_option( 'sharing-services' );
1069
		$sharing_options  = get_option( 'sharing-options' );
1070
		if ( empty( $sharing_services['visible'] ) ) {
1071
			// Default buttons to set
1072
			$visible = array(
1073
				'twitter',
1074
				'facebook',
1075
				'google-plus-1',
1076
			);
1077
			$hidden = array();
1078
1079
			// Set some sharing settings
1080
			if ( class_exists( 'Sharing_Service' ) ) {
1081
				$sharing = new Sharing_Service();
1082
				$sharing_options['global'] = array(
1083
					'button_style'  => 'icon',
1084
					'sharing_label' => $sharing->default_sharing_label,
1085
					'open_links'    => 'same',
1086
					'show'          => array( 'post' ),
1087
					'custom'        => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array()
1088
				);
1089
1090
				$result['sharing_options']  = update_option( 'sharing-options', $sharing_options );
1091
				$result['sharing_services'] = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) );
1092
			}
1093
		}
1094
1095
		// If all Jumpstart modules were activated
1096
		if ( empty( $result['failed_modules'] ) ) {
1097
			return rest_ensure_response( array(
1098
				'code' 	  => 'success',
1099
				'message' => esc_html__( 'Jumpstart done.', 'jetpack' ),
1100
				'data'    => $result,
1101
			) );
1102
		}
1103
1104
		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 ) );
1105
	}
1106
1107
	/**
1108
	 * Dismisses Jumpstart so user is not prompted to go through it again.
1109
	 *
1110
	 * @since 4.3.0
1111
	 *
1112
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1113
	 *
1114
	 * @return bool|WP_Error True if Jumpstart was disabled or was nothing to dismiss. Otherwise, a WP_Error instance with a message.
1115
	 */
1116
	public static function jumpstart_deactivate( $request ) {
1117
1118
		// If dismissed, flag the jumpstart option as such.
1119
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
1120
			if ( Jetpack_Options::update_option( 'jumpstart', 'jumpstart_dismissed' ) ) {
1121
				return rest_ensure_response( array(
1122
					'code' 	  => 'success',
1123
					'message' => esc_html__( 'Jumpstart dismissed.', 'jetpack' ),
1124
				) );
1125
			} else {
1126
				return new WP_Error( 'jumpstart_failed_dismiss', esc_html__( 'Jumpstart could not be dismissed.', 'jetpack' ), array( 'status' => 400 ) );
1127
			}
1128
		}
1129
1130
		// If this was not a new connection and there was nothing to dismiss, don't fail.
1131
		return rest_ensure_response( array(
1132
			'code' 	  => 'success',
1133
			'message' => esc_html__( 'Nothing to dismiss. This was not a new connection.', 'jetpack' ),
1134
		) );
1135
	}
1136
1137
	/**
1138
	 * Get the query parameters to update module options or general settings.
1139
	 *
1140
	 * @since 4.3.0
1141
	 * @since 4.4.0 Accepts a $selector parameter.
1142
	 *
1143
	 * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'.
1144
	 *
1145
	 * @return array
1146
	 */
1147
	public static function get_updateable_parameters( $selector = '' ) {
1148
		$parameters = array(
1149
			'context'     => array(
1150
				'default' => 'edit',
1151
			),
1152
		);
1153
1154
		return array_merge( $parameters, self::get_updateable_data_list( $selector ) );
1155
	}
1156
1157
	/**
1158
	 * Returns a list of module options or general settings that can be updated.
1159
	 *
1160
	 * @since 4.3.0
1161
	 * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list.
1162
	 *
1163
	 * @param string|array $selector Module slug, 'any', or an array of parameters.
1164
	 *                               If empty, it's assumed we're updating a module and we'll try to get its slug.
1165
	 *                               If 'any' the full list is returned.
1166
	 *                               If it's an array of parameters, includes the elements by matching keys.
1167
	 *
1168
	 * @return array
1169
	 */
1170
	public static function get_updateable_data_list( $selector = '' ) {
1171
1172
		$options = array(
1173
1174
			// Carousel
1175
			'carousel_background_color' => array(
1176
				'description'       => esc_html__( 'Color scheme.', 'jetpack' ),
1177
				'type'              => 'string',
1178
				'default'           => 'black',
1179
				'enum'              => array(
1180
					'black',
1181
					'white',
1182
				),
1183
				'enum_labels' => array(
1184
					'black' => esc_html__( 'Black', 'jetpack' ),
1185
					'white' => esc_html__( 'White', 'jetpack' ),
1186
				),
1187
				'validate_callback' => __CLASS__ . '::validate_list_item',
1188
				'jp_group'          => 'carousel',
1189
			),
1190
			'carousel_display_exif' => array(
1191
				'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 ) ) ),
1192
				'type'              => 'boolean',
1193
				'default'           => 0,
1194
				'validate_callback' => __CLASS__ . '::validate_boolean',
1195
				'jp_group'          => 'carousel',
1196
			),
1197
1198
			// Comments
1199
			'highlander_comment_form_prompt' => array(
1200
				'description'       => esc_html__( 'Greeting Text', 'jetpack' ),
1201
				'type'              => 'string',
1202
				'default'           => esc_html__( 'Leave a Reply', 'jetpack' ),
1203
				'sanitize_callback' => 'sanitize_text_field',
1204
				'jp_group'          => 'comments',
1205
			),
1206
			'jetpack_comment_form_color_scheme' => array(
1207
				'description'       => esc_html__( "Color scheme", 'jetpack' ),
1208
				'type'              => 'string',
1209
				'default'           => 'light',
1210
				'enum'              => array(
1211
					'light',
1212
					'dark',
1213
					'transparent',
1214
				),
1215
				'enum_labels' => array(
1216
					'light'       => esc_html__( 'Light', 'jetpack' ),
1217
					'dark'        => esc_html__( 'Dark', 'jetpack' ),
1218
					'transparent' => esc_html__( 'Transparent', 'jetpack' ),
1219
				),
1220
				'validate_callback' => __CLASS__ . '::validate_list_item',
1221
				'jp_group'          => 'comments',
1222
			),
1223
1224
			// Custom Content Types
1225
			'jetpack_portfolio' => array(
1226
				'description'       => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1227
				'type'              => 'boolean',
1228
				'default'           => 0,
1229
				'validate_callback' => __CLASS__ . '::validate_boolean',
1230
				'jp_group'          => 'custom-content-types',
1231
			),
1232
			'jetpack_portfolio_posts_per_page' => array(
1233
				'description'       => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1234
				'type'              => 'integer',
1235
				'default'           => 10,
1236
				'validate_callback' => __CLASS__ . '::validate_posint',
1237
				'jp_group'          => 'custom-content-types',
1238
			),
1239
			'jetpack_testimonial' => array(
1240
				'description'       => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1241
				'type'              => 'boolean',
1242
				'default'           => 0,
1243
				'validate_callback' => __CLASS__ . '::validate_boolean',
1244
				'jp_group'          => 'custom-content-types',
1245
			),
1246
			'jetpack_testimonial_posts_per_page' => array(
1247
				'description'       => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1248
				'type'              => 'integer',
1249
				'default'           => 10,
1250
				'validate_callback' => __CLASS__ . '::validate_posint',
1251
				'jp_group'          => 'custom-content-types',
1252
			),
1253
1254
			// Galleries
1255
			'tiled_galleries' => array(
1256
				'description'       => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1257
				'type'              => 'boolean',
1258
				'default'           => 0,
1259
				'validate_callback' => __CLASS__ . '::validate_boolean',
1260
				'jp_group'          => 'tiled-gallery',
1261
			),
1262
1263
			'gravatar_disable_hovercards' => array(
1264
				'description'       => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1265
				'type'              => 'string',
1266
				'default'           => 'enabled',
1267
				// Not visible. This is used as the checkbox value.
1268
				'enum'              => array(
1269
					'enabled',
1270
					'disabled',
1271
				),
1272
				'enum_labels' => array(
1273
					'enabled'  => esc_html__( 'Enabled', 'jetpack' ),
1274
					'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1275
				),
1276
				'validate_callback' => __CLASS__ . '::validate_list_item',
1277
				'jp_group'          => 'gravatar-hovercards',
1278
			),
1279
1280
			// Infinite Scroll
1281
			'infinite_scroll' => array(
1282
				'description'       => esc_html__( 'To infinity and beyond', 'jetpack' ),
1283
				'type'              => 'boolean',
1284
				'default'           => 1,
1285
				'validate_callback' => __CLASS__ . '::validate_boolean',
1286
				'jp_group'          => 'infinite-scroll',
1287
			),
1288
			'infinite_scroll_google_analytics' => array(
1289
				'description'       => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1290
				'type'              => 'boolean',
1291
				'default'           => 0,
1292
				'validate_callback' => __CLASS__ . '::validate_boolean',
1293
				'jp_group'          => 'infinite-scroll',
1294
			),
1295
1296
			// Likes
1297
			'wpl_default' => array(
1298
				'description'       => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1299
				'type'              => 'string',
1300
				'default'           => 'on',
1301
				'enum'              => array(
1302
					'on',
1303
					'off',
1304
				),
1305
				'enum_labels' => array(
1306
					'on'  => esc_html__( 'On for all posts', 'jetpack' ),
1307
					'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1308
				),
1309
				'validate_callback' => __CLASS__ . '::validate_list_item',
1310
				'jp_group'          => 'likes',
1311
			),
1312
			'social_notifications_like' => array(
1313
				'description'       => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1314
				'type'              => 'boolean',
1315
				'default'           => 1,
1316
				'validate_callback' => __CLASS__ . '::validate_boolean',
1317
				'jp_group'          => 'likes',
1318
			),
1319
1320
			// Markdown
1321
			'wpcom_publish_comments_with_markdown' => array(
1322
				'description'       => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1323
				'type'              => 'boolean',
1324
				'default'           => 0,
1325
				'validate_callback' => __CLASS__ . '::validate_boolean',
1326
				'jp_group'          => 'markdown',
1327
			),
1328
			'wpcom_publish_posts_with_markdown' => array(
1329
				'description'       => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1330
				'type'              => 'boolean',
1331
				'default'           => 0,
1332
				'validate_callback' => __CLASS__ . '::validate_boolean',
1333
				'jp_group'          => 'markdown',
1334
			),
1335
1336
			// Mobile Theme
1337
			'wp_mobile_excerpt' => array(
1338
				'description'       => esc_html__( 'Excerpts', 'jetpack' ),
1339
				'type'              => 'boolean',
1340
				'default'           => 0,
1341
				'validate_callback' => __CLASS__ . '::validate_boolean',
1342
				'jp_group'          => 'minileven',
1343
			),
1344
			'wp_mobile_featured_images' => array(
1345
				'description'       => esc_html__( 'Featured Images', 'jetpack' ),
1346
				'type'              => 'boolean',
1347
				'default'           => 0,
1348
				'validate_callback' => __CLASS__ . '::validate_boolean',
1349
				'jp_group'          => 'minileven',
1350
			),
1351
			'wp_mobile_app_promos' => array(
1352
				'description'       => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ),
1353
				'type'              => 'boolean',
1354
				'default'           => 0,
1355
				'validate_callback' => __CLASS__ . '::validate_boolean',
1356
				'jp_group'          => 'minileven',
1357
			),
1358
1359
			// Monitor
1360
			'monitor_receive_notifications' => array(
1361
				'description'       => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1362
				'type'              => 'boolean',
1363
				'default'           => 0,
1364
				'validate_callback' => __CLASS__ . '::validate_boolean',
1365
				'jp_group'          => 'monitor',
1366
			),
1367
1368
			// Post by Email
1369
			'post_by_email_address' => array(
1370
				'description'       => esc_html__( 'Email Address', 'jetpack' ),
1371
				'type'              => 'string',
1372
				'default'           => 'noop',
1373
				'enum'              => array(
1374
					'noop',
1375
					'create',
1376
					'regenerate',
1377
					'delete',
1378
				),
1379
				'enum_labels' => array(
1380
					'noop'       => '',
1381
					'create'     => esc_html__( 'Create Post by Email address', 'jetpack' ),
1382
					'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1383
					'delete'     => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1384
				),
1385
				'validate_callback' => __CLASS__ . '::validate_list_item',
1386
				'jp_group'          => 'post-by-email',
1387
			),
1388
1389
			// Protect
1390
			'jetpack_protect_key' => array(
1391
				'description'       => esc_html__( 'Protect API key', 'jetpack' ),
1392
				'type'              => 'string',
1393
				'default'           => '',
1394
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1395
				'jp_group'          => 'protect',
1396
			),
1397
			'jetpack_protect_global_whitelist' => array(
1398
				'description'       => esc_html__( 'Protect global whitelist', 'jetpack' ),
1399
				'type'              => 'string',
1400
				'default'           => '',
1401
				'validate_callback' => __CLASS__ . '::validate_string',
1402
				'sanitize_callback' => 'esc_textarea',
1403
				'jp_group'          => 'protect',
1404
			),
1405
1406
			// Sharing
1407
			'sharing_services' => array(
1408
				'description'       => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1409
				'type'              => 'object',
1410
				'default'           => array(
1411
					'visible' => array( 'twitter', 'facebook', 'google-plus-1' ),
1412
					'hidden'  => array(),
1413
				),
1414
				'validate_callback' => __CLASS__ . '::validate_services',
1415
				'jp_group'          => 'sharedaddy',
1416
			),
1417
			'button_style' => array(
1418
				'description'       => esc_html__( 'Button Style', 'jetpack' ),
1419
				'type'              => 'string',
1420
				'default'           => 'icon',
1421
				'enum'              => array(
1422
					'icon-text',
1423
					'icon',
1424
					'text',
1425
					'official',
1426
				),
1427
				'enum_labels' => array(
1428
					'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1429
					'icon'      => esc_html__( 'Icon only', 'jetpack' ),
1430
					'text'      => esc_html__( 'Text only', 'jetpack' ),
1431
					'official'  => esc_html__( 'Official buttons', 'jetpack' ),
1432
				),
1433
				'validate_callback' => __CLASS__ . '::validate_list_item',
1434
				'jp_group'          => 'sharedaddy',
1435
			),
1436
			'sharing_label' => array(
1437
				'description'       => esc_html__( 'Sharing Label', 'jetpack' ),
1438
				'type'              => 'string',
1439
				'default'           => '',
1440
				'validate_callback' => __CLASS__ . '::validate_string',
1441
				'sanitize_callback' => 'esc_html',
1442
				'jp_group'          => 'sharedaddy',
1443
			),
1444
			'show' => array(
1445
				'description'       => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1446
				'type'              => 'array',
1447
				'items'             => array(
1448
					'type' => 'string'
1449
				),
1450
				'default'           => array( 'post' ),
1451
				'validate_callback' => __CLASS__ . '::validate_sharing_show',
1452
				'jp_group'          => 'sharedaddy',
1453
			),
1454
			'jetpack-twitter-cards-site-tag' => array(
1455
				'description'       => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
1456
				'type'              => 'string',
1457
				'default'           => '',
1458
				'validate_callback' => __CLASS__ . '::validate_twitter_username',
1459
				'sanitize_callback' => 'esc_html',
1460
				'jp_group'          => 'sharedaddy',
1461
			),
1462
			'sharedaddy_disable_resources' => array(
1463
				'description'       => esc_html__( 'Disable CSS and JS', 'jetpack' ),
1464
				'type'              => 'boolean',
1465
				'default'           => 0,
1466
				'validate_callback' => __CLASS__ . '::validate_boolean',
1467
				'jp_group'          => 'sharedaddy',
1468
			),
1469
			'custom' => array(
1470
				'description'       => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
1471
				'type'              => 'object',
1472
				'default'           => array(
1473
					'sharing_name' => '',
1474
					'sharing_url'  => '',
1475
					'sharing_icon' => '',
1476
				),
1477
				'validate_callback' => __CLASS__ . '::validate_custom_service',
1478
				'jp_group'          => 'sharedaddy',
1479
			),
1480
			// Not an option, but an action that can be perfomed on the list of custom services passing the service ID.
1481
			'sharing_delete_service' => array(
1482
				'description'       => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
1483
				'type'              => 'string',
1484
				'default'           => '',
1485
				'validate_callback' => __CLASS__ . '::validate_custom_service_id',
1486
				'jp_group'          => 'sharedaddy',
1487
			),
1488
1489
			// SSO
1490
			'jetpack_sso_require_two_step' => array(
1491
				'description'       => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
1492
				'type'              => 'boolean',
1493
				'default'           => 0,
1494
				'validate_callback' => __CLASS__ . '::validate_boolean',
1495
				'jp_group'          => 'sso',
1496
			),
1497
			'jetpack_sso_match_by_email' => array(
1498
				'description'       => esc_html__( 'Match by Email', 'jetpack' ),
1499
				'type'              => 'boolean',
1500
				'default'           => 0,
1501
				'validate_callback' => __CLASS__ . '::validate_boolean',
1502
				'jp_group'          => 'sso',
1503
			),
1504
1505
			// Subscriptions
1506
			'stb_enabled' => array(
1507
				'description'       => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ),
1508
				'type'              => 'boolean',
1509
				'default'           => 1,
1510
				'validate_callback' => __CLASS__ . '::validate_boolean',
1511
				'jp_group'          => 'subscriptions',
1512
			),
1513
			'stc_enabled' => array(
1514
				'description'       => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
1515
				'type'              => 'boolean',
1516
				'default'           => 1,
1517
				'validate_callback' => __CLASS__ . '::validate_boolean',
1518
				'jp_group'          => 'subscriptions',
1519
			),
1520
1521
			// Related Posts
1522
			'show_headline' => array(
1523
				'description'       => esc_html__( 'Show a "Related" header to more clearly separate the related section from posts', 'jetpack' ),
1524
				'type'              => 'boolean',
1525
				'default'           => 1,
1526
				'validate_callback' => __CLASS__ . '::validate_boolean',
1527
				'jp_group'          => 'related-posts',
1528
			),
1529
			'show_thumbnails' => array(
1530
				'description'       => esc_html__( 'Use a large and visually striking layout', 'jetpack' ),
1531
				'type'              => 'boolean',
1532
				'default'           => 0,
1533
				'validate_callback' => __CLASS__ . '::validate_boolean',
1534
				'jp_group'          => 'related-posts',
1535
			),
1536
1537
			// Spelling and Grammar - After the Deadline
1538
			'onpublish' => array(
1539
				'description'       => esc_html__( 'Proofread when a post or page is first published.', 'jetpack' ),
1540
				'type'              => 'boolean',
1541
				'default'           => 0,
1542
				'validate_callback' => __CLASS__ . '::validate_boolean',
1543
				'jp_group'          => 'after-the-deadline',
1544
			),
1545
			'onupdate' => array(
1546
				'description'       => esc_html__( 'Proofread when a post or page is updated.', 'jetpack' ),
1547
				'type'              => 'boolean',
1548
				'default'           => 0,
1549
				'validate_callback' => __CLASS__ . '::validate_boolean',
1550
				'jp_group'          => 'after-the-deadline',
1551
			),
1552
			'Bias Language' => array(
1553
				'description'       => esc_html__( 'Bias Language', 'jetpack' ),
1554
				'type'              => 'boolean',
1555
				'default'           => 0,
1556
				'validate_callback' => __CLASS__ . '::validate_boolean',
1557
				'jp_group'          => 'after-the-deadline',
1558
			),
1559
			'Cliches' => array(
1560
				'description'       => esc_html__( 'Clichés', 'jetpack' ),
1561
				'type'              => 'boolean',
1562
				'default'           => 0,
1563
				'validate_callback' => __CLASS__ . '::validate_boolean',
1564
				'jp_group'          => 'after-the-deadline',
1565
			),
1566
			'Complex Expression' => array(
1567
				'description'       => esc_html__( 'Complex Phrases', 'jetpack' ),
1568
				'type'              => 'boolean',
1569
				'default'           => 0,
1570
				'validate_callback' => __CLASS__ . '::validate_boolean',
1571
				'jp_group'          => 'after-the-deadline',
1572
			),
1573
			'Diacritical Marks' => array(
1574
				'description'       => esc_html__( 'Diacritical Marks', 'jetpack' ),
1575
				'type'              => 'boolean',
1576
				'default'           => 0,
1577
				'validate_callback' => __CLASS__ . '::validate_boolean',
1578
				'jp_group'          => 'after-the-deadline',
1579
			),
1580
			'Double Negative' => array(
1581
				'description'       => esc_html__( 'Double Negatives', 'jetpack' ),
1582
				'type'              => 'boolean',
1583
				'default'           => 0,
1584
				'validate_callback' => __CLASS__ . '::validate_boolean',
1585
				'jp_group'          => 'after-the-deadline',
1586
			),
1587
			'Hidden Verbs' => array(
1588
				'description'       => esc_html__( 'Hidden Verbs', 'jetpack' ),
1589
				'type'              => 'boolean',
1590
				'default'           => 0,
1591
				'validate_callback' => __CLASS__ . '::validate_boolean',
1592
				'jp_group'          => 'after-the-deadline',
1593
			),
1594
			'Jargon Language' => array(
1595
				'description'       => esc_html__( 'Jargon', 'jetpack' ),
1596
				'type'              => 'boolean',
1597
				'default'           => 0,
1598
				'validate_callback' => __CLASS__ . '::validate_boolean',
1599
				'jp_group'          => 'after-the-deadline',
1600
			),
1601
			'Passive voice' => array(
1602
				'description'       => esc_html__( 'Passive Voice', 'jetpack' ),
1603
				'type'              => 'boolean',
1604
				'default'           => 0,
1605
				'validate_callback' => __CLASS__ . '::validate_boolean',
1606
				'jp_group'          => 'after-the-deadline',
1607
			),
1608
			'Phrases to Avoid' => array(
1609
				'description'       => esc_html__( 'Phrases to Avoid', 'jetpack' ),
1610
				'type'              => 'boolean',
1611
				'default'           => 0,
1612
				'validate_callback' => __CLASS__ . '::validate_boolean',
1613
				'jp_group'          => 'after-the-deadline',
1614
			),
1615
			'Redundant Expression' => array(
1616
				'description'       => esc_html__( 'Redundant Phrases', 'jetpack' ),
1617
				'type'              => 'boolean',
1618
				'default'           => 0,
1619
				'validate_callback' => __CLASS__ . '::validate_boolean',
1620
				'jp_group'          => 'after-the-deadline',
1621
			),
1622
			'guess_lang' => array(
1623
				'description'       => esc_html__( 'Use automatically detected language to proofread posts and pages', 'jetpack' ),
1624
				'type'              => 'boolean',
1625
				'default'           => 0,
1626
				'validate_callback' => __CLASS__ . '::validate_boolean',
1627
				'jp_group'          => 'after-the-deadline',
1628
			),
1629
			'ignored_phrases' => array(
1630
				'description'       => esc_html__( 'Add Phrase to be ignored', 'jetpack' ),
1631
				'type'              => 'string',
1632
				'default'           => '',
1633
				'sanitize_callback' => 'esc_html',
1634
				'jp_group'          => 'after-the-deadline',
1635
			),
1636
			'unignore_phrase' => array(
1637
				'description'       => esc_html__( 'Remove Phrase from being ignored', 'jetpack' ),
1638
				'type'              => 'string',
1639
				'default'           => '',
1640
				'sanitize_callback' => 'esc_html',
1641
				'jp_group'          => 'after-the-deadline',
1642
			),
1643
1644
			// Verification Tools
1645
			'google' => array(
1646
				'description'       => esc_html__( 'Google Search Console', 'jetpack' ),
1647
				'type'              => 'string',
1648
				'default'           => '',
1649
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1650
				'jp_group'          => 'verification-tools',
1651
			),
1652
			'bing' => array(
1653
				'description'       => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
1654
				'type'              => 'string',
1655
				'default'           => '',
1656
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1657
				'jp_group'          => 'verification-tools',
1658
			),
1659
			'pinterest' => array(
1660
				'description'       => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
1661
				'type'              => 'string',
1662
				'default'           => '',
1663
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1664
				'jp_group'          => 'verification-tools',
1665
			),
1666
			'yandex' => array(
1667
				'description'       => esc_html__( 'Yandex Site Verification', 'jetpack' ),
1668
				'type'              => 'string',
1669
				'default'           => '',
1670
				'validate_callback' => __CLASS__ . '::validate_verification_service',
1671
				'jp_group'          => 'verification-tools',
1672
			),
1673
			'enable_header_ad' => array(
1674
				'description'        => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
1675
				'type'               => 'boolean',
1676
				'default'            => 0,
1677
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1678
				'jp_group'           => 'wordads',
1679
			),
1680
			'wordads_approved' => array(
1681
				'description'        => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
1682
				'type'               => 'boolean',
1683
				'default'            => 0,
1684
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1685
				'jp_group'           => 'wordads',
1686
			),
1687
			'wordads_second_belowpost' => array(
1688
				'description'        => esc_html__( 'Display second ad below post?', 'jetpack' ),
1689
				'type'               => 'boolean',
1690
				'default'            => 1,
1691
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1692
				'jp_group'           => 'wordads',
1693
			),
1694
			'wordads_display_front_page' => array(
1695
				'description'        => esc_html__( 'Display ads on the front page?', 'jetpack' ),
1696
				'type'               => 'boolean',
1697
				'default'            => 1,
1698
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1699
				'jp_group'           => 'wordads',
1700
			),
1701
			'wordads_display_post' => array(
1702
				'description'        => esc_html__( 'Display ads on posts?', 'jetpack' ),
1703
				'type'               => 'boolean',
1704
				'default'            => 1,
1705
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1706
				'jp_group'           => 'wordads',
1707
			),
1708
			'wordads_display_page' => array(
1709
				'description'        => esc_html__( 'Display ads on pages?', 'jetpack' ),
1710
				'type'               => 'boolean',
1711
				'default'            => 1,
1712
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1713
				'jp_group'           => 'wordads',
1714
			),
1715
			'wordads_display_archive' => array(
1716
				'description'        => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
1717
				'type'               => 'boolean',
1718
				'default'            => 1,
1719
				'validate_callback'  => __CLASS__ . '::validate_boolean',
1720
				'jp_group'           => 'wordads',
1721
			),
1722
1723
			// Google Analytics
1724
			'google_analytics_tracking_id' => array(
1725
				'description'        => esc_html__( 'Google Analytics', 'jetpack' ),
1726
				'type'               => 'string',
1727
				'default'            => '',
1728
				'validate_callback'  => __CLASS__ . '::validate_alphanum',
1729
				'jp_group'           => 'google-analytics',
1730
			),
1731
1732
			// Stats
1733
			'admin_bar' => array(
1734
				'description'       => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ),
1735
				'type'              => 'boolean',
1736
				'default'           => 1,
1737
				'validate_callback' => __CLASS__ . '::validate_boolean',
1738
				'jp_group'          => 'stats',
1739
			),
1740
			'roles' => array(
1741
				'description'       => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
1742
				'type'              => 'array',
1743
				'items'             => array(
1744
					'type' => 'string'
1745
				),
1746
				'default'           => array( 'administrator' ),
1747
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
1748
				'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
1749
				'jp_group'          => 'stats',
1750
			),
1751
			'count_roles' => array(
1752
				'description'       => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
1753
				'type'              => 'array',
1754
				'items'             => array(
1755
					'type' => 'string'
1756
				),
1757
				'default'           => array( 'administrator' ),
1758
				'validate_callback' => __CLASS__ . '::validate_stats_roles',
1759
				'jp_group'          => 'stats',
1760
			),
1761
			'blog_id' => array(
1762
				'description'       => esc_html__( 'Blog ID.', 'jetpack' ),
1763
				'type'              => 'boolean',
1764
				'default'           => 0,
1765
				'validate_callback' => __CLASS__ . '::validate_boolean',
1766
				'jp_group'          => 'stats',
1767
			),
1768
			'do_not_track' => array(
1769
				'description'       => esc_html__( 'Do not track.', 'jetpack' ),
1770
				'type'              => 'boolean',
1771
				'default'           => 1,
1772
				'validate_callback' => __CLASS__ . '::validate_boolean',
1773
				'jp_group'          => 'stats',
1774
			),
1775
			'hide_smile' => array(
1776
				'description'       => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ),
1777
				'type'              => 'boolean',
1778
				'default'           => 1,
1779
				'validate_callback' => __CLASS__ . '::validate_boolean',
1780
				'jp_group'          => 'stats',
1781
			),
1782
			'version' => array(
1783
				'description'       => esc_html__( 'Version.', 'jetpack' ),
1784
				'type'              => 'integer',
1785
				'default'           => 9,
1786
				'validate_callback' => __CLASS__ . '::validate_posint',
1787
				'jp_group'          => 'stats',
1788
			),
1789
1790
			// Settings - Not a module
1791
			self::holiday_snow_option_name() => array(
1792
				'description'       => '',
1793
				'type'              => 'boolean',
1794
				'default'           => 0,
1795
				'validate_callback' => __CLASS__ . '::validate_boolean',
1796
				'jp_group'          => 'settings',
1797
			),
1798
1799
			// Akismet - Not a module, but a plugin. The options can be passed and handled differently.
1800
			'akismet_show_user_comments_approved' => array(
1801
				'description'       => '',
1802
				'type'              => 'boolean',
1803
				'default'           => 0,
1804
				'validate_callback' => __CLASS__ . '::validate_boolean',
1805
				'jp_group'          => 'settings',
1806
			),
1807
1808
			'wordpress_api_key' => array(
1809
				'description'       => '',
1810
				'type'              => 'string',
1811
				'default'           => '',
1812
				'validate_callback' => __CLASS__ . '::validate_alphanum',
1813
				'jp_group'          => 'settings',
1814
			),
1815
1816
			// Apps card on dashboard
1817
			'dismiss_dash_app_card' => array(
1818
				'description'       => '',
1819
				'type'              => 'boolean',
1820
				'default'           => 0,
1821
				'validate_callback' => __CLASS__ . '::validate_boolean',
1822
				'jp_group'          => 'settings',
1823
			),
1824
1825
			// Empty stats card dismiss
1826
			'dismiss_empty_stats_card' => array(
1827
				'description'       => '',
1828
				'type'              => 'boolean',
1829
				'default'           => 0,
1830
				'validate_callback' => __CLASS__ . '::validate_boolean',
1831
				'jp_group'          => 'settings',
1832
			),
1833
1834
			'onboarding' => array(
1835
				'description'       => '',
1836
				'type'              => 'object',
1837
				'default'           => array(
1838
					'token'            => '',
1839
					'siteTitle'        => '',
1840
					'siteDescription'  => '',
1841
					'genre'            => 'blog',
1842
					'businessPersonal' => 'personal',
1843
					'businessInfo'     => array(
1844
						'businessName'     => '',
1845
						'businessAddress'  => '',
1846
						'businessCity'     => '',
1847
						'businessState'    => '',
1848
						'businessZipCode'  => '',
1849
					),
1850
					'homepageFormat'   => 'news',
1851
					'addContactForm'   => false,
1852
					'end'              => false,
1853
				),
1854
				'validate_callback' => __CLASS__ . '::validate_onboarding',
1855
				'jp_group'          => 'settings',
1856
			),
1857
1858
			// Show welcome for newly purchased plan
1859
			'show_welcome_for_new_plan' => array(
1860
				'description'       => '',
1861
				'type'              => 'boolean',
1862
				'default'           => 0,
1863
				'validate_callback' => __CLASS__ . '::validate_boolean',
1864
				'jp_group'          => 'settings',
1865
			),
1866
1867
		);
1868
1869
		// Add modules to list so they can be toggled
1870
		$modules = Jetpack::get_available_modules();
1871
		if ( is_array( $modules ) && ! empty( $modules ) ) {
1872
			$module_args = array(
1873
				'description'       => '',
1874
				'type'              => 'boolean',
1875
				'default'           => 0,
1876
				'validate_callback' => __CLASS__ . '::validate_boolean',
1877
				'jp_group'          => 'modules',
1878
			);
1879
			foreach( $modules as $module ) {
1880
				$options[ $module ] = $module_args;
1881
			}
1882
		}
1883
1884
		if ( is_array( $selector ) ) {
1885
1886
			// Return only those options whose keys match $selector keys
1887
			return array_intersect_key( $options, $selector );
1888
		}
1889
1890
		if ( 'any' === $selector ) {
1891
1892
			// Toggle module or update any module option or any general setting
1893
			return $options;
1894
		}
1895
1896
		// We're updating the options for a single module.
1897
		if ( empty( $selector ) ) {
1898
			$selector = self::get_module_requested();
1899
		}
1900
		$selected = array();
1901
		foreach ( $options as $option => $attributes ) {
1902
1903
			// Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
1904
			if ( $selector === $attributes['jp_group'] ) {
1905
				$selected[ $option ] = $attributes;
1906
			}
1907
		}
1908
		return $selected;
1909
	}
1910
1911
	/**
1912
	 * Validates that the parameters are proper values that can be set during Jetpack onboarding.
1913
	 *
1914
	 * @since 5.4.0
1915
	 *
1916
	 * @param array           $onboarding_data Values to check.
1917
	 * @param WP_REST_Request $request         The request sent to the WP REST API.
1918
	 * @param string          $param           Name of the parameter passed to endpoint holding $value.
1919
	 *
1920
	 * @return bool|WP_Error
1921
	 */
1922
	public static function validate_onboarding( $onboarding_data, $request, $param ) {
1923
		if ( ! is_array( $onboarding_data ) ) {
1924
			return new WP_Error( 'invalid_param', esc_html__( 'Not valid onboarding data.', 'jetpack' ) );
1925
		}
1926
		foreach ( $onboarding_data as $value ) {
1927
			if ( is_string( $value ) ) {
1928
				$onboarding_choice = self::validate_string( $value, $request, $param );
1929
			} elseif ( is_array( $value ) ) {
1930
				$onboarding_choice = self::validate_onboarding( $value, $request, $param );
1931
			} else {
1932
				$onboarding_choice = self::validate_boolean( $value, $request, $param );
1933
			}
1934
			if ( is_wp_error( $onboarding_choice ) ) {
1935
				return $onboarding_choice;
1936
			}
1937
		}
1938
		return true;
1939
	}
1940
1941
	/**
1942
	 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
1943
	 *
1944
	 * @since 4.3.0
1945
	 *
1946
	 * @param string|bool $value Value to check.
1947
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1948
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1949
	 *
1950
	 * @return bool|WP_Error
1951
	 */
1952
	public static function validate_boolean( $value, $request, $param ) {
1953
		if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) {
1954
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) );
1955
		}
1956
		return true;
1957
	}
1958
1959
	/**
1960
	 * Validates that the parameter is a positive integer.
1961
	 *
1962
	 * @since 4.3.0
1963
	 *
1964
	 * @param int $value Value to check.
1965
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1966
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1967
	 *
1968
	 * @return bool|WP_Error
1969
	 */
1970
	public static function validate_posint( $value = 0, $request, $param ) {
1971 View Code Duplication
		if ( ! is_numeric( $value ) || $value <= 0 ) {
1972
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a positive integer.', 'jetpack' ), $param ) );
1973
		}
1974
		return true;
1975
	}
1976
1977
	/**
1978
	 * Validates that the parameter belongs to a list of admitted values.
1979
	 *
1980
	 * @since 4.3.0
1981
	 *
1982
	 * @param string $value Value to check.
1983
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1984
	 * @param string $param Name of the parameter passed to endpoint holding $value.
1985
	 *
1986
	 * @return bool|WP_Error
1987
	 */
1988
	public static function validate_list_item( $value = '', $request, $param ) {
1989
		$attributes = $request->get_attributes();
1990
		if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1991
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s not recognized', 'jetpack' ), $param ) );
1992
		}
1993
		$args = $attributes['args'][ $param ];
1994
		if ( ! empty( $args['enum'] ) ) {
1995
1996
			// If it's an associative array, use the keys to check that the value is among those admitted.
1997
			$enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) ? array_keys( $args['enum'] ) : $args['enum'];
1998 View Code Duplication
			if ( ! in_array( $value, $enum ) ) {
1999
				return new WP_Error( 'invalid_param_value', sprintf(
2000
					/* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
2001
					esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum )
2002
				) );
2003
			}
2004
		}
2005
		return true;
2006
	}
2007
2008
	/**
2009
	 * Validates that the parameter belongs to a list of admitted values.
2010
	 *
2011
	 * @since 4.3.0
2012
	 *
2013
	 * @param string $value Value to check.
2014
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2015
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2016
	 *
2017
	 * @return bool|WP_Error
2018
	 */
2019
	public static function validate_module_list( $value = '', $request, $param ) {
2020
		if ( ! is_array( $value ) ) {
2021
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be an array', 'jetpack' ), $param ) );
2022
		}
2023
2024
		$modules = Jetpack::get_available_modules();
2025
2026 View Code Duplication
		if ( count( array_intersect( $value, $modules ) ) != count( $value ) ) {
2027
			return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be a list of valid modules', 'jetpack' ), $param ) );
2028
		}
2029
2030
		return true;
2031
	}
2032
2033
	/**
2034
	 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
2035
	 *
2036
	 * @since 4.3.0
2037
	 *
2038
	 * @param string $value Value to check.
2039
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2040
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2041
	 *
2042
	 * @return bool|WP_Error
2043
	 */
2044
	public static function validate_alphanum( $value = '', $request, $param ) {
2045 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
2046
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) );
2047
		}
2048
		return true;
2049
	}
2050
2051
	/**
2052
	 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
2053
	 *
2054
	 * @since 4.6.0
2055
	 *
2056
	 * @param string $value Value to check.
2057
	 * @param WP_REST_Request $request
2058
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2059
	 *
2060
	 * @return bool|WP_Error
2061
	 */
2062
	public static function validate_verification_service( $value = '', $request, $param ) {
2063 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 ) ) ) ) {
2064
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
2065
		}
2066
		return true;
2067
	}
2068
2069
	/**
2070
	 * Validates that the parameter is among the roles allowed for Stats.
2071
	 *
2072
	 * @since 4.3.0
2073
	 *
2074
	 * @param string|bool $value Value to check.
2075
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2076
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2077
	 *
2078
	 * @return bool|WP_Error
2079
	 */
2080
	public static function validate_stats_roles( $value, $request, $param ) {
2081
		if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) {
2082
			return new WP_Error( 'invalid_param', sprintf(
2083
				/* 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. */
2084
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', self::$stats_roles )
2085
			) );
2086
		}
2087
		return true;
2088
	}
2089
2090
	/**
2091
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2092
	 *
2093
	 * @since 4.3.0
2094
	 *
2095
	 * @param string|bool $value Value to check.
2096
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2097
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2098
	 *
2099
	 * @return bool|WP_Error
2100
	 */
2101
	public static function validate_sharing_show( $value, $request, $param ) {
2102
		$views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
2103
		if ( ! is_array( $value ) ) {
2104
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) );
2105
		}
2106 View Code Duplication
		if ( ! array_intersect( $views, $value ) ) {
2107
			return new WP_Error( 'invalid_param', sprintf(
2108
				/* 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 */
2109
				esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views )
2110
			) );
2111
		}
2112
		return true;
2113
	}
2114
2115
	/**
2116
	 * Validates that the parameter is among the views where the Sharing can be displayed.
2117
	 *
2118
	 * @since 4.3.0
2119
	 *
2120
	 * @param string|bool $value {
2121
	 *     Value to check received by request.
2122
	 *
2123
	 *     @type array $visible List of slug of services to share to that are displayed directly in the page.
2124
	 *     @type array $hidden  List of slug of services to share to that are concealed in a folding menu.
2125
	 * }
2126
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2127
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2128
	 *
2129
	 * @return bool|WP_Error
2130
	 */
2131
	public static function validate_services( $value, $request, $param ) {
2132 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
2133
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), $param ) );
2134
		}
2135
2136
		// Allow to clear everything.
2137
		if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
2138
			return true;
2139
		}
2140
2141 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2142
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2143
		}
2144
		$sharer = new Sharing_Service();
2145
		$services = array_keys( $sharer->get_all_services() );
2146
2147
		if (
2148
			( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
2149
			||
2150
			( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) )
2151
		{
2152
			return new WP_Error( 'invalid_param', sprintf(
2153
				/* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
2154
				esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services )
2155
			) );
2156
		}
2157
		return true;
2158
	}
2159
2160
	/**
2161
	 * Validates that the parameter has enough information to build a custom sharing button.
2162
	 *
2163
	 * @since 4.3.0
2164
	 *
2165
	 * @param string|bool $value Value to check.
2166
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2167
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2168
	 *
2169
	 * @return bool|WP_Error
2170
	 */
2171
	public static function validate_custom_service( $value, $request, $param ) {
2172 View Code Duplication
		if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
2173
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), $param ) );
2174
		}
2175
2176
		// Allow to clear everything.
2177
		if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
2178
			return true;
2179
		}
2180
2181 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2182
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2183
		}
2184
2185
		if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
2186
		|| ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
2187
		|| ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
2188
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), $param ) );
2189
		}
2190
		return true;
2191
	}
2192
2193
	/**
2194
	 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
2195
	 *
2196
	 * @since 4.3.0
2197
	 *
2198
	 * @param string $value Value to check.
2199
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2200
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2201
	 *
2202
	 * @return bool|WP_Error
2203
	 */
2204
	public static function validate_custom_service_id( $value = '', $request, $param ) {
2205 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
2206
			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 ) );
2207
		}
2208
2209 View Code Duplication
		if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2210
			return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
2211
		}
2212
		$sharer = new Sharing_Service();
2213
		$services = array_keys( $sharer->get_all_services() );
2214
2215 View Code Duplication
		if ( ! empty( $value ) && ! in_array( $value, $services ) ) {
2216
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), $param ) );
2217
		}
2218
2219
		return true;
2220
	}
2221
2222
	/**
2223
	 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
2224
	 *
2225
	 * @since 4.3.0
2226
	 *
2227
	 * @param string $value Value to check.
2228
	 * @param WP_REST_Request $request
2229
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2230
	 *
2231
	 * @return bool|WP_Error
2232
	 */
2233
	public static function validate_twitter_username( $value = '', $request, $param ) {
2234 View Code Duplication
		if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
2235
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a Twitter username.', 'jetpack' ), $param ) );
2236
		}
2237
		return true;
2238
	}
2239
2240
	/**
2241
	 * Validates that the parameter is a string.
2242
	 *
2243
	 * @since 4.3.0
2244
	 *
2245
	 * @param string $value Value to check.
2246
	 * @param WP_REST_Request $request The request sent to the WP REST API.
2247
	 * @param string $param Name of the parameter passed to endpoint holding $value.
2248
	 *
2249
	 * @return bool|WP_Error
2250
	 */
2251
	public static function validate_string( $value = '', $request, $param ) {
0 ignored issues
show
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
2252 View Code Duplication
		if ( ! is_string( $value ) ) {
2253
			return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack' ), $param ) );
2254
		}
2255
		return true;
2256
	}
2257
2258
	/**
2259
	 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
2260
	 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
2261
	 *
2262
	 * @since 4.3.0
2263
	 *
2264
	 * @param string|bool $value Value to check.
2265
	 *
2266
	 * @return bool|array
2267
	 */
2268
	public static function sanitize_stats_allowed_roles( $value ) {
2269
		if ( empty( $value ) ) {
2270
			return array( 'administrator' );
2271
		}
2272
		return $value;
2273
	}
2274
2275
	/**
2276
	 * Get the currently accessed route and return the module slug in it.
2277
	 *
2278
	 * @since 4.3.0
2279
	 *
2280
	 * @param string $route Regular expression for the endpoint with the module slug to return.
2281
	 *
2282
	 * @return array|string
2283
	 */
2284
	public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
2285
2286
		if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
2287
			return '';
2288
		}
2289
2290
		preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
2291
2292
		if ( empty( $module['slug'] ) ) {
2293
			return '';
2294
		}
2295
2296
		return $module['slug'];
2297
	}
2298
2299
	/**
2300
	 * Adds extra information for modules.
2301
	 *
2302
	 * @since 4.3.0
2303
	 *
2304
	 * @param string|array $modules Can be a single module or a list of modules.
2305
	 * @param null|string  $slug    Slug of the module in the first parameter.
2306
	 *
2307
	 * @return array|string
2308
	 */
2309
	public static function prepare_modules_for_response( $modules = '', $slug = null ) {
2310
		global $wp_rewrite;
2311
2312
		/** This filter is documented in modules/sitemaps/sitemaps.php */
2313
		$location = apply_filters( 'jetpack_sitemap_location', '' );
2314
2315
		if ( $wp_rewrite->using_index_permalinks() ) {
2316
			$sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
2317
			$news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
2318
		} else if ( $wp_rewrite->using_permalinks() ) {
2319
			$sitemap_url = home_url( $location . '/sitemap.xml' );
2320
			$news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
2321
		} else {
2322
			$sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
2323
			$news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
2324
		}
2325
2326
		if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) {
2327
			// Is a list of modules
2328
			$modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
2329
			$modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
2330
		} elseif ( 'sitemaps' == $slug ) {
2331
			// It's a single module
2332
			$modules['extra']['sitemap_url'] = $sitemap_url;
2333
			$modules['extra']['news_sitemap_url'] = $news_sitemap_url;
2334
		}
2335
		return $modules;
2336
	}
2337
2338
	/**
2339
	 * Remove 'validate_callback' item from options available for module.
2340
	 * Fetch current option value and add to array of module options.
2341
	 * Prepare values of module options that need special handling, like those saved in wpcom.
2342
	 *
2343
	 * @since 4.3.0
2344
	 *
2345
	 * @param string $module Module slug.
2346
	 * @return array
2347
	 */
2348
	public static function prepare_options_for_response( $module = '' ) {
2349
		$options = self::get_updateable_data_list( $module );
2350
2351
		if ( ! is_array( $options ) || empty( $options ) ) {
2352
			return $options;
2353
		}
2354
2355
		// Some modules need special treatment.
2356
		switch ( $module ) {
2357
2358
			case 'monitor':
2359
				// Status of user notifications
2360
				$options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
2361
				break;
2362
2363
			case 'post-by-email':
2364
				// Email address
2365
				$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'] );
2366
				break;
2367
2368
			case 'protect':
2369
				// Protect
2370
				$options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
2371
				if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) {
2372
					include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' );
2373
				}
2374
				$options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist();
2375
				break;
2376
2377
			case 'related-posts':
2378
				// It's local, but it must be broken apart since it's saved as an array.
2379
				$options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
2380
				break;
2381
2382
			case 'verification-tools':
2383
				// It's local, but it must be broken apart since it's saved as an array.
2384
				$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
2385
				break;
2386
2387
			case 'google-analytics':
2388
				$wga = get_option( 'jetpack_wga' );
2389
				$code = '';
2390
				if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
2391
					 $code = $wga[ 'code' ];
2392
				}
2393
				$options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
2394
				break;
2395
2396
			case 'sharedaddy':
2397
				// It's local, but it must be broken apart since it's saved as an array.
2398
				if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
2399
					break;
2400
				}
2401
				$sharer = new Sharing_Service();
2402
				$options = self::split_options( $options, $sharer->get_global_options() );
2403
				$options['sharing_services']['current_value'] = $sharer->get_blog_services();
2404
				$other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
2405 View Code Duplication
				foreach ( $other_sharedaddy_options as $key ) {
2406
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2407
					$current_value = get_option( $key, $default_value );
2408
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2409
				}
2410
				break;
2411
2412
			case 'after-the-deadline':
2413
				if ( ! function_exists( 'AtD_get_options' ) ) {
2414
					include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' );
2415
				}
2416
				$atd_options = array_merge( AtD_get_options( get_current_user_id(), 'AtD_options' ), AtD_get_options( get_current_user_id(), 'AtD_check_when' ) );
2417
				unset( $atd_options['name'] );
2418
				foreach ( $atd_options as $key => $value ) {
2419
					$options[ $key ]['current_value'] = self::cast_value( $value, $options[ $key ] );
2420
				}
2421
				$atd_options = AtD_get_options( get_current_user_id(), 'AtD_guess_lang' );
2422
				$options['guess_lang']['current_value'] = self::cast_value( isset( $atd_options['true'] ), $options[ 'guess_lang' ] );
2423
				$options['ignored_phrases']['current_value'] = AtD_get_setting( get_current_user_id(), 'AtD_ignored_phrases' );
2424
				unset( $options['unignore_phrase'] );
2425
				break;
2426
2427
			case 'stats':
2428
				// It's local, but it must be broken apart since it's saved as an array.
2429
				if ( ! function_exists( 'stats_get_options' ) ) {
2430
					include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
2431
				}
2432
				$options = self::split_options( $options, stats_get_options() );
2433
				break;
2434
			default:
2435
				// These option are just stored as plain WordPress options.
2436 View Code Duplication
				foreach ( $options as $key => $value ) {
2437
					$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2438
					$current_value = get_option( $key, $default_value );
2439
					$options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
2440
				}
2441
		}
2442
		// At this point some options have current_value not set because they're options
2443
		// that only get written on update, so we set current_value to the default one.
2444
		foreach ( $options as $key => $value ) {
2445
			// We don't need validate_callback in the response
2446
			if ( isset( $options[ $key ]['validate_callback'] ) ) {
2447
				unset( $options[ $key ]['validate_callback'] );
2448
			}
2449
			$default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : '';
2450
			if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
2451
				$options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
2452
			}
2453
		}
2454
		return $options;
2455
	}
2456
2457
	/**
2458
	 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
2459
	 *
2460
	 * @since 4.3.0
2461
	 *
2462
	 * @param array  $separate_options Array of options admitted by the module.
2463
	 * @param array  $grouped_options Option saved as array to be splitted.
2464
	 * @param string $prefix Optional prefix for the separate option keys.
2465
	 *
2466
	 * @return array
2467
	 */
2468
	public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
2469
		if ( is_array( $grouped_options ) ) {
2470
			foreach ( $grouped_options as $key => $value ) {
2471
				$option_key = $prefix . $key;
2472
				if ( isset( $separate_options[ $option_key ] ) ) {
2473
					$separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
2474
				}
2475
			}
2476
		}
2477
		return $separate_options;
2478
	}
2479
2480
	/**
2481
	 * Perform a casting to the value specified in the option definition.
2482
	 *
2483
	 * @since 4.3.0
2484
	 *
2485
	 * @param mixed $value Value to cast to the proper type.
2486
	 * @param array $definition Type to cast the value to.
2487
	 *
2488
	 * @return bool|float|int|string
2489
	 */
2490
	public static function cast_value( $value, $definition ) {
2491
		if ( $value === 'NULL' ) {
2492
			return null;
2493
		}
2494
2495
		if ( isset( $definition['type'] ) ) {
2496
			switch ( $definition['type'] ) {
2497
				case 'boolean':
2498
					if ( 'true' === $value ) {
2499
						return true;
2500
					} elseif ( 'false' === $value ) {
2501
						return false;
2502
					}
2503
					return (bool) $value;
2504
					break;
2505
2506
				case 'integer':
2507
					return (int) $value;
2508
					break;
2509
2510
				case 'float':
2511
					return (float) $value;
2512
					break;
2513
2514
				case 'string':
2515
					return (string) $value;
2516
					break;
2517
			}
2518
		}
2519
		return $value;
2520
	}
2521
2522
	/**
2523
	 * Get a value not saved locally.
2524
	 *
2525
	 * @since 4.3.0
2526
	 *
2527
	 * @param string $module Module slug.
2528
	 * @param string $option Option name.
2529
	 *
2530
	 * @return bool Whether user is receiving notifications or not.
2531
	 */
2532
	public static function get_remote_value( $module, $option ) {
2533
2534
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
2535
			$option .= get_current_user_id();
2536
		}
2537
2538
		// If option doesn't exist, 'does_not_exist' will be returned.
2539
		$value = get_option( $option, 'does_not_exist' );
2540
2541
		// If option exists, just return it.
2542
		if ( 'does_not_exist' !== $value ) {
2543
			return $value;
2544
		}
2545
2546
		// Only check a remote option if Jetpack is connected.
2547
		if ( ! Jetpack::is_active() ) {
2548
			return false;
2549
		}
2550
2551
		// Do what is necessary for each module.
2552
		switch ( $module ) {
2553
			case 'monitor':
2554
				// Load the class to use the method. If class can't be found, do nothing.
2555
				if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2556
					return false;
2557
				}
2558
				$value = Jetpack_Monitor::user_receives_notifications( false );
2559
				break;
2560
2561
			case 'post-by-email':
2562
				// Load the class to use the method. If class can't be found, do nothing.
2563
				if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) {
2564
					return false;
2565
				}
2566
				$post_by_email = new Jetpack_Post_By_Email();
2567
				$value = $post_by_email->get_post_by_email_address();
2568
				if ( $value === null ) {
2569
					$value = 'NULL'; // sentinel value so it actually gets set
2570
				}
2571
				break;
2572
		}
2573
2574
		// Normalize value to boolean.
2575
		if ( is_wp_error( $value ) || is_null( $value ) ) {
2576
			$value = false;
2577
		}
2578
2579
		// Save option to use it next time.
2580
		update_option( $option, $value );
2581
2582
		return $value;
2583
	}
2584
2585
	/**
2586
	 * Get number of plugin updates available.
2587
	 *
2588
	 * @since 4.3.0
2589
	 *
2590
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
2591
	 */
2592
	public static function get_plugin_update_count() {
2593
		$updates = wp_get_update_data();
2594
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
2595
			$count = $updates['counts']['plugins'];
2596
			if ( 0 == $count ) {
2597
				$response = array(
2598
					'code'    => 'success',
2599
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
2600
					'count'   => 0,
2601
				);
2602
			} else {
2603
				$response = array(
2604
					'code'    => 'updates-available',
2605
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
2606
					'count'   => $count,
2607
				);
2608
			}
2609
			return rest_ensure_response( $response );
2610
		}
2611
2612
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
2613
	}
2614
2615
2616
	/**
2617
	 * Returns a list of all plugins in the site.
2618
	 *
2619
	 * @since 4.2.0
2620
	 * @uses get_plugins()
2621
	 *
2622
	 * @return array
2623
	 */
2624
	private static function core_get_plugins() {
2625
		if ( ! function_exists( 'get_plugins' ) ) {
2626
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2627
		}
2628
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2629
		$plugins = apply_filters( 'all_plugins', get_plugins() );
2630
2631
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
2632
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
2633
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
2634
			}
2635
			return $plugins;
2636
		}
2637
2638
		return array();
2639
	}
2640
2641
	/**
2642
	 * Checks if the queried plugin is active.
2643
	 *
2644
	 * @since 4.2.0
2645
	 * @uses is_plugin_active()
2646
	 *
2647
	 * @return bool
2648
	 */
2649
	private static function core_is_plugin_active( $plugin ) {
2650
		if ( ! function_exists( 'is_plugin_active' ) ) {
2651
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2652
		}
2653
2654
		return is_plugin_active( $plugin );
2655
	}
2656
2657
	/**
2658
	 * Get plugins data in site.
2659
	 *
2660
	 * @since 4.2.0
2661
	 *
2662
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
2663
	 */
2664
	public static function get_plugins() {
2665
		$plugins = self::core_get_plugins();
2666
2667
		if ( ! empty( $plugins ) ) {
2668
			return rest_ensure_response( $plugins );
2669
		}
2670
2671
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
2672
	}
2673
2674
	/**
2675
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
2676
	 *
2677
	 * @since 4.2.0
2678
	 *
2679
	 * @param WP_REST_Request $request {
2680
	 *     Array of parameters received by request.
2681
	 *
2682
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2683
	 * }
2684
	 *
2685
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
2686
	 */
2687
	public static function get_plugin( $request ) {
2688
2689
		$plugins = self::core_get_plugins();
2690
2691
		if ( empty( $plugins ) ) {
2692
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
2693
		}
2694
2695
		$plugin = stripslashes( $request['plugin'] );
2696
2697
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
2698
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
2699
		}
2700
2701
		$plugin_data = $plugins[ $plugin ];
2702
2703
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
2704
2705
		return rest_ensure_response( array(
2706
			'code'    => 'success',
2707
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
2708
			'data'    => $plugin_data
2709
		) );
2710
	}
2711
2712
} // class end
2713