Completed
Push — add/plugin_install ( 13506f )
by
unknown
12:20
created

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

Labels
Severity

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * Register WP REST API endpoints for Jetpack.
4
 *
5
 * @author Automattic
6
 */
7
8
/**
9
 * Disable direct access.
10
 */
11
if ( ! defined( 'ABSPATH' ) ) {
12
	exit;
13
}
14
15
// Load WP_Error for error messages.
16
require_once ABSPATH . '/wp-includes/class-wp-error.php';
17
18
// Register endpoints when WP REST API is initialized.
19
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
20
21
/**
22
 * Class Jetpack_Core_Json_Api_Endpoints
23
 *
24
 * @since 4.3.0
25
 */
26
class Jetpack_Core_Json_Api_Endpoints {
27
28
	/**
29
	 * @var string Generic error message when user is not allowed to perform an action.
30
	 */
31
	public static $user_permissions_error_msg;
32
33
	/**
34
	 * @var array Roles that can access Stats once they're granted access.
35
	 */
36
	public static $stats_roles;
37
38
	/**
39
	 * Declare the Jetpack REST API endpoints.
40
	 *
41
	 * @since 4.3.0
42
	 */
43
	public static function register_endpoints() {
44
45
		// Load API endpoint base classes
46
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
47
48
		// Load API endpoints
49
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
50
51
		self::$user_permissions_error_msg = esc_html__(
52
			'You do not have the correct user permissions to perform this action.
53
			Please contact your site admin if you think this is a mistake.',
54
			'jetpack'
55
		);
56
57
		self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
58
59
		Jetpack::load_xml_rpc_client();
60
		$ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
61
		$core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
62
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
63
		$module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
64
		$module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
65
66
		// Get current connection status of Jetpack
67
		register_rest_route( 'jetpack/v4', '/connection', array(
68
			'methods' => WP_REST_Server::READABLE,
69
			'callback' => __CLASS__ . '::jetpack_connection_status',
70
		) );
71
72
		// Fetches a fresh connect URL
73
		register_rest_route( 'jetpack/v4', '/connection/url', array(
74
			'methods' => WP_REST_Server::READABLE,
75
			'callback' => __CLASS__ . '::build_connect_url',
76
			'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
77
		) );
78
79
		// Get current user connection data
80
		register_rest_route( 'jetpack/v4', '/connection/data', array(
81
			'methods' => WP_REST_Server::READABLE,
82
			'callback' => __CLASS__ . '::get_user_connection_data',
83
			'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback',
84
		) );
85
86
		// Disconnect site from WordPress.com servers
87
		register_rest_route( 'jetpack/v4', '/connection', array(
88
			'methods' => WP_REST_Server::EDITABLE,
89
			'callback' => __CLASS__ . '::disconnect_site',
90
			'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
91
		) );
92
93
		// Disconnect/unlink user from WordPress.com servers
94
		register_rest_route( 'jetpack/v4', '/connection/user', array(
95
			'methods' => WP_REST_Server::EDITABLE,
96
			'callback' => __CLASS__ . '::unlink_user',
97
			'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
98
		) );
99
100
		// Get current site data
101
		register_rest_route( 'jetpack/v4', '/site', array(
102
			'methods' => WP_REST_Server::READABLE,
103
			'callback' => __CLASS__ . '::get_site_data',
104
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
105
		) );
106
107
		// Confirm that a site in identity crisis should be in staging mode
108
		register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
109
			'methods' => WP_REST_Server::EDITABLE,
110
			'callback' => __CLASS__ . '::confirm_safe_mode',
111
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
112
		) );
113
114
		// IDC resolve: create an entirely new shadow site for this URL.
115
		register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array(
116
			'methods' => WP_REST_Server::EDITABLE,
117
			'callback' => __CLASS__ . '::start_fresh_connection',
118
			'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
119
		) );
120
121
		// Handles the request to migrate stats and subscribers during an identity crisis.
122
		register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array(
123
			'methods' => WP_REST_Server::EDITABLE,
124
			'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
125
			'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
126
		) );
127
128
		// Return all modules
129
		register_rest_route( 'jetpack/v4', '/module/all', array(
130
			'methods' => WP_REST_Server::READABLE,
131
			'callback' => array( $module_list_endpoint, 'process' ),
132
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
133
		) );
134
135
		// Activate many modules
136
		register_rest_route( 'jetpack/v4', '/module/all/active', array(
137
			'methods' => WP_REST_Server::EDITABLE,
138
			'callback' => array( $module_list_endpoint, 'process' ),
139
			'permission_callback' => array( $module_list_endpoint, 'can_request' ),
140
			'args' => array(
141
				'modules' => array(
142
					'default'           => '',
143
					'type'              => 'array',
144
					'items'             => array(
145
						'type'          => 'string',
146
					),
147
					'required'          => true,
148
					'validate_callback' => __CLASS__ . '::validate_module_list',
149
				),
150
				'active' => array(
151
					'default'           => true,
152
					'type'              => 'boolean',
153
					'required'          => false,
154
					'validate_callback' => __CLASS__ . '::validate_boolean',
155
				),
156
			)
157
		) );
158
159
		// Return a single module and update it when needed
160
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
161
			'methods' => WP_REST_Server::READABLE,
162
			'callback' => array( $core_api_endpoint, 'process' ),
163
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
164
		) );
165
166
		// Activate and deactivate a module
167
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array(
168
			'methods' => WP_REST_Server::EDITABLE,
169
			'callback' => array( $module_toggle_endpoint, 'process' ),
170
			'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
171
			'args' => array(
172
				'active' => array(
173
					'default'           => true,
174
					'type'              => 'boolean',
175
					'required'          => true,
176
					'validate_callback' => __CLASS__ . '::validate_boolean',
177
				),
178
			)
179
		) );
180
181
		// Update a module
182
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array(
183
			'methods' => WP_REST_Server::EDITABLE,
184
			'callback' => array( $core_api_endpoint, 'process' ),
185
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
186
			'args' => self::get_updateable_parameters( 'any' )
187
		) );
188
189
		// Get data for a specific module, i.e. Protect block count, WPCOM stats,
190
		// Akismet spam count, etc.
191
		register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array(
192
			'methods' => WP_REST_Server::READABLE,
193
			'callback' => array( $module_data_endpoint, 'process' ),
194
			'permission_callback' => array( $module_data_endpoint, 'can_request' ),
195
			'args' => array(
196
				'range' => array(
197
					'default'           => 'day',
198
					'type'              => 'string',
199
					'required'          => false,
200
					'validate_callback' => __CLASS__ . '::validate_string',
201
				),
202
			)
203
		) );
204
205
		// Check if the API key for a specific service is valid or not
206
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
207
			'methods' => WP_REST_Server::READABLE,
208
			'callback' => array( $module_data_endpoint, 'key_check' ),
209
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
210
			'sanitize_callback' => 'sanitize_text_field',
211
		) );
212
213
		register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array(
214
			'methods' => WP_REST_Server::EDITABLE,
215
			'callback' => array( $module_data_endpoint, 'key_check' ),
216
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
217
			'sanitize_callback' => 'sanitize_text_field',
218
			'args' => array(
219
				'api_key' => array(
220
					'default'           => '',
221
					'type'              => 'string',
222
					'validate_callback' => __CLASS__ . '::validate_alphanum',
223
				),
224
			)
225
		) );
226
227
		// Update any Jetpack module option or setting
228
		register_rest_route( 'jetpack/v4', '/settings', array(
229
			'methods' => WP_REST_Server::EDITABLE,
230
			'callback' => array( $core_api_endpoint, 'process' ),
231
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
232
			'args' => self::get_updateable_parameters( 'any' )
233
		) );
234
235
		// Update a module
236
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
237
			'methods' => WP_REST_Server::EDITABLE,
238
			'callback' => array( $core_api_endpoint, 'process' ),
239
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
240
			'args' => self::get_updateable_parameters()
241
		) );
242
243
		// Return all module settings
244
		register_rest_route( 'jetpack/v4', '/settings/', array(
245
			'methods' => WP_REST_Server::READABLE,
246
			'callback' => array( $core_api_endpoint, 'process' ),
247
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
248
		) );
249
250
		// Reset all Jetpack options
251
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
252
			'methods' => WP_REST_Server::EDITABLE,
253
			'callback' => __CLASS__ . '::reset_jetpack_options',
254
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
255
		) );
256
257
		// Return current Jumpstart status
258
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
259
			'methods'             => WP_REST_Server::READABLE,
260
			'callback'            => __CLASS__ . '::jumpstart_status',
261
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
262
		) );
263
264
		// Update Jumpstart
265
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
266
			'methods'             => WP_REST_Server::EDITABLE,
267
			'callback'            => __CLASS__ . '::jumpstart_toggle',
268
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
269
			'args'                => array(
270
				'active' => array(
271
					'required'          => true,
272
					'validate_callback' => __CLASS__  . '::validate_boolean',
273
				),
274
			),
275
		) );
276
277
		// Updates: get number of plugin updates available
278
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
279
			'methods' => WP_REST_Server::READABLE,
280
			'callback' => __CLASS__ . '::get_plugin_update_count',
281
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
282
		) );
283
284
		// Dismiss Jetpack Notices
285
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
286
			'methods' => WP_REST_Server::EDITABLE,
287
			'callback' => __CLASS__ . '::dismiss_notice',
288
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
289
		) );
290
291
		// Plugins: get list of all plugins.
292
		register_rest_route( 'jetpack/v4', '/plugins', array(
293
			'methods' => WP_REST_Server::READABLE,
294
			'callback' => __CLASS__ . '::get_plugins',
295
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
296
		) );
297
298
		// Plugins: check if the plugin is active.
299
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
300
			'methods' => WP_REST_Server::READABLE,
301
			'callback' => __CLASS__ . '::get_plugin',
302
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
303
		) );
304
	}
305
306
	/**
307
	 * Handles dismissing of Jetpack Notices
308
	 *
309
	 * @since 4.3.0
310
	 *
311
	 * @param WP_REST_Request $request The request sent to the WP REST API.
312
	 *
313
	 * @return array|wp-error
314
	 */
315
	public static function dismiss_notice( $request ) {
316
		$notice = $request['notice'];
317
318
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
319
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
320
		}
321
322
		if ( isset( $notice ) && ! empty( $notice ) ) {
323
			switch( $notice ) {
324
				case 'feedback_dash_request':
325
				case 'welcome':
326
					$notices = get_option( 'jetpack_dismissed_notices', array() );
327
					$notices[ $notice ] = true;
328
					update_option( 'jetpack_dismissed_notices', $notices );
329
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
330
331
				default:
332
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
333
			}
334
		}
335
336
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
337
	}
338
339
	/**
340
	 * Verify that the user can disconnect the site.
341
	 *
342
	 * @since 4.3.0
343
	 *
344
	 * @return bool|WP_Error True if user is able to disconnect the site.
345
	 */
346
	public static function disconnect_site_permission_callback() {
347
		if ( current_user_can( 'jetpack_disconnect' ) ) {
348
			return true;
349
		}
350
351
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
352
353
	}
354
355
	/**
356
	 * Verify that the user can get a connect/link URL
357
	 *
358
	 * @since 4.3.0
359
	 *
360
	 * @return bool|WP_Error True if user is able to disconnect the site.
361
	 */
362 View Code Duplication
	public static function connect_url_permission_callback() {
363
		if ( current_user_can( 'jetpack_connect_user' ) ) {
364
			return true;
365
		}
366
367
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
368
369
	}
370
371
	/**
372
	 * Verify that a user can get the data about the current user.
373
	 * Only those who can connect.
374
	 *
375
	 * @since 4.3.0
376
	 *
377
	 * @uses Jetpack::is_user_connected();
378
	 *
379
	 * @return bool|WP_Error True if user is able to unlink.
380
	 */
381 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
382
		if ( current_user_can( 'jetpack_connect_user' ) ) {
383
			return true;
384
		}
385
386
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
387
	}
388
389
	/**
390
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
391
	 *
392
	 * @since 4.3.0
393
	 *
394
	 * @uses Jetpack::is_user_connected();
395
	 *
396
	 * @return bool|WP_Error True if user is able to unlink.
397
	 */
398
	public static function unlink_user_permission_callback() {
399
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
400
			return true;
401
		}
402
403
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
404
	}
405
406
	/**
407
	 * Verify that user can manage Jetpack modules.
408
	 *
409
	 * @since 4.3.0
410
	 *
411
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
412
	 */
413
	public static function manage_modules_permission_check() {
414
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
415
			return true;
416
		}
417
418
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
419
	}
420
421
	/**
422
	 * Verify that user can update Jetpack modules.
423
	 *
424
	 * @since 4.3.0
425
	 *
426
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
427
	 */
428 View Code Duplication
	public static function configure_modules_permission_check() {
429
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
430
			return true;
431
		}
432
433
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
434
	}
435
436
	/**
437
	 * Verify that user can view Jetpack admin page.
438
	 *
439
	 * @since 4.3.0
440
	 *
441
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
442
	 */
443 View Code Duplication
	public static function view_admin_page_permission_check() {
444
		if ( current_user_can( 'jetpack_admin_page' ) ) {
445
			return true;
446
		}
447
448
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
449
	}
450
451
	/**
452
	 * Verify that user can mitigate an identity crisis.
453
	 *
454
	 * @since 4.4.0
455
	 *
456
	 * @return bool Whether user has capability 'jetpack_disconnect'.
457
	 */
458
	public static function identity_crisis_mitigation_permission_check() {
459
		if ( current_user_can( 'jetpack_disconnect' ) ) {
460
			return true;
461
		}
462
463
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
464
	}
465
466
	/**
467
	 * Verify that user can update Jetpack general settings.
468
	 *
469
	 * @since 4.3.0
470
	 *
471
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
472
	 */
473 View Code Duplication
	public static function update_settings_permission_check() {
474
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
475
			return true;
476
		}
477
478
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
479
	}
480
481
	/**
482
	 * Verify that user can view Jetpack admin page and can activate plugins.
483
	 *
484
	 * @since 4.3.0
485
	 *
486
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
487
	 */
488 View Code Duplication
	public static function activate_plugins_permission_check() {
489
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
490
			return true;
491
		}
492
493
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
494
	}
495
496
	/**
497
	 * Contextual HTTP error code for authorization failure.
498
	 *
499
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
500
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
501
	 *
502
	 * @since 4.3.0
503
	 *
504
	 * @return int
505
	 */
506
	public static function rest_authorization_required_code() {
507
		return is_user_logged_in() ? 403 : 401;
508
	}
509
510
	/**
511
	 * Get connection status for this Jetpack site.
512
	 *
513
	 * @since 4.3.0
514
	 *
515
	 * @return bool True if site is connected
516
	 */
517
	public static function jetpack_connection_status() {
518
		return rest_ensure_response( array(
519
				'isActive'  => Jetpack::is_active(),
520
				'isStaging' => Jetpack::is_staging_site(),
521
				'devMode'   => array(
522
					'isActive' => Jetpack::is_development_mode(),
523
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
524
					'url'      => site_url() && false === strpos( site_url(), '.' ),
525
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
526
				),
527
			)
528
		);
529
	}
530
531
	/**
532
	 * Disconnects Jetpack from the WordPress.com Servers
533
	 *
534
	 * @uses Jetpack::disconnect();
535
	 * @since 4.3.0
536
	 *
537
	 * @param WP_REST_Request $request The request sent to the WP REST API.
538
	 *
539
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
540
	 */
541 View Code Duplication
	public static function disconnect_site( $request ) {
542
543
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
544
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
545
		}
546
547
		if ( Jetpack::is_active() ) {
548
			Jetpack::disconnect();
549
			return rest_ensure_response( array( 'code' => 'success' ) );
550
		}
551
552
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
553
	}
554
555
	/**
556
	 * Gets a new connect raw URL with fresh nonce.
557
	 *
558
	 * @uses Jetpack::disconnect();
559
	 * @since 4.3.0
560
	 *
561
	 * @param WP_REST_Request $request The request sent to the WP REST API.
0 ignored issues
show
There is no parameter named $request. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
562
	 *
563
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
564
	 */
565
	public static function build_connect_url() {
566
		$url = Jetpack::init()->build_connect_url( true, false, false );
567
		if ( $url ) {
568
			return rest_ensure_response( $url );
569
		}
570
571
		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 ) );
572
	}
573
574
	/**
575
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
576
	 * Information about the master/primary user.
577
	 * Information about the current user.
578
	 *
579
	 * @since 4.3.0
580
	 *
581
	 * @param WP_REST_Request $request The request sent to the WP REST API.
0 ignored issues
show
There is no parameter named $request. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

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