Completed
Push — fix/7357 ( 13a0c4...764bff )
by
unknown
11:52
created

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

Upgrade to new PHP Analysis Engine

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

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

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

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

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

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

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

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