Completed
Push — feature/settings-overhaul ( 864988...0dd61e )
by
unknown
18:35 queued 09:57
created

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

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * Register WP REST API endpoints for Jetpack.
4
 *
5
 * @author Automattic
6
 */
7
8
/**
9
 * Disable direct access.
10
 */
11
if ( ! defined( 'ABSPATH' ) ) {
12
	exit;
13
}
14
15
// Load WP_Error for error messages.
16
require_once ABSPATH . '/wp-includes/class-wp-error.php';
17
18
// Register endpoints when WP REST API is initialized.
19
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
20
21
/**
22
 * Class Jetpack_Core_Json_Api_Endpoints
23
 *
24
 * @since 4.3.0
25
 */
26
class Jetpack_Core_Json_Api_Endpoints {
27
28
	/**
29
	 * @var string Generic error message when user is not allowed to perform an action.
30
	 */
31
	public static $user_permissions_error_msg;
32
33
	/**
34
	 * @var array Roles that can access Stats once they're granted access.
35
	 */
36
	public static $stats_roles;
37
38
	/**
39
	 * Declare the Jetpack REST API endpoints.
40
	 *
41
	 * @since 4.3.0
42
	 */
43
	public static function register_endpoints() {
44
45
		// Load API endpoint base classes
46
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
47
48
		// Load API endpoints
49
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
50
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
		// Update any Jetpack module option or setting
206
		register_rest_route( 'jetpack/v4', '/settings', array(
207
			'methods' => WP_REST_Server::EDITABLE,
208
			'callback' => array( $core_api_endpoint, 'process' ),
209
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
210
			'args' => self::get_updateable_parameters( 'any' )
211
		) );
212
213
		// Update a module
214
		register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array(
215
			'methods' => WP_REST_Server::EDITABLE,
216
			'callback' => array( $core_api_endpoint, 'process' ),
217
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
218
			'args' => self::get_updateable_parameters()
219
		) );
220
221
		// Return all module settings
222
		register_rest_route( 'jetpack/v4', '/settings/', array(
223
			'methods' => WP_REST_Server::READABLE,
224
			'callback' => array( $core_api_endpoint, 'process' ),
225
			'permission_callback' => array( $core_api_endpoint, 'can_request' ),
226
		) );
227
228
		// Reset all Jetpack options
229
		register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array(
230
			'methods' => WP_REST_Server::EDITABLE,
231
			'callback' => __CLASS__ . '::reset_jetpack_options',
232
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
233
		) );
234
235
		// Return current Jumpstart status
236
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
237
			'methods'             => WP_REST_Server::READABLE,
238
			'callback'            => __CLASS__ . '::jumpstart_status',
239
			'permission_callback' => __CLASS__ . '::update_settings_permission_check',
240
		) );
241
242
		// Update Jumpstart
243
		register_rest_route( 'jetpack/v4', '/jumpstart', array(
244
			'methods'             => WP_REST_Server::EDITABLE,
245
			'callback'            => __CLASS__ . '::jumpstart_toggle',
246
			'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
247
			'args'                => array(
248
				'active' => array(
249
					'required'          => true,
250
					'validate_callback' => __CLASS__  . '::validate_boolean',
251
				),
252
			),
253
		) );
254
255
		// Updates: get number of plugin updates available
256
		register_rest_route( 'jetpack/v4', '/updates/plugins', array(
257
			'methods' => WP_REST_Server::READABLE,
258
			'callback' => __CLASS__ . '::get_plugin_update_count',
259
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
260
		) );
261
262
		// Dismiss Jetpack Notices
263
		register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array(
264
			'methods' => WP_REST_Server::EDITABLE,
265
			'callback' => __CLASS__ . '::dismiss_notice',
266
			'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
267
		) );
268
269
		// Plugins: get list of all plugins.
270
		register_rest_route( 'jetpack/v4', '/plugins', array(
271
			'methods' => WP_REST_Server::READABLE,
272
			'callback' => __CLASS__ . '::get_plugins',
273
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
274
		) );
275
276
		// Plugins: check if the plugin is active.
277
		register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array(
278
			'methods' => WP_REST_Server::READABLE,
279
			'callback' => __CLASS__ . '::get_plugin',
280
			'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
281
		) );
282
	}
283
284
	/**
285
	 * Handles dismissing of Jetpack Notices
286
	 *
287
	 * @since 4.3.0
288
	 *
289
	 * @param WP_REST_Request $request The request sent to the WP REST API.
290
	 *
291
	 * @return array|wp-error
292
	 */
293
	public static function dismiss_notice( $request ) {
294
		$notice = $request['notice'];
295
296
		if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) {
297
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
298
		}
299
300
		if ( isset( $notice ) && ! empty( $notice ) ) {
301
			switch( $notice ) {
302
				case 'feedback_dash_request':
303
				case 'welcome':
304
					$notices = get_option( 'jetpack_dismissed_notices', array() );
305
					$notices[ $notice ] = true;
306
					update_option( 'jetpack_dismissed_notices', $notices );
307
					return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
308
309
				default:
310
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
311
			}
312
		}
313
314
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
315
	}
316
317
	/**
318
	 * Verify that the user can disconnect the site.
319
	 *
320
	 * @since 4.3.0
321
	 *
322
	 * @return bool|WP_Error True if user is able to disconnect the site.
323
	 */
324
	public static function disconnect_site_permission_callback() {
325
		if ( current_user_can( 'jetpack_disconnect' ) ) {
326
			return true;
327
		}
328
329
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
330
331
	}
332
333
	/**
334
	 * Verify that the user can get a connect/link URL
335
	 *
336
	 * @since 4.3.0
337
	 *
338
	 * @return bool|WP_Error True if user is able to disconnect the site.
339
	 */
340 View Code Duplication
	public static function connect_url_permission_callback() {
341
		if ( current_user_can( 'jetpack_connect_user' ) ) {
342
			return true;
343
		}
344
345
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
346
347
	}
348
349
	/**
350
	 * Verify that a user can get the data about the current user.
351
	 * Only those who can connect.
352
	 *
353
	 * @since 4.3.0
354
	 *
355
	 * @uses Jetpack::is_user_connected();
356
	 *
357
	 * @return bool|WP_Error True if user is able to unlink.
358
	 */
359 View Code Duplication
	public static function get_user_connection_data_permission_callback() {
360
		if ( current_user_can( 'jetpack_connect_user' ) ) {
361
			return true;
362
		}
363
364
		return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
365
	}
366
367
	/**
368
	 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
369
	 *
370
	 * @since 4.3.0
371
	 *
372
	 * @uses Jetpack::is_user_connected();
373
	 *
374
	 * @return bool|WP_Error True if user is able to unlink.
375
	 */
376
	public static function unlink_user_permission_callback() {
377
		if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) {
378
			return true;
379
		}
380
381
		return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
382
	}
383
384
	/**
385
	 * Verify that user can manage Jetpack modules.
386
	 *
387
	 * @since 4.3.0
388
	 *
389
	 * @return bool Whether user has the capability 'jetpack_manage_modules'.
390
	 */
391
	public static function manage_modules_permission_check() {
392
		if ( current_user_can( 'jetpack_manage_modules' ) ) {
393
			return true;
394
		}
395
396
		return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
397
	}
398
399
	/**
400
	 * Verify that user can update Jetpack modules.
401
	 *
402
	 * @since 4.3.0
403
	 *
404
	 * @return bool Whether user has the capability 'jetpack_configure_modules'.
405
	 */
406 View Code Duplication
	public static function configure_modules_permission_check() {
407
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
408
			return true;
409
		}
410
411
		return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
412
	}
413
414
	/**
415
	 * Verify that user can view Jetpack admin page.
416
	 *
417
	 * @since 4.3.0
418
	 *
419
	 * @return bool Whether user has the capability 'jetpack_admin_page'.
420
	 */
421 View Code Duplication
	public static function view_admin_page_permission_check() {
422
		if ( current_user_can( 'jetpack_admin_page' ) ) {
423
			return true;
424
		}
425
426
		return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
427
	}
428
429
	/**
430
	 * Verify that user can mitigate an identity crisis.
431
	 *
432
	 * @since 4.4.0
433
	 *
434
	 * @return bool Whether user has capability 'jetpack_disconnect'.
435
	 */
436
	public static function identity_crisis_mitigation_permission_check() {
437
		if ( current_user_can( 'jetpack_disconnect' ) ) {
438
			return true;
439
		}
440
441
		return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
442
	}
443
444
	/**
445
	 * Verify that user can update Jetpack general settings.
446
	 *
447
	 * @since 4.3.0
448
	 *
449
	 * @return bool Whether user has the capability 'update_settings_permission_check'.
450
	 */
451 View Code Duplication
	public static function update_settings_permission_check() {
452
		if ( current_user_can( 'jetpack_configure_modules' ) ) {
453
			return true;
454
		}
455
456
		return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
457
	}
458
459
	/**
460
	 * Verify that user can view Jetpack admin page and can activate plugins.
461
	 *
462
	 * @since 4.3.0
463
	 *
464
	 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
465
	 */
466 View Code Duplication
	public static function activate_plugins_permission_check() {
467
		if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
468
			return true;
469
		}
470
471
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
472
	}
473
474
	/**
475
	 * Contextual HTTP error code for authorization failure.
476
	 *
477
	 * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
478
	 * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
479
	 *
480
	 * @since 4.3.0
481
	 *
482
	 * @return int
483
	 */
484
	public static function rest_authorization_required_code() {
485
		return is_user_logged_in() ? 403 : 401;
486
	}
487
488
	/**
489
	 * Get connection status for this Jetpack site.
490
	 *
491
	 * @since 4.3.0
492
	 *
493
	 * @return bool True if site is connected
494
	 */
495
	public static function jetpack_connection_status() {
496
		return rest_ensure_response( array(
497
				'isActive'  => Jetpack::is_active(),
498
				'isStaging' => Jetpack::is_staging_site(),
499
				'devMode'   => array(
500
					'isActive' => Jetpack::is_development_mode(),
501
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
502
					'url'      => site_url() && false === strpos( site_url(), '.' ),
503
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
504
				),
505
			)
506
		);
507
	}
508
509
	/**
510
	 * Disconnects Jetpack from the WordPress.com Servers
511
	 *
512
	 * @uses Jetpack::disconnect();
513
	 * @since 4.3.0
514
	 *
515
	 * @param WP_REST_Request $request The request sent to the WP REST API.
516
	 *
517
	 * @return bool|WP_Error True if Jetpack successfully disconnected.
518
	 */
519 View Code Duplication
	public static function disconnect_site( $request ) {
520
521
		if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) {
522
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
523
		}
524
525
		if ( Jetpack::is_active() ) {
526
			Jetpack::disconnect();
527
			return rest_ensure_response( array( 'code' => 'success' ) );
528
		}
529
530
		return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
531
	}
532
533
	/**
534
	 * Gets a new connect raw URL with fresh nonce.
535
	 *
536
	 * @uses Jetpack::disconnect();
537
	 * @since 4.3.0
538
	 *
539
	 * @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...
540
	 *
541
	 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
542
	 */
543
	public static function build_connect_url() {
544
		$url = Jetpack::init()->build_connect_url( true, false, false );
545
		if ( $url ) {
546
			return rest_ensure_response( $url );
547
		}
548
549
		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 ) );
550
	}
551
552
	/**
553
	 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
554
	 * Information about the master/primary user.
555
	 * Information about the current user.
556
	 *
557
	 * @since 4.3.0
558
	 *
559
	 * @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...
560
	 *
561
	 * @return object
562
	 */
563
	public static function get_user_connection_data() {
564
		require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' );
565
566
		$response = array(
567
//			'othersLinked' => Jetpack::get_other_linked_admins(),
568
			'currentUser'  => jetpack_current_user_data(),
569
		);
570
		return rest_ensure_response( $response );
571
	}
572
573
	/**
574
	 * Returns the proper name for Jetpack Holiday Snow setting.
575
	 * When the REST route starts, the holiday-snow.php file where jetpack_holiday_snow_option_name() function is defined is not loaded,
576
	 * so where using this to replicate it and have the same functionality.
577
	 *
578
	 * @since 4.4.0
579
	 *
580
	 * @return string
581
	 */
582
	public static function holiday_snow_option_name() {
583
		/** This filter is documented in modules/holiday-snow.php */
584
		return apply_filters( 'jetpack_holiday_snow_option_name', 'jetpack_holiday_snow_enabled' );
585
	}
586
587
	/**
588
	 * Update a single miscellaneous setting for this Jetpack installation, like Holiday Snow.
589
	 *
590
	 * @since 4.3.0
591
	 *
592
	 * @param WP_REST_Request $request The request sent to the WP REST API.
593
	 *
594
	 * @return object Jetpack miscellaneous settings.
595
	 */
596
	public static function update_setting( $request ) {
597
		// Get parameters to update the module.
598
		$param = $request->get_params();
599
600
		// Exit if no parameters were passed.
601 View Code Duplication
		if ( ! is_array( $param ) ) {
602
			return new WP_Error( 'missing_setting', esc_html__( 'Missing setting.', 'jetpack' ), array( 'status' => 404 ) );
603
		}
604
605
		// Get option name and value.
606
		$option = key( $param );
607
		$value  = current( $param );
608
609
		// Log success or not
610
		$updated = false;
611
612
		switch ( $option ) {
613
			case self::holiday_snow_option_name():
614
				$updated = update_option( $option, ( true == (bool) $value ) ? 'letitsnow' : '' );
615
				break;
616
		}
617
618
		if ( $updated ) {
619
			return rest_ensure_response( array(
620
				'code' 	  => 'success',
621
				'message' => esc_html__( 'Setting updated.', 'jetpack' ),
622
				'value'   => $value,
623
			) );
624
		}
625
626
		return new WP_Error( 'setting_not_updated', esc_html__( 'The setting was not updated.', 'jetpack' ), array( 'status' => 400 ) );
627
	}
628
629
	/**
630
	 * Unlinks current user from the WordPress.com Servers.
631
	 *
632
	 * @since 4.3.0
633
	 * @uses  Jetpack::unlink_user
634
	 *
635
	 * @param WP_REST_Request $request The request sent to the WP REST API.
636
	 *
637
	 * @return bool|WP_Error True if user successfully unlinked.
638
	 */
639 View Code Duplication
	public static function unlink_user( $request ) {
640
641
		if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) {
642
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
643
		}
644
645
		if ( Jetpack::unlink_user() ) {
646
			return rest_ensure_response(
647
				array(
648
					'code' => 'success'
649
				)
650
			);
651
		}
652
653
		return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user.  Please try again.', 'jetpack' ), array( 'status' => 400 ) );
654
	}
655
656
	/**
657
	 * Get site data, including for example, the site's current plan.
658
	 *
659
	 * @since 4.3.0
660
	 *
661
	 * @return array Array of Jetpack modules.
662
	 */
663
	public static function get_site_data() {
664
665
		if ( $site_id = Jetpack_Options::get_option( 'id' ) ) {
666
			$response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ), '1.1' );
667
668
			if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
669
				return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
670
			}
671
672
			// Save plan details in the database for future use without API calls
673
			$results = json_decode( $response['body'], true );
674
675
			if ( is_array( $results ) && isset( $results['plan'] ) ) {
676
				update_option( 'jetpack_active_plan', $results['plan'] );
677
			}
678
679
			return rest_ensure_response( array(
680
					'code' => 'success',
681
					'message' => esc_html__( 'Site data correctly received.', 'jetpack' ),
682
					'data' => wp_remote_retrieve_body( $response ),
683
				)
684
			);
685
		}
686
687
		return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
688
	}
689
690
	/**
691
	 * Handles identity crisis mitigation, confirming safe mode for this site.
692
	 *
693
	 * @since 4.4.0
694
	 *
695
	 * @return bool | WP_Error True if option is properly set.
696
	 */
697
	public static function confirm_safe_mode() {
698
		$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
699
		if ( $updated ) {
700
			return rest_ensure_response(
701
				array(
702
					'code' => 'success'
703
				)
704
			);
705
		}
706
		return new WP_Error(
707
			'error_setting_jetpack_safe_mode',
708
			esc_html__( 'Could not confirm safe mode.', 'jetpack' ),
709
			array( 'status' => 500 )
710
		);
711
	}
712
713
	/**
714
	 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
715
	 *
716
	 * @since 4.4.0
717
	 *
718
	 * @return bool | WP_Error True if option is properly set.
719
	 */
720
	public static function migrate_stats_and_subscribers() {
721
		if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
722
			return new WP_Error(
723
				'error_deleting_sync_error_idc',
724
				esc_html__( 'Could not delete sync error option.', 'jetpack' ),
725
				array( 'status' => 500 )
726
			);
727
		}
728
729
		if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
730
			return rest_ensure_response(
731
				array(
732
					'code' => 'success'
733
				)
734
			);
735
		}
736
		return new WP_Error(
737
			'error_setting_jetpack_migrate',
738
			esc_html__( 'Could not confirm migration.', 'jetpack' ),
739
			array( 'status' => 500 )
740
		);
741
	}
742
743
	/**
744
	 * This IDC resolution will disconnect the site and re-connect to a completely new
745
	 * and separate shadow site than the original.
746
	 *
747
	 * It will first will disconnect the site without phoning home as to not disturb the production site.
748
	 * It then builds a fresh connection URL and sends it back along with the response.
749
	 *
750
	 * @since 4.4.0
751
	 * @return bool|WP_Error
752
	 */
753
	public static function start_fresh_connection() {
754
		// First clear the options / disconnect.
755
		Jetpack::disconnect();
756
		return self::build_connect_url();
757
	}
758
759
	/**
760
	 * Reset Jetpack options
761
	 *
762
	 * @since 4.3.0
763
	 *
764
	 * @param WP_REST_Request $request {
765
	 *     Array of parameters received by request.
766
	 *
767
	 *     @type string $options Available options to reset are options|modules
768
	 * }
769
	 *
770
	 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
771
	 */
772
	public static function reset_jetpack_options( $request ) {
773
774
		if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) {
775
			return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
776
		}
777
778
		if ( isset( $request['options'] ) ) {
779
			$data = $request['options'];
780
781
			switch( $data ) {
782
				case ( 'options' ) :
783
					$options_to_reset = Jetpack::get_jetpack_options_for_reset();
784
785
					// Reset the Jetpack options
786
					foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
787
						Jetpack_Options::delete_option( $option_to_reset );
788
					}
789
790
					foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
791
						delete_option( $option_to_reset );
792
					}
793
794
					// Reset to default modules
795
					$default_modules = Jetpack::get_default_modules();
796
					Jetpack::update_active_modules( $default_modules );
797
798
					// Jumpstart option is special
799
					Jetpack_Options::update_option( 'jumpstart', 'new_connection' );
800
					return rest_ensure_response( array(
801
						'code' 	  => 'success',
802
						'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ),
803
					) );
804
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
805
806
				case 'modules':
807
					$default_modules = Jetpack::get_default_modules();
808
					Jetpack::update_active_modules( $default_modules );
809
					return rest_ensure_response( array(
810
						'code' 	  => 'success',
811
						'message' => esc_html__( 'Modules reset to default.', 'jetpack' ),
812
					) );
813
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
814
815
				default:
816
					return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
817
			}
818
		}
819
820
		return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
821
	}
822
823
	/**
824
	 * Retrieves the current status of Jumpstart.
825
	 *
826
	 * @since 4.5.0
827
	 *
828
	 * @return bool
829
	 */
830
	public static function jumpstart_status() {
831
		return array(
832
			'status' => Jetpack_Options::get_option( 'jumpstart' )
833
		);
834
	}
835
836
	/**
837
	 * Toggles activation or deactivation of the JumpStart
838
	 *
839
	 * @since 4.3.0
840
	 *
841
	 * @param WP_REST_Request $request The request sent to the WP REST API.
842
	 *
843
	 * @return bool|WP_Error True if toggling Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
844
	 */
845
	public static function jumpstart_toggle( $request ) {
846
847
		if ( $request[ 'active' ] ) {
848
			return self::jumpstart_activate( $request );
849
		} else {
850
			return self::jumpstart_deactivate( $request );
851
		}
852
	}
853
854
	/**
855
	 * Activates a series of valid Jetpack modules and initializes some options.
856
	 *
857
	 * @since 4.3.0
858
	 *
859
	 * @param WP_REST_Request $request The request sent to the WP REST API.
860
	 *
861
	 * @return bool|WP_Error True if Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error.
862
	 */
863
	public static function jumpstart_activate( $request ) {
0 ignored issues
show
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
864
		$modules = Jetpack::get_available_modules();
865
		$activate_modules = array();
866
		foreach ( $modules as $module ) {
867
			$module_info = Jetpack::get_module( $module );
868
			if ( isset( $module_info['feature'] ) && is_array( $module_info['feature'] ) && in_array( 'Jumpstart', $module_info['feature'] ) ) {
869
				$activate_modules[] = $module;
870
			}
871
		}
872
873
		// Collect success/error messages like modules that are properly activated.
874
		$result = array(
875
			'activated_modules' => array(),
876
			'failed_modules'    => array(),
877
		);
878
879
		// Update the jumpstart option
880
		if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) {
881
			$result['jumpstart_activated'] = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' );
882
		}
883
884
		// Check for possible conflicting plugins
885
		$module_slugs_filtered = Jetpack::init()->filter_default_modules( $activate_modules );
886
887
		foreach ( $module_slugs_filtered as $module_slug ) {
888
			Jetpack::log( 'activate', $module_slug );
889
			if ( Jetpack::activate_module( $module_slug, false, false ) ) {
890
				$result['activated_modules'][] = $module_slug;
891
			} else {
892
				$result['failed_modules'][] = $module_slug;
893
			}
894
		}
895
896
		// Set the default sharing buttons and set to display on posts if none have been set.
897
		$sharing_services = get_option( 'sharing-services' );
898
		$sharing_options  = get_option( 'sharing-options' );
899
		if ( empty( $sharing_services['visible'] ) ) {
900
			// Default buttons to set
901
			$visible = array(
902
				'twitter',
903
				'facebook',
904
				'google-plus-1',
905
			);
906
			$hidden = array();
907
908
			// Set some sharing settings
909
			if ( class_exists( 'Sharing_Service' ) ) {
910
				$sharing = new Sharing_Service();
911
				$sharing_options['global'] = array(
912
					'button_style'  => 'icon',
913
					'sharing_label' => $sharing->default_sharing_label,
914
					'open_links'    => 'same',
915
					'show'          => array( 'post' ),
916
					'custom'        => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array()
917
				);
918
919
				$result['sharing_options']  = update_option( 'sharing-options', $sharing_options );
920
				$result['sharing_services'] = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) );
921
			}
922
		}
923
924
		// If all Jumpstart modules were activated
925 View Code Duplication
		if ( empty( $result['failed_modules'] ) ) {
926
			return rest_ensure_response( array(
927
				'code' 	  => 'success',
928
				'message' => esc_html__( 'Jumpstart done.', 'jetpack' ),
929
				'data'    => $result,
930
			) );
931
		}
932
933
		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 ) );
934
	}
935
936
	/**
937
	 * Dismisses Jumpstart so user is not prompted to go through it again.
938
	 *
939
	 * @since 4.3.0
940
	 *
941
	 * @param WP_REST_Request $request The request sent to the WP REST API.
942
	 *
943
	 * @return bool|WP_Error True if Jumpstart was disabled or was nothing to dismiss. Otherwise, a WP_Error instance with a message.
944
	 */
945
	public static function jumpstart_deactivate( $request ) {
0 ignored issues
show
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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

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

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

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

Loading history...
2195
2196
				case 'integer':
2197
					return (int) $value;
2198
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
2199
2200
				case 'float':
2201
					return (float) $value;
2202
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
2203
2204
				case 'string':
2205
					return (string) $value;
2206
					break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
2207
			}
2208
		}
2209
		return $value;
2210
	}
2211
2212
	/**
2213
	 * Get a value not saved locally.
2214
	 *
2215
	 * @since 4.3.0
2216
	 *
2217
	 * @param string $module Module slug.
2218
	 * @param string $option Option name.
2219
	 *
2220
	 * @return bool Whether user is receiving notifications or not.
2221
	 */
2222
	public static function get_remote_value( $module, $option ) {
2223
2224
		if ( in_array( $module, array( 'post-by-email' ), true ) ) {
2225
			$option .= get_current_user_id();
2226
		}
2227
2228
		// If option doesn't exist, 'does_not_exist' will be returned.
2229
		$value = get_option( $option, 'does_not_exist' );
2230
2231
		// If option exists, just return it.
2232
		if ( 'does_not_exist' !== $value ) {
2233
			return $value;
2234
		}
2235
2236
		// Only check a remote option if Jetpack is connected.
2237
		if ( ! Jetpack::is_active() ) {
2238
			return false;
2239
		}
2240
2241
		// If the module is inactive, load the class to use the method.
2242
		if ( ! did_action( 'jetpack_module_loaded_' . $module ) ) {
2243
			// Class can't be found so do nothing.
2244
			if ( ! @include( Jetpack::get_module_path( $module ) ) ) {
2245
				return false;
2246
			}
2247
		}
2248
2249
		// Do what is necessary for each module.
2250
		switch ( $module ) {
2251
			case 'monitor':
2252
				$monitor = new Jetpack_Monitor();
2253
				$value = $monitor->user_receives_notifications( false );
2254
				break;
2255
2256
			case 'post-by-email':
2257
				$post_by_email = new Jetpack_Post_By_Email();
2258
				$value = $post_by_email->get_post_by_email_address();
2259
				if ( $value === null ) {
2260
					$value = 'NULL'; // sentinel value so it actually gets set
2261
				}
2262
				break;
2263
		}
2264
2265
		// Normalize value to boolean.
2266
		if ( is_wp_error( $value ) || is_null( $value ) ) {
2267
			$value = false;
2268
		}
2269
2270
		// Save option to use it next time.
2271
		update_option( $option, $value );
2272
2273
		return $value;
2274
	}
2275
2276
	/**
2277
	 * Get number of plugin updates available.
2278
	 *
2279
	 * @since 4.3.0
2280
	 *
2281
	 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
2282
	 */
2283
	public static function get_plugin_update_count() {
2284
		$updates = wp_get_update_data();
2285
		if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
2286
			$count = $updates['counts']['plugins'];
2287
			if ( 0 == $count ) {
2288
				$response = array(
2289
					'code'    => 'success',
2290
					'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
2291
					'count'   => 0,
2292
				);
2293
			} else {
2294
				$response = array(
2295
					'code'    => 'updates-available',
2296
					'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ),
2297
					'count'   => $count,
2298
				);
2299
			}
2300
			return rest_ensure_response( $response );
2301
		}
2302
2303
		return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
2304
	}
2305
2306
2307
	/**
2308
	 * Returns a list of all plugins in the site.
2309
	 *
2310
	 * @since 4.2.0
2311
	 * @uses get_plugins()
2312
	 *
2313
	 * @return array
2314
	 */
2315
	private static function core_get_plugins() {
2316
		if ( ! function_exists( 'get_plugins' ) ) {
2317
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2318
		}
2319
		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
2320
		$plugins = apply_filters( 'all_plugins', get_plugins() );
2321
2322
		if ( is_array( $plugins ) && ! empty( $plugins ) ) {
2323
			foreach ( $plugins as $plugin_slug => $plugin_data ) {
2324
				$plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug );
2325
			}
2326
			return $plugins;
2327
		}
2328
2329
		return array();
2330
	}
2331
2332
	/**
2333
	 * Checks if the queried plugin is active.
2334
	 *
2335
	 * @since 4.2.0
2336
	 * @uses is_plugin_active()
2337
	 *
2338
	 * @return bool
2339
	 */
2340
	private static function core_is_plugin_active( $plugin ) {
2341
		if ( ! function_exists( 'get_plugins' ) ) {
2342
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
2343
		}
2344
2345
		return is_plugin_active( $plugin );
2346
	}
2347
2348
	/**
2349
	 * Get plugins data in site.
2350
	 *
2351
	 * @since 4.2.0
2352
	 *
2353
	 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
2354
	 */
2355
	public static function get_plugins() {
2356
		$plugins = self::core_get_plugins();
2357
2358
		if ( ! empty( $plugins ) ) {
2359
			return rest_ensure_response( $plugins );
2360
		}
2361
2362
		return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
2363
	}
2364
2365
	/**
2366
	 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
2367
	 *
2368
	 * @since 4.2.0
2369
	 *
2370
	 * @param WP_REST_Request $request {
2371
	 *     Array of parameters received by request.
2372
	 *
2373
	 *     @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
2374
	 * }
2375
	 *
2376
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
2377
	 */
2378
	public static function get_plugin( $request ) {
2379
2380
		$plugins = self::core_get_plugins();
2381
2382
		if ( empty( $plugins ) ) {
2383
			return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
2384
		}
2385
2386
		$plugin = stripslashes( $request['plugin'] );
2387
2388
		if ( ! in_array( $plugin, array_keys( $plugins ) ) ) {
2389
			return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) );
2390
		}
2391
2392
		$plugin_data = $plugins[ $plugin ];
2393
2394
		$plugin_data['active'] = self::core_is_plugin_active( $plugin );
2395
2396
		return rest_ensure_response( array(
2397
			'code'    => 'success',
2398
			'message' => esc_html__( 'Plugin found.', 'jetpack' ),
2399
			'data'    => $plugin_data
2400
		) );
2401
	}
2402
2403
} // class end
2404