PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / _inc / lib / class.core-rest-api-endpoints.php

class.core-rest-api-endpoints.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.5, at _inc/lib/class.core-rest-api-endpoints.php

4,036 lines 135.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Register WP REST API endpoints for Jetpack.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Connection\Client;
9 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
10 use Automattic\Jetpack\Connection\Rest_Authentication;
11 use Automattic\Jetpack\Connection\REST_Connector;
12 use Automattic\Jetpack\Connection\REST_Jetpack_AI_JWT;
13 use Automattic\Jetpack\Connection\SSO;
14 use Automattic\Jetpack\Jetpack_CRM_Data;
15 use Automattic\Jetpack\Plugins_Installer;
16 use Automattic\Jetpack\Stats\Options as Stats_Options;
17 use Automattic\Jetpack\Status\Host;
18 use Automattic\Jetpack\Status\Visitor;
19 use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection_Shared_Functions;
20 use Automattic\Jetpack\Waf\Waf_Compatibility;
21
22 // Disable direct access.
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit( 0 );
25 }
26
27 // Load WP_Error for error messages.
28 require_once ABSPATH . '/wp-includes/class-wp-error.php';
29
30 // Register endpoints when WP REST API is initialized.
31 add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
32 // Load API endpoints that are synced with WP.com
33 // Each of these is a class that will register its own routes on 'rest_api_init'.
34 require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
35
36 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
37
38 /**
39 * Class Jetpack_Core_Json_Api_Endpoints
40 *
41 * @since 4.3.0
42 */
43 class Jetpack_Core_Json_Api_Endpoints {
44 /**
45 * Roles that can access Stats once they're granted access.
46 *
47 * @var array
48 */
49 public static $stats_roles;
50
51 /**
52 * Declare the Jetpack REST API endpoints.
53 *
54 * @since 4.3.0
55 */
56 public static function register_endpoints() {
57
58 // Load API endpoint base classes.
59 require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
60
61 // Load API endpoints.
62 require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
63 require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php';
64 require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php';
65
66 self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
67
68 $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
69 $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client );
70 $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
71 $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint();
72 $module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
73 $site_endpoint = new Jetpack_Core_API_Site_Endpoint();
74 $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint();
75
76 // My Jetpack and Agents Manager register the same controller; its guard keeps the route registered once.
77 ( new REST_Jetpack_AI_JWT() )->register_rest_route();
78
79 register_rest_route(
80 'jetpack/v4',
81 'plans',
82 array(
83 'methods' => WP_REST_Server::READABLE,
84 'callback' => __CLASS__ . '::get_plans',
85 'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
86 )
87 );
88
89 register_rest_route(
90 'jetpack/v4',
91 'products',
92 array(
93 'methods' => WP_REST_Server::READABLE,
94 'callback' => __CLASS__ . '::get_products',
95 'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
96 )
97 );
98
99 register_rest_route(
100 'jetpack/v4',
101 'marketing/survey',
102 array(
103 'methods' => WP_REST_Server::CREATABLE,
104 'callback' => __CLASS__ . '::submit_survey',
105 'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback',
106 )
107 );
108
109 register_rest_route(
110 'jetpack/v4',
111 '/rewind',
112 array(
113 'methods' => WP_REST_Server::READABLE,
114 'callback' => __CLASS__ . '::get_rewind_data',
115 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
116 )
117 );
118
119 register_rest_route(
120 'jetpack/v4',
121 '/scan',
122 array(
123 'methods' => WP_REST_Server::READABLE,
124 'callback' => __CLASS__ . '::get_scan_state',
125 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
126 )
127 );
128
129 // Fetches a fresh connect URL.
130 register_rest_route(
131 'jetpack/v4',
132 '/connection/url',
133 array(
134 'methods' => WP_REST_Server::READABLE,
135 'callback' => __CLASS__ . '::build_connect_url',
136 'permission_callback' => __CLASS__ . '::connect_url_permission_callback',
137 'args' => array(
138 'from' => array( 'type' => 'string' ),
139 'redirect' => array( 'type' => 'string' ),
140 ),
141 )
142 );
143
144 // Current user: get or set tracking settings.
145 register_rest_route(
146 'jetpack/v4',
147 '/tracking/settings',
148 array(
149 array(
150 'methods' => WP_REST_Server::READABLE,
151 'callback' => __CLASS__ . '::get_user_tracking_settings',
152 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
153 ),
154 array(
155 'methods' => WP_REST_Server::EDITABLE,
156 'callback' => __CLASS__ . '::update_user_tracking_settings',
157 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
158 'args' => array(
159 'tracks_opt_out' => array( 'type' => 'boolean' ),
160 ),
161 ),
162 )
163 );
164
165 // Get current site features.
166 register_rest_route(
167 'jetpack/v4',
168 '/site/features',
169 array(
170 'methods' => WP_REST_Server::READABLE,
171 'callback' => array( $site_endpoint, 'get_features' ),
172 'permission_callback' => array( $site_endpoint, 'can_request' ),
173 )
174 );
175
176 register_rest_route(
177 'jetpack/v4',
178 '/site/products',
179 array(
180 'methods' => WP_REST_Server::READABLE,
181 'callback' => array( $site_endpoint, 'get_products' ),
182 'permission_callback' => array( $site_endpoint, 'can_request' ),
183 )
184 );
185
186 // Get current site purchases.
187 register_rest_route(
188 'jetpack/v4',
189 '/site/purchases',
190 array(
191 'methods' => WP_REST_Server::READABLE,
192 'callback' => array( $site_endpoint, 'get_purchases' ),
193 'permission_callback' => array( $site_endpoint, 'can_request' ),
194 )
195 );
196
197 // Get current site benefits.
198 register_rest_route(
199 'jetpack/v4',
200 '/site/benefits',
201 array(
202 'methods' => WP_REST_Server::READABLE,
203 'callback' => array( $site_endpoint, 'get_benefits' ),
204 'permission_callback' => array( $site_endpoint, 'can_request' ),
205 )
206 );
207
208 // Get Activity Log data for this site.
209 register_rest_route(
210 'jetpack/v4',
211 '/site/activity',
212 array(
213 'methods' => WP_REST_Server::READABLE,
214 'callback' => __CLASS__ . '::get_site_activity',
215 'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
216 )
217 );
218
219 // Return all modules.
220 register_rest_route(
221 'jetpack/v4',
222 '/module/all',
223 array(
224 'methods' => WP_REST_Server::READABLE,
225 'callback' => array( $module_list_endpoint, 'process' ),
226 'permission_callback' => array( $module_list_endpoint, 'can_request' ),
227 )
228 );
229
230 // Activate many modules.
231 register_rest_route(
232 'jetpack/v4',
233 '/module/all/active',
234 array(
235 'methods' => WP_REST_Server::EDITABLE,
236 'callback' => array( $module_list_endpoint, 'process' ),
237 'permission_callback' => array( $module_list_endpoint, 'can_request' ),
238 'args' => array(
239 'modules' => array(
240 'default' => '',
241 'type' => 'array',
242 'items' => array(
243 'type' => 'string',
244 ),
245 'required' => true,
246 'validate_callback' => __CLASS__ . '::validate_module_list',
247 ),
248 'active' => array(
249 'default' => true,
250 'type' => 'boolean',
251 'required' => false,
252 'validate_callback' => __CLASS__ . '::validate_boolean',
253 ),
254 ),
255 )
256 );
257
258 // Return a single module and update it when needed.
259 register_rest_route(
260 'jetpack/v4',
261 '/module/(?P<slug>[a-z\-]+)',
262 array(
263 'methods' => WP_REST_Server::READABLE,
264 'callback' => array( $core_api_endpoint, 'process' ),
265 'permission_callback' => array( $core_api_endpoint, 'can_request' ),
266 )
267 );
268
269 // Activate and deactivate a module.
270 register_rest_route(
271 'jetpack/v4',
272 '/module/(?P<slug>[a-z\-]+)/active',
273 array(
274 'methods' => WP_REST_Server::EDITABLE,
275 'callback' => array( $module_toggle_endpoint, 'process' ),
276 'permission_callback' => array( $module_toggle_endpoint, 'can_request' ),
277 'args' => array(
278 'active' => array(
279 'default' => true,
280 'type' => 'boolean',
281 'required' => true,
282 'validate_callback' => __CLASS__ . '::validate_boolean',
283 ),
284 ),
285 )
286 );
287
288 // Update a module.
289 register_rest_route(
290 'jetpack/v4',
291 '/module/(?P<slug>[a-z\-]+)',
292 array(
293 'methods' => WP_REST_Server::EDITABLE,
294 'callback' => array( $core_api_endpoint, 'process' ),
295 'permission_callback' => array( $core_api_endpoint, 'can_request' ),
296 'args' => self::get_updateable_parameters( 'any' ),
297 )
298 );
299
300 // Get data for a specific module, i.e. Protect block count, WPCOM stats,
301 // Akismet spam count, etc.
302 register_rest_route(
303 'jetpack/v4',
304 '/module/(?P<slug>[a-z\-]+)/data',
305 array(
306 'methods' => WP_REST_Server::READABLE,
307 'callback' => array( $module_data_endpoint, 'process' ),
308 'permission_callback' => array( $module_data_endpoint, 'can_request' ),
309 'args' => array(
310 'range' => array(
311 'default' => 'day',
312 'type' => 'string',
313 'required' => false,
314 'validate_callback' => __CLASS__ . '::validate_string',
315 ),
316 ),
317 )
318 );
319
320 // Check if the API key for a specific service is valid or not.
321 register_rest_route(
322 'jetpack/v4',
323 '/module/(?P<service>[a-z\-]+)/key/check',
324 array(
325 'methods' => WP_REST_Server::READABLE,
326 'callback' => array( $module_data_endpoint, 'key_check' ),
327 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
328 'sanitize_callback' => 'sanitize_text_field',
329 )
330 );
331
332 register_rest_route(
333 'jetpack/v4',
334 '/module/(?P<service>[a-z\-]+)/key/check',
335 array(
336 'methods' => WP_REST_Server::EDITABLE,
337 'callback' => array( $module_data_endpoint, 'key_check' ),
338 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
339 'sanitize_callback' => 'sanitize_text_field',
340 'args' => array(
341 'api_key' => array(
342 'default' => '',
343 'type' => 'string',
344 'validate_callback' => __CLASS__ . '::validate_alphanum',
345 ),
346 ),
347 )
348 );
349
350 // Update any Jetpack module option or setting.
351 register_rest_route(
352 'jetpack/v4',
353 '/settings',
354 array(
355 'methods' => WP_REST_Server::EDITABLE,
356 'callback' => array( $core_api_endpoint, 'process' ),
357 'permission_callback' => array( $core_api_endpoint, 'can_request' ),
358 'args' => self::get_updateable_parameters( 'any' ),
359 )
360 );
361
362 // Update a module.
363 register_rest_route(
364 'jetpack/v4',
365 '/settings/(?P<slug>[a-z\-]+)',
366 array(
367 'methods' => WP_REST_Server::EDITABLE,
368 'callback' => array( $core_api_endpoint, 'process' ),
369 'permission_callback' => array( $core_api_endpoint, 'can_request' ),
370 'args' => self::get_updateable_parameters(),
371 )
372 );
373
374 // Return all module settings.
375 register_rest_route(
376 'jetpack/v4',
377 '/settings/',
378 array(
379 'methods' => WP_REST_Server::READABLE,
380 'callback' => array( $core_api_endpoint, 'process' ),
381 'permission_callback' => array( $core_api_endpoint, 'can_request' ),
382 )
383 );
384
385 // Reset all Jetpack options.
386 register_rest_route(
387 'jetpack/v4',
388 '/options/(?P<options>[a-z\-]+)',
389 array(
390 'methods' => WP_REST_Server::EDITABLE,
391 'callback' => __CLASS__ . '::reset_jetpack_options',
392 'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
393 )
394 );
395
396 // Updates: get number of plugin updates available.
397 register_rest_route(
398 'jetpack/v4',
399 '/updates/plugins',
400 array(
401 'methods' => WP_REST_Server::READABLE,
402 'callback' => __CLASS__ . '::get_plugin_update_count',
403 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
404 )
405 );
406
407 // Dismiss Jetpack Notices.
408 register_rest_route(
409 'jetpack/v4',
410 '/notice/(?P<notice>[a-z\-_]+)',
411 array(
412 'methods' => WP_REST_Server::EDITABLE,
413 'callback' => __CLASS__ . '::dismiss_notice',
414 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check',
415 )
416 );
417
418 /*
419 * Plugins: manage plugins on your site.
420 *
421 * @since 8.9.0
422 *
423 * @to-do: deprecate and switch to /wp/v2/plugins when WordPress 5.5 is the minimum required version.
424 * Noting that the `source` parameter is Jetpack-specific (not implemented in Core).
425 */
426 register_rest_route(
427 'jetpack/v4',
428 '/plugins',
429 array(
430 array(
431 'methods' => WP_REST_Server::READABLE,
432 'callback' => __CLASS__ . '::get_plugins',
433 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
434 ),
435 array(
436 'methods' => WP_REST_Server::CREATABLE,
437 'callback' => __CLASS__ . '::install_plugin',
438 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
439 'args' => array(
440 'slug' => array(
441 'type' => 'string',
442 'required' => true,
443 'description' => __( 'WordPress.org plugin directory slug.', 'jetpack' ),
444 'pattern' => '[\w\-]+',
445 ),
446 'status' => array(
447 'description' => __( 'The plugin activation status.', 'jetpack' ),
448 'type' => 'string',
449 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
450 'default' => 'inactive',
451 ),
452 'source' => array(
453 'required' => false,
454 'type' => 'string',
455 'validate_callback' => __CLASS__ . '::validate_string',
456 ),
457 ),
458 ),
459 )
460 );
461
462 /*
463 * Plugins: activate a specific plugin.
464 *
465 * @since 8.9.0
466 *
467 * @to-do: deprecate and switch to /wp/v2/plugins when WordPress 5.5 is the minimum required version.
468 * Noting that the `source` parameter is Jetpack-specific (not implemented in Core).
469 */
470 register_rest_route(
471 'jetpack/v4',
472 '/plugins/(?P<plugin>[^.\/]+(?:\/[^.\/]+)?)',
473 array(
474 'methods' => WP_REST_Server::EDITABLE,
475 'callback' => __CLASS__ . '::activate_plugin',
476 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
477 'args' => array(
478 'status' => array(
479 'required' => true,
480 'type' => 'string',
481 'validate_callback' => __CLASS__ . '::validate_activate_plugin',
482 ),
483 'source' => array(
484 'required' => false,
485 'type' => 'string',
486 'validate_callback' => __CLASS__ . '::validate_string',
487 ),
488 ),
489 )
490 );
491
492 // Plugins: check if the plugin is active.
493 register_rest_route(
494 'jetpack/v4',
495 '/plugin/(?P<plugin>[a-z\/\.\-_]+)',
496 array(
497 'methods' => WP_REST_Server::READABLE,
498 'callback' => __CLASS__ . '::get_plugin',
499 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
500 )
501 );
502
503 // Widgets: get information about a widget that supports it.
504 register_rest_route(
505 'jetpack/v4',
506 '/widgets/(?P<id>[0-9a-z\-_]+)',
507 array(
508 'methods' => WP_REST_Server::READABLE,
509 'callback' => array( $widget_endpoint, 'process' ),
510 'permission_callback' => array( $widget_endpoint, 'can_request' ),
511 )
512 );
513
514 // Site Verify: check if the site is verified, and a get verification token if not.
515 register_rest_route(
516 'jetpack/v4',
517 '/verify-site/(?P<service>[a-z\-_]+)',
518 array(
519 'methods' => WP_REST_Server::READABLE,
520 'callback' => __CLASS__ . '::is_site_verified_and_token',
521 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
522 )
523 );
524
525 register_rest_route(
526 'jetpack/v4',
527 '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)',
528 array(
529 'methods' => WP_REST_Server::READABLE,
530 'callback' => __CLASS__ . '::is_site_verified_and_token',
531 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
532 )
533 );
534
535 // Site Verify: tell a service to verify the site.
536 register_rest_route(
537 'jetpack/v4',
538 '/verify-site/(?P<service>[a-z\-_]+)',
539 array(
540 'methods' => WP_REST_Server::EDITABLE,
541 'callback' => __CLASS__ . '::verify_site',
542 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
543 'args' => array(
544 'keyring_id' => array(
545 'required' => true,
546 'type' => 'integer',
547 'validate_callback' => __CLASS__ . '::validate_posint',
548 ),
549 ),
550 )
551 );
552
553 /*
554 * Manage the Jetpack CRM plugin's integration with Jetpack contact forms.
555 */
556 register_rest_route(
557 'jetpack/v4',
558 'jetpack_crm',
559 array(
560 array(
561 'methods' => WP_REST_Server::READABLE,
562 'callback' => __CLASS__ . '::get_jetpack_crm_data',
563 'permission_callback' => __CLASS__ . '::jetpack_crm_data_permission_check',
564 ),
565 array(
566 'methods' => WP_REST_Server::EDITABLE,
567 'callback' => __CLASS__ . '::activate_crm_jetpack_forms_extension',
568 'permission_callback' => __CLASS__ . '::activate_crm_extensions_permission_check',
569 'args' => array(
570 'extension' => array(
571 'required' => true,
572 'type' => 'text',
573 ),
574 ),
575 ),
576 )
577 );
578
579 register_rest_route(
580 'jetpack/v4',
581 'purchase-token',
582 array(
583 array(
584 'methods' => WP_REST_Server::READABLE,
585 'callback' => __CLASS__ . '::get_purchase_token',
586 'permission_callback' => __CLASS__ . '::purchase_token_permission_check',
587 ),
588 array(
589 'methods' => WP_REST_Server::CREATABLE,
590 'callback' => __CLASS__ . '::delete_purchase_token',
591 'permission_callback' => __CLASS__ . '::purchase_token_permission_check',
592 ),
593 )
594 );
595
596 /*
597 * Set the Jetpack Option `has_see_wc_connection_modal` to true
598 */
599 register_rest_route(
600 'jetpack/v4',
601 'seen-wc-connection-modal',
602 array(
603 'methods' => WP_REST_Server::EDITABLE,
604 'callback' => __CLASS__ . '::set_has_seen_wc_connection_modal',
605 'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
606 )
607 );
608
609 // Save subscriber token and redirect
610 register_rest_route(
611 'jetpack/v4',
612 '/subscribers/auth',
613 array(
614 'methods' => WP_REST_Server::READABLE,
615 'callback' => __CLASS__ . '::set_subscriber_cookie_and_redirect',
616 'permission_callback' => '__return_true',
617 'args' => array(
618 'redirect_url' => array(
619 'required' => true,
620 'description' => __( 'The URL to redirect to.', 'jetpack' ),
621 'validate_callback' => 'wp_http_validate_url',
622 'sanitize_callback' => 'sanitize_url',
623 'type' => 'string',
624 'format' => 'uri',
625 ),
626 ),
627 )
628 );
629
630 /**
631 * Get the list of available Jetpack features.
632 *
633 * @since 13.9
634 */
635 register_rest_route(
636 'jetpack/v4',
637 '/features/available',
638 array(
639 'methods' => WP_REST_Server::READABLE,
640 'callback' => array( static::class, 'get_features_available' ),
641 'permission_callback' => array( static::class, 'get_features_permission_check' ),
642 )
643 );
644
645 /**
646 * Get the list of enabled Jetpack features.
647 *
648 * @since 13.9
649 */
650 register_rest_route(
651 'jetpack/v4',
652 '/features/enabled',
653 array(
654 'methods' => WP_REST_Server::READABLE,
655 'callback' => array( static::class, 'get_features_enabled' ),
656 'permission_callback' => array( static::class, 'get_features_permission_check' ),
657 )
658 );
659 }
660
661 /**
662 * Ask WPCOM for a JWT token to use for OpenAI conversations.
663 *
664 * @deprecated since 16.2
665 * @see Automattic\Jetpack\Connection\REST_Jetpack_AI_JWT::get_jwt()
666 *
667 * @return array|WP_Error The token and blog ID, or the error from WPCOM.
668 */
669 public static function get_openai_jwt() {
670 _deprecated_function( __METHOD__, 'jetpack-16.2', '\Automattic\Jetpack\Connection\REST_Jetpack_AI_JWT::get_jwt' );
671
672 $response = ( new REST_Jetpack_AI_JWT() )->get_jwt();
673
674 if ( is_wp_error( $response ) ) {
675 return $response;
676 }
677
678 // Pre-deprecation callers expect the raw array, not a WP_REST_Response.
679 return $response->get_data();
680 }
681
682 /**
683 * Set subscriber cookie and redirect
684 *
685 * @param \WP_Rest_Request $request The URL to redirect to.
686 *
687 * @return WP_Error|WP_REST_Response
688 */
689 public static function set_subscriber_cookie_and_redirect( $request ) {
690 require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
691 $subscription_service = \Automattic\Jetpack\Extensions\Premium_Content\subscription_service();
692 // Note: get_and_set_token_from_request() sets the subscriber cookie as a side effect.
693 // The cookie is set regardless of the redirect target below; only the redirect is gated.
694 $token = $subscription_service->get_and_set_token_from_request();
695 $payload = $subscription_service->decode_token( $token );
696 $is_valid_token = ! empty( $payload );
697 if ( ! $is_valid_token ) {
698 return new WP_Error( 'invalid-token', 'Invalid Token', array( 'status' => 403 ) );
699 }
700
701 // Only redirect to the current site, not to an arbitrary host.
702 $redirect_url = wp_validate_redirect( $request['redirect_url'], '' );
703 if ( ! $redirect_url ) {
704 return new WP_Error( 'invalid-redirect', 'Invalid Redirect URL', array( 'status' => 400 ) );
705 }
706
707 return new WP_REST_Response( null, 302, array( 'location' => $redirect_url ) );
708 }
709
710 /**
711 * Return a purchase token used for site-connected (non user-authenticated) checkout.
712 *
713 * @return string|WP_Error The current purchase token or WP_Error with error details.
714 */
715 public static function get_purchase_token() {
716 $blog_id = Jetpack_Options::get_option( 'id' );
717 if ( ! $blog_id ) {
718 return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) );
719 }
720
721 return Jetpack_Options::get_option( 'purchase_token', '' );
722 }
723
724 /**
725 * Delete the current purchase token.
726 *
727 * @return boolean|WP_Error Whether the token was deleted or WP_Error with error details.
728 */
729 public static function delete_purchase_token() {
730 $blog_id = Jetpack_Options::get_option( 'id' );
731 if ( ! $blog_id ) {
732 return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) );
733 }
734
735 return Jetpack_Options::delete_option( 'purchase_token' );
736 }
737
738 /**
739 * Get list of Jetpack Plans.
740 *
741 * @param WP_REST_Request $request The request.
742 */
743 public static function get_plans( $request ) {
744 $request = Client::wpcom_json_api_request_as_user(
745 '/plans?_locale=' . get_user_locale(),
746 '2',
747 array(
748 'method' => 'GET',
749 'headers' => array(
750 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
751 ),
752 )
753 );
754
755 $body = json_decode( wp_remote_retrieve_body( $request ) );
756 if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
757 $data = $body;
758 } else {
759 // something went wrong so we'll just return the response without caching.
760 return $body;
761 }
762
763 return $data;
764 }
765
766 /**
767 * Gets the WP.com products that are in use on wpcom.
768 * Similar to the WP.com plans that we currently in user on WPCOM.
769 *
770 * @param WP_REST_Request $request The request.
771 *
772 * @return string|WP_Error A JSON object of wpcom products if the request was successful, or a WP_Error otherwise.
773 */
774 public static function get_products( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
775 $wpcom_request = Client::wpcom_json_api_request_as_user(
776 '/products?_locale=' . get_user_locale() . '&type=jetpack',
777 '2',
778 array(
779 'method' => 'GET',
780 'headers' => array(
781 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
782 ),
783 )
784 );
785
786 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
787 if ( 200 === $response_code ) {
788 return json_decode( wp_remote_retrieve_body( $wpcom_request ) );
789 } else {
790 // Something went wrong so we'll just return the response without caching.
791 return new WP_Error(
792 'failed_to_fetch_data',
793 esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
794 array( 'status' => $response_code )
795 );
796 }
797 }
798
799 /**
800 * Send Survey details to WordPress.com.
801 *
802 * @param WP_REST_Request $request The request.
803 */
804 public static function submit_survey( $request ) {
805 $wpcom_request = Client::wpcom_json_api_request_as_user(
806 '/marketing/survey',
807 'v2',
808 array(
809 'method' => 'POST',
810 'headers' => array(
811 'Content-Type' => 'application/json',
812 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
813 ),
814 ),
815 $request->get_json_params()
816 );
817
818 $wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
819 if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) {
820 $data = $wpcom_request_body;
821 } else {
822 // something went wrong so we'll just return the response without caching.
823 return $wpcom_request_body;
824 }
825
826 return $data;
827 }
828
829 /**
830 * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
831 * keyring to use to get the token if it is not
832 *
833 * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
834 *
835 * @since 6.6.0
836 *
837 * @param WP_REST_Request $request The request sent to the WP REST API.
838 *
839 * @return array|WP_Error
840 */
841 public static function is_site_verified_and_token( $request ) {
842 /**
843 * Return an error if the site uses a Maintenance / Coming Soon plugin
844 * and if the plugin is configured to make the site private.
845 *
846 * We currently handle the following plugins:
847 * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
848 * - https://wordpress.org/plugins/mojo-under-construction
849 * - https://wordpress.org/plugins/under-construction-page
850 * - https://wordpress.org/plugins/ultimate-under-construction
851 * - https://wordpress.org/plugins/coming-soon
852 *
853 * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
854 * If the filter returns true, we will consider the site as under construction.
855 */
856 $mm_coming_soon = get_option( 'mm_coming_soon', null );
857 $under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
858 $ucp_options = get_option( 'ucp_options', array() );
859 $uuc_settings = get_option( 'uuc_settings', array() );
860 $csp4 = get_option( 'seed_csp4_settings_content', array() );
861 if (
862 ( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
863 || Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
864 || ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
865 || ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
866 || ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) && isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
867 ||
868 /**
869 * Allow plugins to mark a site as "under construction".
870 *
871 * @since 6.7.0
872 *
873 * @param false bool Is the site under construction? Default to false.
874 */
875 true === apply_filters( 'jetpack_is_under_construction_plugin', false )
876 ) {
877 return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
878 }
879
880 $xml = new Jetpack_IXR_Client(
881 array(
882 'user_id' => get_current_user_id(),
883 )
884 );
885
886 $args = array(
887 'user_id' => get_current_user_id(),
888 'service' => $request['service'],
889 );
890
891 if ( isset( $request['keyring_id'] ) ) {
892 $args['keyring_id'] = $request['keyring_id'];
893 }
894
895 $xml->query( 'jetpack.isSiteVerified', $args );
896
897 if ( $xml->isError() ) {
898 return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
899 } else {
900 return $xml->getResponse();
901 }
902 }
903
904 /**
905 * Verify site with external service.
906 *
907 * @param WP_REST_Request $request The request.
908 */
909 public static function verify_site( $request ) {
910 $xml = new Jetpack_IXR_Client(
911 array(
912 'user_id' => get_current_user_id(),
913 )
914 );
915
916 $params = $request->get_json_params();
917
918 $xml->query(
919 'jetpack.verifySite',
920 array(
921 'user_id' => get_current_user_id(),
922 'service' => $request['service'],
923 'keyring_id' => $params['keyring_id'],
924 )
925 );
926
927 if ( $xml->isError() ) {
928 return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
929 } else {
930 $response = $xml->getResponse();
931
932 if ( ! empty( $response['errors'] ) ) {
933 $error = new WP_Error();
934 $error->errors = $response['errors'];
935 return $error;
936 }
937
938 return $response;
939 }
940 }
941
942 /**
943 * Handles dismissing of Jetpack Notices
944 *
945 * @since 4.3.0
946 *
947 * @param WP_REST_Request $request The request sent to the WP REST API.
948 *
949 * @return array|WP_Error
950 */
951 public static function dismiss_notice( $request ) {
952 $notice = $request['notice'];
953
954 if ( ! isset( $request['dismissed'] ) || true !== $request['dismissed'] ) {
955 return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) );
956 }
957
958 if ( isset( $notice ) && ! empty( $notice ) ) {
959 switch ( $notice ) {
960 case 'feedback_dash_request':
961 case 'welcome':
962 $notices = get_option( 'jetpack_dismissed_notices', array() );
963 $notices[ $notice ] = true;
964 update_option( 'jetpack_dismissed_notices', $notices );
965 return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) );
966
967 default:
968 return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
969 }
970 }
971
972 return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) );
973 }
974
975 /**
976 * Verify that the user can disconnect the site.
977 *
978 * @since 4.3.0
979 *
980 * @return bool|WP_Error True if user is able to disconnect the site.
981 */
982 public static function disconnect_site_permission_callback() {
983 if ( current_user_can( 'jetpack_disconnect' ) ) {
984 return true;
985 }
986
987 return new WP_Error(
988 'invalid_user_permission_jetpack_disconnect',
989 REST_Connector::get_user_permissions_error_msg(),
990 array( 'status' => rest_authorization_required_code() )
991 );
992 }
993
994 /**
995 * Verify that the user can get a connect/link URL
996 *
997 * @since 4.3.0
998 *
999 * @return bool|WP_Error True if user is able to disconnect the site.
1000 */
1001 public static function connect_url_permission_callback() {
1002 if ( current_user_can( 'jetpack_connect_user' ) ) {
1003 return true;
1004 }
1005
1006 return new WP_Error(
1007 'invalid_user_permission_jetpack_connect',
1008 REST_Connector::get_user_permissions_error_msg(),
1009 array( 'status' => rest_authorization_required_code() )
1010 );
1011 }
1012
1013 /**
1014 * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
1015 *
1016 * @uses Automattic\Jetpack\Connection\Manager::is_user_connected();)
1017 *
1018 * @deprecated since Jetpack 14.4.0
1019 * @see Automattic\Jetpack\Connection\REST_Connector::unlink_user_permission_callback()
1020 *
1021 * @since 4.3.0
1022 *
1023 * @return bool|WP_Error True if user is able to unlink.
1024 */
1025 public static function unlink_user_permission_callback() {
1026 _deprecated_function( __METHOD__, 'jetpack-14.4.0', 'Automattic\Jetpack\Connection\REST_Connector::unlink_user_permission_callback()' );
1027 return REST_Connector::unlink_user_permission_callback();
1028 }
1029
1030 /**
1031 * Verify that user can manage Jetpack modules.
1032 *
1033 * @since 4.3.0
1034 *
1035 * @return bool Whether user has the capability 'jetpack_manage_modules'.
1036 */
1037 public static function manage_modules_permission_check() {
1038 if ( current_user_can( 'jetpack_manage_modules' ) ) {
1039 return true;
1040 }
1041
1042 return new WP_Error(
1043 'invalid_user_permission_manage_modules',
1044 REST_Connector::get_user_permissions_error_msg(),
1045 array( 'status' => rest_authorization_required_code() )
1046 );
1047 }
1048
1049 /**
1050 * Verify that user can update Jetpack modules.
1051 *
1052 * @since 4.3.0
1053 *
1054 * @return bool Whether user has the capability 'jetpack_configure_modules'.
1055 */
1056 public static function configure_modules_permission_check() {
1057 if ( current_user_can( 'jetpack_configure_modules' ) ) {
1058 return true;
1059 }
1060
1061 return new WP_Error(
1062 'invalid_user_permission_configure_modules',
1063 REST_Connector::get_user_permissions_error_msg(),
1064 array( 'status' => rest_authorization_required_code() )
1065 );
1066 }
1067
1068 /**
1069 * Verify that user can view Jetpack admin page.
1070 *
1071 * @since 4.3.0
1072 *
1073 * @return bool Whether user has the capability 'jetpack_admin_page'.
1074 */
1075 public static function view_admin_page_permission_check() {
1076 if ( current_user_can( 'jetpack_admin_page' ) ) {
1077 return true;
1078 }
1079
1080 return new WP_Error(
1081 'invalid_user_permission_view_admin',
1082 REST_Connector::get_user_permissions_error_msg(),
1083 array( 'status' => rest_authorization_required_code() )
1084 );
1085 }
1086
1087 /**
1088 * Verify that user can update Jetpack general settings.
1089 *
1090 * @since 4.3.0
1091 *
1092 * @return bool Whether user has the capability 'update_settings_permission_check'.
1093 */
1094 public static function update_settings_permission_check() {
1095 if ( current_user_can( 'jetpack_configure_modules' ) ) {
1096 return true;
1097 }
1098
1099 return new WP_Error(
1100 'invalid_user_permission_manage_settings',
1101 REST_Connector::get_user_permissions_error_msg(),
1102 array( 'status' => rest_authorization_required_code() )
1103 );
1104 }
1105
1106 /**
1107 * Verify that user can view Jetpack admin page and can activate plugins.
1108 *
1109 * @since 4.3.0
1110 *
1111 * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'.
1112 */
1113 public static function activate_plugins_permission_check() {
1114 if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) {
1115 return true;
1116 }
1117
1118 return new WP_Error(
1119 'invalid_user_permission_activate_plugins',
1120 REST_Connector::get_user_permissions_error_msg(),
1121 array( 'status' => rest_authorization_required_code() )
1122 );
1123 }
1124
1125 /**
1126 * Verify that user can edit other's posts (Editors and Administrators).
1127 *
1128 * @return bool Whether user has the capability 'edit_others_posts'.
1129 */
1130 public static function edit_others_posts_check() {
1131 if ( current_user_can( 'edit_others_posts' ) ) {
1132 return true;
1133 }
1134
1135 return new WP_Error(
1136 'invalid_user_permission_edit_others_posts',
1137 REST_Connector::get_user_permissions_error_msg(),
1138 array( 'status' => rest_authorization_required_code() )
1139 );
1140 }
1141
1142 /**
1143 * Verify that site can view and delete the site's purchase token.
1144 *
1145 * @return bool Whether site has level-site auth or user has the capability 'manage_options'.
1146 */
1147 public static function purchase_token_permission_check() {
1148 if ( Rest_Authentication::is_signed_with_blog_token() ) {
1149 return true;
1150 }
1151
1152 if ( current_user_can( 'manage_options' ) ) {
1153 return true;
1154 }
1155
1156 return new WP_Error(
1157 'invalid_permission_manage_purchase_token',
1158 REST_Connector::get_user_permissions_error_msg(),
1159 array( 'status' => rest_authorization_required_code() )
1160 );
1161 }
1162
1163 /**
1164 * Fetch information about the Rewind status of the site.
1165 */
1166 public static function rewind_data() {
1167 $site_id = Jetpack_Options::get_option( 'id' );
1168
1169 if ( ! $site_id ) {
1170 return new WP_Error( 'site_id_missing' );
1171 }
1172
1173 if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1174 $rewind_state = get_transient( 'jetpack_rewind_state' );
1175 if ( $rewind_state ) {
1176 return $rewind_state;
1177 }
1178 }
1179
1180 $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
1181
1182 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1183 return new WP_Error( 'rewind_data_fetch_failed' );
1184 }
1185
1186 $body = wp_remote_retrieve_body( $response );
1187 $result = json_decode( $body );
1188 set_transient( 'jetpack_rewind_state', $result, 30 * MINUTE_IN_SECONDS );
1189
1190 return $result;
1191 }
1192
1193 /**
1194 * Get rewind data
1195 *
1196 * @since 5.7.0
1197 *
1198 * @return array Array of rewind properties.
1199 */
1200 public static function get_rewind_data() {
1201 $rewind_data = self::rewind_data();
1202
1203 if ( ! is_wp_error( $rewind_data ) ) {
1204 return rest_ensure_response(
1205 array(
1206 'code' => 'success',
1207 'message' => esc_html__( 'Backup & Scan data correctly received.', 'jetpack' ),
1208 'data' => wp_json_encode( $rewind_data, JSON_UNESCAPED_SLASHES ),
1209 )
1210 );
1211 }
1212
1213 if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) {
1214 return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1215 }
1216
1217 if ( $rewind_data->get_error_code() === 'site_id_missing' ) {
1218 return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1219 }
1220
1221 return new WP_Error(
1222 'error_get_rewind_data',
1223 esc_html__( 'Could not retrieve Backup & Scan data.', 'jetpack' ),
1224 array( 'status' => 500 )
1225 );
1226 }
1227
1228 /**
1229 * Gets Scan state data.
1230 *
1231 * @since 8.5.0
1232 *
1233 * @return array|WP_Error Result from WPCOM API or error.
1234 */
1235 public static function scan_state() {
1236
1237 if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1238 $scan_state = get_transient( 'jetpack_scan_state' );
1239 if ( ! empty( $scan_state ) ) {
1240 return $scan_state;
1241 }
1242 }
1243 $site_id = Jetpack_Options::get_option( 'id' );
1244
1245 if ( ! $site_id ) {
1246 return new WP_Error( 'site_id_missing' );
1247 }
1248 // The default timeout was too short in come cases.
1249 add_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 );
1250 $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
1251 remove_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 );
1252
1253 if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
1254 return new WP_Error( 'scan_state_fetch_failed' );
1255 }
1256
1257 $body = wp_remote_retrieve_body( $response );
1258 $result = json_decode( $body );
1259 set_transient( 'jetpack_scan_state', $result, 30 * MINUTE_IN_SECONDS );
1260
1261 return $result;
1262 }
1263
1264 /**
1265 * Increases the request timeout value to 30 seconds.
1266 *
1267 * @return int Always returns 30.
1268 */
1269 public static function increase_timeout_30() {
1270 return 30; // 30 Seconds
1271 }
1272
1273 /**
1274 * Get Scan state for API.
1275 *
1276 * @since 8.5.0
1277 *
1278 * @return WP_REST_Response|WP_Error REST response or error state.
1279 */
1280 public static function get_scan_state() {
1281 $scan_state = self::scan_state();
1282
1283 if ( ! is_wp_error( $scan_state ) ) {
1284 if ( ( new Host() )->is_woa_site() && ! empty( $scan_state->threats ) ) {
1285 $scan_state->threats = array();
1286 }
1287 return rest_ensure_response(
1288 array(
1289 'code' => 'success',
1290 'message' => esc_html__( 'Scan state correctly received.', 'jetpack' ),
1291 'data' => wp_json_encode( $scan_state, JSON_UNESCAPED_SLASHES ),
1292 )
1293 );
1294 }
1295
1296 if ( $scan_state->get_error_code() === 'scan_state_fetch_failed' ) {
1297 return new WP_Error( 'scan_state_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) );
1298 }
1299
1300 if ( $scan_state->get_error_code() === 'site_id_missing' ) {
1301 return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) );
1302 }
1303
1304 return new WP_Error(
1305 'error_get_rewind_data',
1306 esc_html__( 'Could not retrieve Scan state.', 'jetpack' ),
1307 array( 'status' => 500 )
1308 );
1309 }
1310
1311 /**
1312 * Disconnects Jetpack from the WordPress.com Servers
1313 *
1314 * @deprecated since Jetpack 10.0.0
1315 * @see Automattic\Jetpack\Connection\REST_Connector::disconnect_site()
1316 *
1317 * @uses Jetpack::disconnect();
1318 * @since 4.3.0
1319 *
1320 * @param WP_REST_Request $request The request sent to the WP REST API.
1321 *
1322 * @return bool|WP_Error True if Jetpack successfully disconnected.
1323 */
1324 public static function disconnect_site( $request ) {
1325 _deprecated_function( __METHOD__, 'jetpack-10.0.0', '\Automattic\Jetpack\Connection\REST_Connector::disconnect_site' );
1326
1327 if ( ! isset( $request['isActive'] ) || false !== $request['isActive'] ) {
1328 return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1329 }
1330
1331 if ( Jetpack::is_connection_ready() ) {
1332 Jetpack::disconnect();
1333 return rest_ensure_response( array( 'code' => 'success' ) );
1334 }
1335
1336 return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site. Please try again.', 'jetpack' ), array( 'status' => 400 ) );
1337 }
1338
1339 /**
1340 * Gets a new connect raw URL with fresh nonce.
1341 *
1342 * @uses Jetpack::disconnect();
1343 * @since 4.3.0
1344 *
1345 * @param WP_REST_Request $request The request sent to the WP REST API.
1346 *
1347 * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise.
1348 */
1349 public static function build_connect_url( $request = array() ) {
1350 $from = $request['from'] ?? false;
1351 $redirect = $request['redirect'] ?? false;
1352
1353 $url = Jetpack::init()->build_connect_url( true, $redirect, $from );
1354 if ( $url ) {
1355 return rest_ensure_response( $url );
1356 }
1357
1358 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 ) );
1359 }
1360
1361 /**
1362 * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
1363 * Information about the master/primary user.
1364 * Information about the current user.
1365 *
1366 * @deprecated since Jetpack 10.0.0
1367 * @see Automattic\Jetpack\Connection\REST_Connector::get_user_connection_data()
1368 *
1369 * @since 4.3.0
1370 *
1371 * @return object
1372 */
1373 public static function get_user_connection_data() {
1374 _deprecated_function( __METHOD__, 'jetpack-10.0.0', '\Automattic\Jetpack\Connection\REST_Connector::get_user_connection_data' );
1375
1376 require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class-jetpack-redux-state-helper.php';
1377
1378 $connection_owner = ( new Connection_Manager() )->get_connection_owner();
1379 $owner_display_name = false === $connection_owner ? null : $connection_owner->data->display_name;
1380
1381 $response = array(
1382 'currentUser' => jetpack_current_user_data(),
1383 'connectionOwner' => $owner_display_name,
1384 );
1385 return rest_ensure_response( $response );
1386 }
1387
1388 /**
1389 * Unlinks current user from the WordPress.com Servers.
1390 *
1391 * @param WP_REST_Request $request The request sent to the WP REST API.
1392 * @uses Automattic\Jetpack\Connection\Manager->disconnect_user
1393 *
1394 * @deprecated since Jetpack 14.4.0
1395 * @see Automattic\Jetpack\Connection\REST_Connector::unlink_user()
1396 *
1397 * @since 4.3.0
1398 *
1399 * @return bool|WP_Error True if user successfully unlinked.
1400 */
1401 public static function unlink_user( $request ) {
1402 _deprecated_function( __METHOD__, 'jetpack-14.4.0', 'Automattic\Jetpack\Connection\REST_Connector::unlink_user()' );
1403 return REST_Connector::unlink_user( $request );
1404 }
1405
1406 /**
1407 * Gets current user's tracking settings.
1408 *
1409 * @since 6.0.0
1410 *
1411 * @param WP_REST_Request $request The request sent to the WP REST API.
1412 *
1413 * @return WP_REST_Response|WP_Error Response, else error.
1414 */
1415 public static function get_user_tracking_settings( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1416 if ( ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) {
1417 $response = array(
1418 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1419 );
1420 } else {
1421 $response = Client::wpcom_json_api_request_as_user(
1422 '/jetpack-user-tracking',
1423 'v2',
1424 array(
1425 'method' => 'GET',
1426 'headers' => array(
1427 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
1428 ),
1429 )
1430 );
1431 if ( ! is_wp_error( $response ) ) {
1432 $response = json_decode( wp_remote_retrieve_body( $response ), true );
1433 }
1434 }
1435
1436 return rest_ensure_response( $response );
1437 }
1438
1439 /**
1440 * Updates current user's tracking settings.
1441 *
1442 * @since 6.0.0
1443 *
1444 * @param WP_REST_Request $request The request sent to the WP REST API.
1445 *
1446 * @return WP_REST_Response|WP_Error Response, else error.
1447 */
1448 public static function update_user_tracking_settings( $request ) {
1449 if ( ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) {
1450 $response = array(
1451 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com.
1452 );
1453 } else {
1454 $response = Client::wpcom_json_api_request_as_user(
1455 '/jetpack-user-tracking',
1456 'v2',
1457 array(
1458 'method' => 'PUT',
1459 'headers' => array(
1460 'Content-Type' => 'application/json',
1461 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
1462 ),
1463 ),
1464 wp_json_encode( $request->get_params(), JSON_UNESCAPED_SLASHES )
1465 );
1466 if ( ! is_wp_error( $response ) ) {
1467 $response = json_decode( wp_remote_retrieve_body( $response ), true );
1468 }
1469 }
1470
1471 return rest_ensure_response( $response );
1472 }
1473
1474 /**
1475 * Fetch site data from .com including the site's current plan and the site's products.
1476 *
1477 * @since 5.5.0
1478 * @deprecated 16.2 Use Automattic\Jetpack\Connection\Manager::get_connected_site_data().
1479 *
1480 * @return stdClass|WP_Error
1481 */
1482 public static function site_data() {
1483 _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\Jetpack\Connection\Manager::get_connected_site_data' );
1484
1485 return ( new Connection_Manager() )->get_connected_site_data();
1486 }
1487
1488 /**
1489 * Get site data, including for example, the site's current plan.
1490 *
1491 * @since 4.3.0
1492 * @deprecated 16.2 Use Automattic\Jetpack\Connection\REST_Connector::site_data_response().
1493 *
1494 * @return WP_Error|WP_HTTP_Response|WP_REST_Response
1495 */
1496 public static function get_site_data() {
1497 _deprecated_function( __METHOD__, 'jetpack-16.2', 'Automattic\Jetpack\Connection\REST_Connector::site_data_response' );
1498
1499 return REST_Connector::site_data_response();
1500 }
1501
1502 /**
1503 * Fetch AL data for this site and return it.
1504 *
1505 * @since 7.4
1506 *
1507 * @return array|WP_Error
1508 */
1509 public static function get_site_activity() {
1510 $site_id = Jetpack_Options::get_option( 'id' );
1511
1512 if ( ! $site_id ) {
1513 return new WP_Error(
1514 'site_id_missing',
1515 esc_html__( 'Site ID is missing.', 'jetpack' ),
1516 array( 'status' => 400 )
1517 );
1518 }
1519
1520 $response = Client::wpcom_json_api_request_as_user(
1521 "/sites/$site_id/activity",
1522 '2',
1523 array(
1524 'method' => 'GET',
1525 'headers' => array(
1526 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
1527 ),
1528 ),
1529 null,
1530 'wpcom'
1531 );
1532 $response_code = wp_remote_retrieve_response_code( $response );
1533
1534 if ( 200 !== $response_code ) {
1535 return new WP_Error(
1536 'activity_fetch_failed',
1537 esc_html__( 'Could not retrieve site activity.', 'jetpack' ),
1538 array( 'status' => $response_code )
1539 );
1540 }
1541
1542 $data = json_decode( wp_remote_retrieve_body( $response ) );
1543
1544 if ( ! isset( $data->current->orderedItems ) ) {
1545 return new WP_Error(
1546 'activity_not_found',
1547 esc_html__( 'No activity found', 'jetpack' ),
1548 array( 'status' => 204 ) // no content.
1549 );
1550 }
1551
1552 return rest_ensure_response(
1553 array(
1554 'code' => 'success',
1555 'data' => $data->current->orderedItems,
1556 )
1557 );
1558 }
1559
1560 /**
1561 * Reset Jetpack options
1562 *
1563 * @since 4.3.0
1564 *
1565 * @param WP_REST_Request $request {
1566 * Array of parameters received by request.
1567 *
1568 * @type string $options Available options to reset are options|modules
1569 * }
1570 *
1571 * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error.
1572 */
1573 public static function reset_jetpack_options( $request ) {
1574
1575 if ( ! isset( $request['reset'] ) || true !== $request['reset'] ) {
1576 return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1577 }
1578
1579 if ( isset( $request['options'] ) ) {
1580 $data = $request['options'];
1581 $message = '';
1582
1583 switch ( $data ) {
1584 case ( 'options' ):
1585 $options_to_reset = Jetpack::get_jetpack_options_for_reset();
1586
1587 // Reset the Jetpack options.
1588 foreach ( $options_to_reset['jp_options'] as $option_to_reset ) {
1589 Jetpack_Options::delete_option( $option_to_reset );
1590 }
1591
1592 foreach ( $options_to_reset['wp_options'] as $option_to_reset ) {
1593 delete_option( $option_to_reset );
1594 }
1595
1596 // Reset to default modules.
1597 $default_modules = Jetpack::get_default_modules();
1598 Jetpack::update_active_modules( $default_modules );
1599 $message = esc_html__( 'Jetpack options reset.', 'jetpack' );
1600
1601 break;
1602 case 'modules':
1603 $default_modules = Jetpack::get_default_modules();
1604 Jetpack::update_active_modules( $default_modules );
1605 $message = esc_html__( 'Modules reset to default.', 'jetpack' );
1606
1607 break;
1608 default:
1609 return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) );
1610 }
1611
1612 return rest_ensure_response(
1613 array(
1614 'code' => 'success',
1615 'message' => $message,
1616 )
1617 );
1618 }
1619
1620 return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) );
1621 }
1622
1623 /**
1624 * Get the query parameters to update module options or general settings.
1625 *
1626 * @since 4.3.0
1627 * @since 4.4.0 Accepts a $selector parameter.
1628 *
1629 * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'.
1630 *
1631 * @return array
1632 */
1633 public static function get_updateable_parameters( $selector = '' ) {
1634 $parameters = array(
1635 'context' => array(
1636 'default' => 'edit',
1637 ),
1638 );
1639
1640 return array_merge( $parameters, self::get_updateable_data_list( $selector ) );
1641 }
1642
1643 /**
1644 * Returns a list of module options or general settings that can be updated.
1645 *
1646 * @since 4.3.0
1647 * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list.
1648 *
1649 * @param string|array $selector Module slug, 'any', or an array of parameters.
1650 * If empty, it's assumed we're updating a module and we'll try to get its slug.
1651 * If 'any' the full list is returned.
1652 * If it's an array of parameters, includes the elements by matching keys.
1653 *
1654 * @return array
1655 */
1656 public static function get_updateable_data_list( $selector = '' ) {
1657
1658 $options = array(
1659 // Blocks.
1660 'jetpack_blocks_disabled' => array(
1661 'description' => esc_html__( 'Jetpack Blocks disabled.', 'jetpack' ),
1662 'type' => 'boolean',
1663 'default' => false,
1664 'validate_callback' => __CLASS__ . '::validate_boolean',
1665 'jp_group' => 'settings',
1666 ),
1667
1668 // Carousel
1669 'carousel_background_color' => array(
1670 'description' => esc_html__( 'Color scheme.', 'jetpack' ),
1671 'type' => 'string',
1672 'default' => 'black',
1673 'enum' => array(
1674 'black',
1675 'white',
1676 ),
1677 'enum_labels' => array(
1678 'black' => esc_html__( 'Black', 'jetpack' ),
1679 'white' => esc_html__( 'White', 'jetpack' ),
1680 ),
1681 'validate_callback' => __CLASS__ . '::validate_list_item',
1682 'jp_group' => 'carousel',
1683 ),
1684 'carousel_display_exif' => array(
1685 'description' => wp_kses(
1686 sprintf( __( 'Show photo metadata (<a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ),
1687 array(
1688 'a' => array(
1689 'href' => true,
1690 'target' => true,
1691 ),
1692 )
1693 ),
1694 'type' => 'boolean',
1695 'default' => 0,
1696 'validate_callback' => __CLASS__ . '::validate_boolean',
1697 'jp_group' => 'carousel',
1698 ),
1699 'carousel_display_comments' => array(
1700 'description' => esc_html__( 'Show comments area in carousel', 'jetpack' ),
1701 'type' => 'boolean',
1702 'default' => 1,
1703 'validate_callback' => __CLASS__ . '::validate_boolean',
1704 'jp_group' => 'carousel',
1705 ),
1706
1707 // Comments.
1708 'highlander_comment_form_prompt' => array(
1709 'description' => esc_html__( 'Greeting Text', 'jetpack' ),
1710 'type' => 'string',
1711 'default' => esc_html__( 'Leave a Reply', 'jetpack' ),
1712 'sanitize_callback' => 'sanitize_text_field',
1713 'jp_group' => 'comments',
1714 ),
1715 'jetpack_comment_form_color_scheme' => array(
1716 'description' => esc_html__( 'Color scheme', 'jetpack' ),
1717 'type' => 'string',
1718 'default' => 'light',
1719 'enum' => array(
1720 'light',
1721 'dark',
1722 'transparent',
1723 ),
1724 'enum_labels' => array(
1725 'light' => esc_html__( 'Light', 'jetpack' ),
1726 'dark' => esc_html__( 'Dark', 'jetpack' ),
1727 'transparent' => esc_html__( 'Transparent', 'jetpack' ),
1728 ),
1729 'validate_callback' => __CLASS__ . '::validate_list_item',
1730 'jp_group' => 'comments',
1731 ),
1732
1733 // Custom Content Types.
1734 'jetpack_portfolio' => array(
1735 'description' => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1736 'type' => 'boolean',
1737 'default' => 0,
1738 'validate_callback' => __CLASS__ . '::validate_boolean',
1739 'jp_group' => 'settings',
1740 ),
1741 'jetpack_portfolio_posts_per_page' => array(
1742 'description' => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1743 'type' => 'integer',
1744 'default' => 10,
1745 'validate_callback' => __CLASS__ . '::validate_posint',
1746 'jp_group' => 'settings',
1747 ),
1748 'jetpack_testimonial' => array(
1749 'description' => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1750 'type' => 'boolean',
1751 'default' => 0,
1752 'validate_callback' => __CLASS__ . '::validate_boolean',
1753 'jp_group' => 'settings',
1754 ),
1755 'jetpack_testimonial_posts_per_page' => array(
1756 'description' => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1757 'type' => 'integer',
1758 'default' => 10,
1759 'validate_callback' => __CLASS__ . '::validate_posint',
1760 'jp_group' => 'settings',
1761 ),
1762 // WAF.
1763 'jetpack_waf_automatic_rules' => array(
1764 'description' => esc_html__( 'Enable automatic rules - Protect your site against untrusted traffic sources with automatic security rules.', 'jetpack' ),
1765 'type' => 'boolean',
1766 'default' => Waf_Compatibility::get_default_automatic_rules_option(),
1767 'validate_callback' => __CLASS__ . '::validate_boolean',
1768 'jp_group' => 'waf',
1769 ),
1770 'jetpack_waf_ip_block_list_enabled' => array(
1771 'description' => esc_html__( 'Block list - Block a specific request IP.', 'jetpack' ),
1772 'type' => 'boolean',
1773 'default' => 0,
1774 'validate_callback' => __CLASS__ . '::validate_boolean',
1775 'jp_group' => 'waf',
1776 ),
1777 'jetpack_waf_ip_block_list' => array(
1778 'description' => esc_html__( 'Blocked IP addresses', 'jetpack' ),
1779 'type' => 'string',
1780 'default' => '',
1781 'validate_callback' => __CLASS__ . '::validate_string',
1782 'sanitize_callback' => 'esc_textarea',
1783 'jp_group' => 'waf',
1784 ),
1785 'jetpack_waf_ip_allow_list_enabled' => array(
1786 'description' => esc_html__( 'Allow list - Allow a specific request IP.', 'jetpack' ),
1787 'type' => 'boolean',
1788 'default' => 0,
1789 'validate_callback' => __CLASS__ . '::validate_boolean',
1790 'jp_group' => 'settings',
1791 ),
1792 'jetpack_waf_ip_allow_list' => array(
1793 'description' => esc_html__( 'Always allowed IP addresses', 'jetpack' ),
1794 'type' => 'string',
1795 'default' => '',
1796 'validate_callback' => __CLASS__ . '::validate_string',
1797 'sanitize_callback' => 'esc_textarea',
1798 'jp_group' => 'settings',
1799 ),
1800 'jetpack_waf_share_data' => array(
1801 'description' => esc_html__( 'Share basic data with Jetpack.', 'jetpack' ),
1802 'type' => 'boolean',
1803 'default' => 0,
1804 'validate_callback' => __CLASS__ . '::validate_boolean',
1805 'jp_group' => 'waf',
1806 ),
1807 'jetpack_waf_share_debug_data' => array(
1808 'description' => esc_html__( 'Share detailed data with Jetpack.', 'jetpack' ),
1809 'type' => 'boolean',
1810 'default' => 0,
1811 'validate_callback' => __CLASS__ . '::validate_boolean',
1812 'jp_group' => 'waf',
1813 ),
1814 // Galleries.
1815 'tiled_galleries' => array(
1816 'description' => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1817 'type' => 'boolean',
1818 'default' => 0,
1819 'validate_callback' => __CLASS__ . '::validate_boolean',
1820 'jp_group' => 'tiled-gallery',
1821 ),
1822
1823 'gravatar_disable_hovercards' => array(
1824 'description' => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1825 'type' => 'string',
1826 'default' => 'enabled',
1827 // Not visible. This is used as the checkbox value.
1828 'enum' => array(
1829 'enabled',
1830 'disabled',
1831 ),
1832 'enum_labels' => array(
1833 'enabled' => esc_html__( 'Enabled', 'jetpack' ),
1834 'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1835 ),
1836 'validate_callback' => __CLASS__ . '::validate_list_item',
1837 'jp_group' => 'gravatar-hovercards',
1838 ),
1839
1840 // Infinite Scroll.
1841 'infinite_scroll' => array(
1842 'description' => esc_html__( 'To infinity and beyond', 'jetpack' ),
1843 'type' => 'boolean',
1844 'default' => 1,
1845 'validate_callback' => __CLASS__ . '::validate_boolean',
1846 'jp_group' => 'infinite-scroll',
1847 ),
1848 'infinite_scroll_google_analytics' => array(
1849 'description' => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1850 'type' => 'boolean',
1851 'default' => 0,
1852 'validate_callback' => __CLASS__ . '::validate_boolean',
1853 'jp_group' => 'infinite-scroll',
1854 ),
1855
1856 // Likes.
1857 'wpl_default' => array(
1858 'description' => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1859 'type' => 'string',
1860 'default' => 'on',
1861 'enum' => array(
1862 'on',
1863 'off',
1864 ),
1865 'enum_labels' => array(
1866 'on' => esc_html__( 'On for all posts', 'jetpack' ),
1867 'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1868 ),
1869 'validate_callback' => __CLASS__ . '::validate_list_item',
1870 'jp_group' => 'likes',
1871 ),
1872 'social_notifications_like' => array(
1873 'description' => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1874 'type' => 'boolean',
1875 'default' => 1,
1876 'validate_callback' => __CLASS__ . '::validate_boolean',
1877 'jp_group' => 'likes',
1878 ),
1879
1880 // Markdown.
1881 'wpcom_publish_comments_with_markdown' => array(
1882 'description' => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1883 'type' => 'boolean',
1884 'default' => 0,
1885 'validate_callback' => __CLASS__ . '::validate_boolean',
1886 'jp_group' => 'markdown',
1887 ),
1888 'wpcom_publish_posts_with_markdown' => array(
1889 'description' => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1890 'type' => 'boolean',
1891 'default' => 0,
1892 'validate_callback' => __CLASS__ . '::validate_boolean',
1893 'jp_group' => 'markdown',
1894 ),
1895
1896 // Monitor.
1897 'monitor_receive_notifications' => array(
1898 'description' => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1899 'type' => 'boolean',
1900 'default' => 0,
1901 'validate_callback' => __CLASS__ . '::validate_boolean',
1902 'jp_group' => 'monitor',
1903 ),
1904
1905 // Post by Email.
1906 'post_by_email_address' => array(
1907 'description' => esc_html__( 'Email Address', 'jetpack' ),
1908 'type' => 'string',
1909 'default' => 'noop',
1910 'enum' => array(
1911 'noop',
1912 'create',
1913 'regenerate',
1914 'delete',
1915 ),
1916 'enum_labels' => array(
1917 'noop' => '',
1918 'create' => esc_html__( 'Create Post by Email address', 'jetpack' ),
1919 'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1920 'delete' => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1921 ),
1922 'validate_callback' => __CLASS__ . '::validate_list_item',
1923 'jp_group' => 'post-by-email',
1924 ),
1925
1926 // Protect.
1927 'jetpack_protect_key' => array(
1928 'description' => esc_html__( 'Protect API key', 'jetpack' ),
1929 'type' => 'string',
1930 'default' => '',
1931 'validate_callback' => __CLASS__ . '::validate_alphanum',
1932 'jp_group' => 'protect',
1933 ),
1934 'jetpack_protect_global_whitelist' => array(
1935 'description' => esc_html__( 'Protect global IP allow list', 'jetpack' ),
1936 'type' => 'string',
1937 'default' => '',
1938 'validate_callback' => __CLASS__ . '::validate_string',
1939 'sanitize_callback' => 'esc_textarea',
1940 'jp_group' => 'protect',
1941 ),
1942
1943 // Sharing.
1944 'sharing_services' => array(
1945 'description' => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1946 'type' => 'object',
1947 'default' => array(
1948 'visible' => array( 'facebook', 'x' ),
1949 'hidden' => array(),
1950 ),
1951 'validate_callback' => __CLASS__ . '::validate_services',
1952 'jp_group' => 'sharedaddy',
1953 ),
1954 'button_style' => array(
1955 'description' => esc_html__( 'Button Style', 'jetpack' ),
1956 'type' => 'string',
1957 'default' => 'icon',
1958 'enum' => array(
1959 'icon-text',
1960 'icon',
1961 'text',
1962 'official',
1963 ),
1964 'enum_labels' => array(
1965 'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1966 'icon' => esc_html__( 'Icon only', 'jetpack' ),
1967 'text' => esc_html__( 'Text only', 'jetpack' ),
1968 'official' => esc_html__( 'Official buttons', 'jetpack' ),
1969 ),
1970 'validate_callback' => __CLASS__ . '::validate_list_item',
1971 'jp_group' => 'sharedaddy',
1972 ),
1973 'sharing_label' => array(
1974 'description' => esc_html__( 'Sharing Label', 'jetpack' ),
1975 'type' => 'string',
1976 'default' => '',
1977 'validate_callback' => __CLASS__ . '::validate_string',
1978 'sanitize_callback' => 'esc_html',
1979 'jp_group' => 'sharedaddy',
1980 ),
1981 'show' => array(
1982 'description' => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1983 'type' => 'array',
1984 'items' => array(
1985 'type' => 'string',
1986 ),
1987 'default' => array( 'post' ),
1988 'validate_callback' => __CLASS__ . '::validate_sharing_show',
1989 'jp_group' => 'sharedaddy',
1990 ),
1991 'jetpack-twitter-cards-site-tag' => array(
1992 'description' => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
1993 'type' => 'string',
1994 'default' => '',
1995 'validate_callback' => __CLASS__ . '::validate_twitter_username',
1996 'sanitize_callback' => 'esc_html',
1997 'jp_group' => 'sharedaddy',
1998 ),
1999 'sharedaddy_disable_resources' => array(
2000 'description' => esc_html__( 'Disable CSS and JS', 'jetpack' ),
2001 'type' => 'boolean',
2002 'default' => 0,
2003 'validate_callback' => __CLASS__ . '::validate_boolean',
2004 'jp_group' => 'sharedaddy',
2005 ),
2006 'custom' => array(
2007 'description' => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
2008 'type' => 'object',
2009 'default' => array(
2010 'sharing_name' => '',
2011 'sharing_url' => '',
2012 'sharing_icon' => '',
2013 ),
2014 'validate_callback' => __CLASS__ . '::validate_custom_service',
2015 'jp_group' => 'sharedaddy',
2016 ),
2017 // Not an option, but an action that can be performed on the list of custom services passing the service ID.
2018 'sharing_delete_service' => array(
2019 'description' => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
2020 'type' => 'string',
2021 'default' => '',
2022 'validate_callback' => __CLASS__ . '::validate_custom_service_id',
2023 'jp_group' => 'sharedaddy',
2024 ),
2025
2026 // SSO.
2027 'jetpack_sso_require_two_step' => array(
2028 'description' => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
2029 'type' => 'boolean',
2030 'default' => SSO\Helpers::is_require_two_step_checkbox_disabled(),
2031 'validate_callback' => __CLASS__ . '::validate_boolean',
2032 'jp_group' => 'sso',
2033 ),
2034 'jetpack_sso_match_by_email' => array(
2035 'description' => esc_html__( 'Match by Email', 'jetpack' ),
2036 'type' => 'boolean',
2037 'default' => 1,
2038 'validate_callback' => __CLASS__ . '::validate_boolean',
2039 'jp_group' => 'sso',
2040 ),
2041
2042 // Subscriptions.
2043 'stb_enabled' => array(
2044 'description' => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ),
2045 'type' => 'boolean',
2046 'default' => 1,
2047 'validate_callback' => __CLASS__ . '::validate_boolean',
2048 'jp_group' => 'subscriptions',
2049 ),
2050 'stc_enabled' => array(
2051 'description' => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
2052 'type' => 'boolean',
2053 'default' => 1,
2054 'validate_callback' => __CLASS__ . '::validate_boolean',
2055 'jp_group' => 'subscriptions',
2056 ),
2057 'wpcom_newsletter_categories' => array(
2058 'description' => esc_html__( 'Array of post category ids that are marked as newsletter categories', 'jetpack' ),
2059 'type' => 'array',
2060 'default' => array(),
2061 'validate_callback' => __CLASS__ . '::validate_array',
2062 'jp_group' => 'subscriptions',
2063 ),
2064 'wpcom_newsletter_categories_enabled' => array(
2065 'description' => esc_html__( 'Whether the newsletter categories are enabled or not', 'jetpack' ),
2066 'type' => 'boolean',
2067 'default' => 0,
2068 'validate_callback' => __CLASS__ . '::validate_boolean',
2069 'jp_group' => 'subscriptions',
2070 ),
2071 'wpcom_newsletter_send_default' => array(
2072 'description' => esc_html__( 'Whether to send newsletter emails by default when publishing a post', 'jetpack' ),
2073 'type' => 'boolean',
2074 'default' => 1,
2075 'validate_callback' => __CLASS__ . '::validate_boolean',
2076 'jp_group' => 'subscriptions',
2077 ),
2078 'wpcom_featured_image_in_email' => array(
2079 'description' => esc_html__( 'Whether to include the featured image in the email or not', 'jetpack' ),
2080 'type' => 'boolean',
2081 'default' => 0,
2082 'validate_callback' => __CLASS__ . '::validate_boolean',
2083 'jp_group' => 'subscriptions',
2084 ),
2085 'jetpack_gravatar_in_email' => array(
2086 'description' => esc_html__( 'Whether to show author avatar in the email byline', 'jetpack' ),
2087 'type' => 'boolean',
2088 'default' => 1,
2089 'validate_callback' => __CLASS__ . '::validate_boolean',
2090 'jp_group' => 'subscriptions',
2091 ),
2092 'jetpack_author_in_email' => array(
2093 'description' => esc_html__( 'Whether to show author display name in the email byline', 'jetpack' ),
2094 'type' => 'boolean',
2095 'default' => 1,
2096 'validate_callback' => __CLASS__ . '::validate_boolean',
2097 'jp_group' => 'subscriptions',
2098 ),
2099 'jetpack_post_date_in_email' => array(
2100 'description' => esc_html__( 'Whether to show date in the email byline', 'jetpack' ),
2101 'type' => 'boolean',
2102 'default' => 1,
2103 'validate_callback' => __CLASS__ . '::validate_boolean',
2104 'jp_group' => 'subscriptions',
2105 ),
2106 'wpcom_subscription_emails_use_excerpt' => array(
2107 'description' => esc_html__( 'Whether to use the excerpt in the email or not', 'jetpack' ),
2108 'type' => 'boolean',
2109 'default' => 0,
2110 'validate_callback' => __CLASS__ . '::validate_boolean',
2111 'jp_group' => 'subscriptions',
2112 ),
2113 'jetpack_subscriptions_reply_to' => array(
2114 'description' => esc_html__( 'Reply to email behaviour for newsletters emails', 'jetpack' ),
2115 'type' => 'string',
2116 'default' => Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to,
2117 'validate_callback' => __CLASS__ . '::validate_subscriptions_reply_to',
2118 'jp_group' => 'subscriptions',
2119 ),
2120 'jetpack_subscriptions_from_name' => array(
2121 'description' => esc_html__( 'From name for newsletters emails', 'jetpack' ),
2122 'type' => 'string',
2123 'default' => '',
2124 'validate_callback' => __CLASS__ . '::validate_subscriptions_reply_to_name',
2125 'jp_group' => 'subscriptions',
2126 ),
2127 'sm_enabled' => array(
2128 'description' => esc_html__( 'Show popup Subscribe modal to readers.', 'jetpack' ),
2129 'type' => 'boolean',
2130 'default' => 0,
2131 'validate_callback' => __CLASS__ . '::validate_boolean',
2132 'jp_group' => 'subscriptions',
2133 ),
2134 'jetpack_subscribe_overlay_enabled' => array(
2135 'description' => esc_html__( 'Show subscribe overlay on homepage.', 'jetpack' ),
2136 'type' => 'boolean',
2137 'default' => 0,
2138 'validate_callback' => __CLASS__ . '::validate_boolean',
2139 'jp_group' => 'subscriptions',
2140 ),
2141 'jetpack_subscribe_floating_button_enabled' => array(
2142 'description' => esc_html__( 'Show a floating subscribe button.', 'jetpack' ),
2143 'type' => 'boolean',
2144 'default' => 0,
2145 'validate_callback' => __CLASS__ . '::validate_boolean',
2146 'jp_group' => 'subscriptions',
2147 ),
2148 'jetpack_subscriptions_subscribe_post_end_enabled' => array(
2149 'description' => esc_html__( 'Add Subscribe block at the end of each post.', 'jetpack' ),
2150 'type' => 'boolean',
2151 'default' => 0,
2152 'validate_callback' => __CLASS__ . '::validate_boolean',
2153 'jp_group' => 'subscriptions',
2154 ),
2155 'jetpack_subscriptions_login_navigation_enabled' => array(
2156 'description' => esc_html__( 'Add Subscriber Login block to the navigation.', 'jetpack' ),
2157 'type' => 'boolean',
2158 'default' => 0,
2159 'validate_callback' => __CLASS__ . '::validate_boolean',
2160 'jp_group' => 'subscriptions',
2161 ),
2162 'jetpack_subscriptions_subscribe_navigation_enabled' => array(
2163 'description' => esc_html__( 'Add Subscribe block to the navigation.', 'jetpack' ),
2164 'type' => 'boolean',
2165 'default' => 0,
2166 'validate_callback' => __CLASS__ . '::validate_boolean',
2167 'jp_group' => 'subscriptions',
2168 ),
2169 'social_notifications_subscribe' => array(
2170 'description' => esc_html__( 'Send email notification when someone subscribes to my blog', 'jetpack' ),
2171 'type' => 'boolean',
2172 'default' => 0,
2173 'validate_callback' => __CLASS__ . '::validate_boolean',
2174 'jp_group' => 'subscriptions',
2175 ),
2176 'subscription_options' => array(
2177 'description' => esc_html__( 'Options used in subscription email templates and the Subscribe block: \'invitation\', \'welcome\', \'comment_follow\', \'subscribe_modal_heading\', \'free_tier_description\' and \'hide_free_tier\'.', 'jetpack' ),
2178 'type' => 'object',
2179 'default' => array(
2180 'invitation' => '',
2181 'welcome' => '',
2182 'comment_follow' => '',
2183 'subscribe_modal_heading' => '',
2184 'free_tier_description' => '',
2185 'hide_free_tier' => false,
2186 ),
2187 'validate_callback' => __CLASS__ . '::validate_subscription_options',
2188 'jp_group' => 'subscriptions',
2189 ),
2190
2191 // Related Posts.
2192 'show_headline' => array(
2193 'description' => esc_html__( 'Highlight related content with a heading', 'jetpack' ),
2194 'type' => 'boolean',
2195 'default' => 1,
2196 'validate_callback' => __CLASS__ . '::validate_boolean',
2197 'jp_group' => 'related-posts',
2198 ),
2199 'show_thumbnails' => array(
2200 'description' => esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
2201 'type' => 'boolean',
2202 'default' => 0,
2203 'validate_callback' => __CLASS__ . '::validate_boolean',
2204 'jp_group' => 'related-posts',
2205 ),
2206
2207 // Search.
2208 'instant_search_enabled' => array(
2209 'description' => esc_html__( 'Enable Instant Search', 'jetpack' ),
2210 'type' => 'boolean',
2211 'default' => 0,
2212 'validate_callback' => __CLASS__ . '::validate_boolean',
2213 'jp_group' => 'search',
2214 ),
2215
2216 'has_jetpack_search_product' => array(
2217 'description' => esc_html__( 'Has an active Jetpack Search product purchase', 'jetpack' ),
2218 'type' => 'boolean',
2219 'default' => 0,
2220 'validate_callback' => __CLASS__ . '::validate_boolean',
2221 'jp_group' => 'settings',
2222 ),
2223
2224 'search_auto_config' => array(
2225 'description' => esc_html__( 'Trigger an auto config of instant search', 'jetpack' ),
2226 'type' => 'boolean',
2227 'default' => 0,
2228 'validate_callback' => __CLASS__ . '::validate_boolean',
2229 'jp_group' => 'search',
2230 ),
2231
2232 // Verification Tools.
2233 'google' => array(
2234 'description' => esc_html__( 'Google Search Console', 'jetpack' ),
2235 'type' => 'string',
2236 'default' => '',
2237 'validate_callback' => __CLASS__ . '::validate_verification_service',
2238 'jp_group' => 'verification-tools',
2239 ),
2240 'bing' => array(
2241 'description' => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
2242 'type' => 'string',
2243 'default' => '',
2244 'validate_callback' => __CLASS__ . '::validate_verification_service',
2245 'jp_group' => 'verification-tools',
2246 ),
2247 'pinterest' => array(
2248 'description' => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
2249 'type' => 'string',
2250 'default' => '',
2251 'validate_callback' => __CLASS__ . '::validate_verification_service',
2252 'jp_group' => 'verification-tools',
2253 ),
2254 'yandex' => array(
2255 'description' => esc_html__( 'Yandex Site Verification', 'jetpack' ),
2256 'type' => 'string',
2257 'default' => '',
2258 'validate_callback' => __CLASS__ . '::validate_verification_service',
2259 'jp_group' => 'verification-tools',
2260 ),
2261 'facebook' => array(
2262 'description' => esc_html__( 'Facebook Domain Verification', 'jetpack' ),
2263 'type' => 'string',
2264 'default' => '',
2265 'validate_callback' => __CLASS__ . '::validate_verification_service',
2266 'jp_group' => 'verification-tools',
2267 ),
2268
2269 // WordAds.
2270 'enable_header_ad' => array(
2271 'description' => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
2272 'type' => 'boolean',
2273 'default' => 1,
2274 'validate_callback' => __CLASS__ . '::validate_boolean',
2275 'jp_group' => 'wordads',
2276 ),
2277 'wordads_approved' => array(
2278 'description' => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
2279 'type' => 'boolean',
2280 'default' => 0,
2281 'validate_callback' => __CLASS__ . '::validate_boolean',
2282 'jp_group' => 'wordads',
2283 ),
2284 'wordads_second_belowpost' => array(
2285 'description' => esc_html__( 'Display second ad below post?', 'jetpack' ),
2286 'type' => 'boolean',
2287 'default' => 1,
2288 'validate_callback' => __CLASS__ . '::validate_boolean',
2289 'jp_group' => 'wordads',
2290 ),
2291 'wordads_inline_enabled' => array(
2292 'description' => esc_html__( 'Display inline ad within post content?', 'jetpack' ),
2293 'type' => 'boolean',
2294 'default' => 1,
2295 'validate_callback' => __CLASS__ . '::validate_boolean',
2296 'jp_group' => 'wordads',
2297 ),
2298 'wordads_display_front_page' => array(
2299 'description' => esc_html__( 'Display ads on the front page?', 'jetpack' ),
2300 'type' => 'boolean',
2301 'default' => 1,
2302 'validate_callback' => __CLASS__ . '::validate_boolean',
2303 'jp_group' => 'wordads',
2304 ),
2305 'wordads_display_post' => array(
2306 'description' => esc_html__( 'Display ads on posts?', 'jetpack' ),
2307 'type' => 'boolean',
2308 'default' => 1,
2309 'validate_callback' => __CLASS__ . '::validate_boolean',
2310 'jp_group' => 'wordads',
2311 ),
2312 'wordads_display_page' => array(
2313 'description' => esc_html__( 'Display ads on pages?', 'jetpack' ),
2314 'type' => 'boolean',
2315 'default' => 1,
2316 'validate_callback' => __CLASS__ . '::validate_boolean',
2317 'jp_group' => 'wordads',
2318 ),
2319 'wordads_display_archive' => array(
2320 'description' => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
2321 'type' => 'boolean',
2322 'default' => 1,
2323 'validate_callback' => __CLASS__ . '::validate_boolean',
2324 'jp_group' => 'wordads',
2325 ),
2326 'wordads_custom_adstxt_enabled' => array(
2327 'description' => esc_html__( 'Custom ads.txt', 'jetpack' ),
2328 'type' => 'boolean',
2329 'default' => 0,
2330 'validate_callback' => __CLASS__ . '::validate_boolean',
2331 'jp_group' => 'wordads',
2332 ),
2333 'wordads_custom_adstxt' => array(
2334 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
2335 'type' => 'string',
2336 'default' => '',
2337 'validate_callback' => __CLASS__ . '::validate_string',
2338 'sanitize_callback' => 'sanitize_textarea_field',
2339 'jp_group' => 'wordads',
2340 ),
2341 'wordads_ccpa_enabled' => array(
2342 'description' => esc_html__( 'Enable support for California Consumer Privacy Act', 'jetpack' ),
2343 'type' => 'boolean',
2344 'default' => 0,
2345 'validate_callback' => __CLASS__ . '::validate_boolean',
2346 'jp_group' => 'wordads',
2347 ),
2348 'wordads_ccpa_privacy_policy_url' => array(
2349 'description' => esc_html__( 'Privacy Policy URL', 'jetpack' ),
2350 'type' => 'string',
2351 'default' => '',
2352 'validate_callback' => __CLASS__ . '::validate_string',
2353 'sanitize_callback' => 'sanitize_text_field',
2354 'jp_group' => 'wordads',
2355 ),
2356 'wordads_cmp_enabled' => array(
2357 'description' => esc_html__( 'Enable GDPR Consent Management Banner for WordAds', 'jetpack' ),
2358 'type' => 'boolean',
2359 'default' => 0,
2360 'validate_callback' => __CLASS__ . '::validate_boolean',
2361 'jp_group' => 'wordads',
2362 ),
2363
2364 // Google Analytics.
2365 'google_analytics_tracking_id' => array(
2366 'description' => esc_html__( 'Google Analytics', 'jetpack' ),
2367 'type' => 'string',
2368 'default' => '',
2369 'validate_callback' => __CLASS__ . '::validate_alphanum',
2370 'jp_group' => 'google-analytics',
2371 ),
2372 'jetpack_wga' => array(
2373 'description' => esc_html__( 'Google Analytics', 'jetpack' ),
2374 'type' => 'object',
2375 'jp_group' => 'settings',
2376 ),
2377
2378 // Stats.
2379 'admin_bar' => array(
2380 'description' => esc_html__( 'Include a small chart in your admin bar with a 48-hour traffic snapshot.', 'jetpack' ),
2381 'type' => 'boolean',
2382 'default' => 1,
2383 'validate_callback' => __CLASS__ . '::validate_boolean',
2384 'jp_group' => 'stats',
2385 ),
2386 'enable_odyssey_stats' => array(
2387 'description' => esc_html__( 'Preview the new Jetpack Stats experience (Experimental).', 'jetpack' ),
2388 'type' => 'boolean',
2389 'default' => 1,
2390 'validate_callback' => __CLASS__ . '::validate_boolean',
2391 'jp_group' => 'stats',
2392 ),
2393 'roles' => array(
2394 'description' => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
2395 'type' => 'array',
2396 'items' => array(
2397 'type' => 'string',
2398 ),
2399 'default' => array( 'administrator' ),
2400 'validate_callback' => __CLASS__ . '::validate_stats_roles',
2401 'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
2402 'jp_group' => 'stats',
2403 ),
2404 'count_roles' => array(
2405 'description' => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
2406 'type' => 'array',
2407 'items' => array(
2408 'type' => 'string',
2409 ),
2410 'default' => array( 'administrator' ),
2411 'validate_callback' => __CLASS__ . '::validate_stats_roles',
2412 'jp_group' => 'stats',
2413 ),
2414 'blog_id' => array(
2415 'description' => esc_html__( 'Blog ID.', 'jetpack' ),
2416 'type' => 'boolean',
2417 'default' => 0,
2418 'validate_callback' => __CLASS__ . '::validate_boolean',
2419 'jp_group' => 'stats',
2420 ),
2421 'do_not_track' => array(
2422 'description' => esc_html__( 'Do not track.', 'jetpack' ),
2423 'type' => 'boolean',
2424 'default' => 1,
2425 'validate_callback' => __CLASS__ . '::validate_boolean',
2426 'jp_group' => 'stats',
2427 ),
2428 'version' => array(
2429 'description' => esc_html__( 'Version.', 'jetpack' ),
2430 'type' => 'integer',
2431 'default' => 9,
2432 'validate_callback' => __CLASS__ . '::validate_posint',
2433 'jp_group' => 'stats',
2434 ),
2435 'collapse_nudges' => array(
2436 'description' => esc_html__( 'Collapse upgrade nudges', 'jetpack' ),
2437 'type' => 'boolean',
2438 'default' => 0,
2439 'validate_callback' => __CLASS__ . '::validate_boolean',
2440 'jp_group' => 'stats',
2441 ),
2442
2443 // Whether to share stats views with WordPress.com Reader.
2444 'wpcom_reader_views_enabled' => array(
2445 'description' => esc_html__( 'Show post views in the WordPress.com Reader.', 'jetpack' ),
2446 'type' => 'boolean',
2447 'default' => 1,
2448 'validate_callback' => __CLASS__ . '::validate_boolean',
2449 'jp_group' => 'settings',
2450 ),
2451
2452 // Akismet - Not a module, but a plugin. The options can be passed and handled differently.
2453 'akismet_show_user_comments_approved' => array(
2454 'description' => '',
2455 'type' => 'boolean',
2456 'default' => 0,
2457 'validate_callback' => __CLASS__ . '::validate_boolean',
2458 'jp_group' => 'settings',
2459 ),
2460
2461 'wordpress_api_key' => array(
2462 'description' => '',
2463 'type' => 'string',
2464 'default' => '',
2465 'validate_callback' => __CLASS__ . '::validate_alphanum',
2466 'jp_group' => 'settings',
2467 ),
2468
2469 // Empty stats card dismiss.
2470 'dismiss_empty_stats_card' => array(
2471 'description' => '',
2472 'type' => 'boolean',
2473 'default' => 0,
2474 'validate_callback' => __CLASS__ . '::validate_boolean',
2475 'jp_group' => 'settings',
2476 ),
2477
2478 // Backup Getting Started card on dashboard.
2479 'dismiss_dash_backup_getting_started' => array(
2480 'description' => '',
2481 'type' => 'boolean',
2482 'default' => 0,
2483 'validate_callback' => __CLASS__ . '::validate_boolean',
2484 'jp_group' => 'settings',
2485 ),
2486
2487 // Agencies Learn More card on dashboard.
2488 'dismiss_dash_agencies_learn_more' => array(
2489 'description' => '',
2490 'type' => 'boolean',
2491 'default' => 0,
2492 'validate_callback' => __CLASS__ . '::validate_boolean',
2493 'jp_group' => 'settings',
2494 ),
2495
2496 'lang_id' => array(
2497 'description' => esc_html__( 'Primary language for the site.', 'jetpack' ),
2498 'type' => 'string',
2499 'default' => 'en_US',
2500 'jp_group' => 'settings',
2501 ),
2502
2503 // SEO Tools.
2504 'advanced_seo_front_page_description' => array(
2505 'description' => esc_html__( 'Front page meta description.', 'jetpack' ),
2506 'type' => 'string',
2507 'default' => '',
2508 'sanitize_callback' => 'Jetpack_SEO_Utils::sanitize_front_page_meta_description',
2509 'jp_group' => 'seo-tools',
2510 ),
2511
2512 'advanced_seo_title_formats' => array(
2513 'description' => esc_html__( 'SEO page title structures.', 'jetpack' ),
2514 'type' => 'object',
2515 'default' => array(
2516 'archives' => array(),
2517 'front_page' => array(),
2518 'groups' => array(),
2519 'pages' => array(),
2520 'posts' => array(),
2521 ),
2522 'jp_group' => 'seo-tools',
2523 'validate_callback' => 'Jetpack_SEO_Titles::are_valid_title_formats',
2524 'sanitize_callback' => 'Jetpack_SEO_Titles::sanitize_title_formats',
2525 ),
2526
2527 // AI tab (Jetpack > SEO). Plain option the SEO package reads to serve
2528 // /llms.txt. The front-end behavior is gated inside the package; this
2529 // only round-trips the persisted state alongside the other seo-tools
2530 // settings.
2531 'jetpack_seo_llms_txt_enabled' => array(
2532 'description' => esc_html__( 'Generate an llms.txt file to guide AI assistants around your content.', 'jetpack' ),
2533 'type' => 'boolean',
2534 'default' => 0,
2535 'validate_callback' => __CLASS__ . '::validate_boolean',
2536 'jp_group' => 'seo-tools',
2537 ),
2538
2539 // AI tab (Jetpack > SEO). Sparse per-crawler override map the SEO
2540 // package reads to emit robots.txt directives for blocked AI crawlers.
2541 // Stored as `slug => bool` (true = blocked); catalog validation and
2542 // default-pruning happen in `Ai_Crawlers::get_overrides()`.
2543 'jetpack_seo_ai_crawler_overrides' => array(
2544 'description' => esc_html__( 'AI crawler allow/block overrides.', 'jetpack' ),
2545 'type' => 'object',
2546 'default' => array(),
2547 'jp_group' => 'seo-tools',
2548 'sanitize_callback' => __CLASS__ . '::sanitize_ai_crawler_overrides',
2549 ),
2550
2551 // VideoPress.
2552 'videopress_private_enabled_for_site' => array(
2553 'description' => esc_html__( 'Video Privacy: Restrict views to members of this site', 'jetpack' ),
2554 'type' => 'boolean',
2555 'default' => 0,
2556 'validate_callback' => __CLASS__ . '::validate_boolean',
2557 'jp_group' => 'videopress',
2558 ),
2559 );
2560
2561 // SEO Tools - SEO Enhancer.
2562 // TODO: move this to the main options array? The filter was there while developing the feature.
2563 // It might come in handy to hold its availability behind the filter since it still depends on AI to be available.
2564 if ( apply_filters( 'ai_seo_enhancer_enabled', true ) ) {
2565 $options['ai_seo_enhancer_enabled'] = array(
2566 'description' => esc_html__( 'Automatically generate SEO title, SEO description, and image alt text for new posts.', 'jetpack' ),
2567 'type' => 'boolean',
2568 'default' => 0,
2569 'validate_callback' => __CLASS__ . '::validate_boolean',
2570 'jp_group' => 'seo-tools',
2571 );
2572 }
2573
2574 // Add modules to list so they can be toggled.
2575 $modules = Jetpack::get_available_modules();
2576 if ( is_array( $modules ) && ! empty( $modules ) ) {
2577 $module_args = array(
2578 'description' => '',
2579 'type' => 'boolean',
2580 'default' => 0,
2581 'validate_callback' => __CLASS__ . '::validate_boolean',
2582 'jp_group' => 'modules',
2583 );
2584 foreach ( $modules as $module ) {
2585 $options[ $module ] = $module_args;
2586 }
2587 }
2588
2589 if ( is_array( $selector ) ) {
2590
2591 // Return only those options whose keys match $selector keys.
2592 return array_intersect_key( $options, $selector );
2593 }
2594
2595 if ( 'any' === $selector ) {
2596
2597 // Toggle module or update any module option or any general setting.
2598 return $options;
2599 }
2600
2601 // We're updating the options for a single module.
2602 if ( empty( $selector ) ) {
2603 $selector = self::get_module_requested();
2604 }
2605 $selected = array();
2606 foreach ( $options as $option => $attributes ) {
2607
2608 // Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
2609 if ( $selector === $attributes['jp_group'] ) {
2610 $selected[ $option ] = $attributes;
2611 }
2612 }
2613 return $selected;
2614 }
2615
2616 /**
2617 * Validates that the parameters are proper values that can be set during Jetpack onboarding.
2618 *
2619 * @since 5.4.0
2620 *
2621 * @deprecated since 13.9
2622 *
2623 * @param array $onboarding_data Values to check.
2624 * @param WP_REST_Request $request The request sent to the WP REST API.
2625 * @param string $param Name of the parameter passed to endpoint holding $value.
2626 *
2627 * @return bool|WP_Error
2628 */
2629 public static function validate_onboarding( $onboarding_data, $request, $param ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
2630 _deprecated_function( __METHOD__, '13.9' );
2631 return true;
2632 }
2633
2634 /**
2635 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
2636 *
2637 * @since 4.3.0
2638 *
2639 * @param string|bool $value Value to check.
2640 * @param WP_REST_Request $request The request sent to the WP REST API.
2641 * @param string $param Name of the parameter passed to endpoint holding $value.
2642 *
2643 * @return bool|WP_Error
2644 */
2645 public static function validate_boolean( $value, $request, $param ) {
2646 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- Other code depends on loose comparison here.
2647 if ( ! is_bool( $value ) && ! ( ctype_digit( (string) $value ) && in_array( $value, array( 0, 1 ) ) ) ) {
2648 return new WP_Error(
2649 'invalid_param',
2650 sprintf(
2651 /* Translators: Placeholder is a parameter name. */
2652 esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ),
2653 $param
2654 )
2655 );
2656 }
2657 return true;
2658 }
2659
2660 /**
2661 * Validates that the parameter is a positive integer.
2662 *
2663 * @since 4.3.0
2664 *
2665 * @param int $value Value to check.
2666 * @param WP_REST_Request $request The request sent to the WP REST API.
2667 * @param string $param Name of the parameter passed to endpoint holding $value.
2668 *
2669 * @return bool|WP_Error
2670 */
2671 public static function validate_posint( $value, $request, $param ) {
2672 if ( ! is_numeric( $value ) || $value <= 0 ) {
2673 return new WP_Error(
2674 'invalid_param',
2675 sprintf(
2676 /* Translators: Placeholder is a parameter name. */
2677 esc_html__( '%s must be a positive integer.', 'jetpack' ),
2678 $param
2679 )
2680 );
2681 }
2682 return true;
2683 }
2684
2685 /**
2686 * Validates that the parameter is a non-negative integer (includes 0).
2687 *
2688 * @since 10.4.0
2689 *
2690 * @param int $value Value to check.
2691 * @param WP_REST_Request $request The request sent to the WP REST API.
2692 * @param string $param Name of the parameter passed to endpoint holding $value.
2693 *
2694 * @return bool|WP_Error
2695 */
2696 public static function validate_non_neg_int( $value, $request, $param ) {
2697 if ( ! is_numeric( $value ) || $value < 0 ) {
2698 return new WP_Error(
2699 'invalid_param',
2700 /* translators: %s: The literal parameter name. Should not be translated. */
2701 sprintf( esc_html__( '%s must be a non-negative integer.', 'jetpack' ), $param )
2702 );
2703 }
2704 return true;
2705 }
2706
2707 /**
2708 * Validates that the parameter belongs to a list of admitted values.
2709 *
2710 * @since 4.3.0
2711 *
2712 * @param string $value Value to check.
2713 * @param WP_REST_Request $request The request sent to the WP REST API.
2714 * @param string $param Name of the parameter passed to endpoint holding $value.
2715 *
2716 * @return bool|WP_Error
2717 */
2718 public static function validate_list_item( $value, $request, $param ) {
2719 $attributes = $request->get_attributes();
2720 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
2721 return new WP_Error(
2722 'invalid_param',
2723 sprintf(
2724 /* Translators: Placeholder is a parameter name. */
2725 esc_html__( '%s not recognized', 'jetpack' ),
2726 $param
2727 )
2728 );
2729 }
2730 $args = $attributes['args'][ $param ];
2731 if ( ! empty( $args['enum'] ) ) {
2732 // If it's an associative array, use the keys to check that the value is among those admitted.
2733 $enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 )
2734 ? array_keys( $args['enum'] )
2735 : $args['enum'];
2736 $enum = array_map( 'strval', $enum );
2737 if ( ! in_array( $value, $enum, true ) ) {
2738 return new WP_Error(
2739 'invalid_param_value',
2740 sprintf(
2741 /* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
2742 esc_html__( '%1$s must be one of %2$s', 'jetpack' ),
2743 $param,
2744 implode( ', ', $enum )
2745 )
2746 );
2747 }
2748 }
2749 return true;
2750 }
2751
2752 /**
2753 * Validates that the parameter belongs to a list of admitted values.
2754 *
2755 * @since 4.3.0
2756 *
2757 * @param string $value Value to check.
2758 * @param WP_REST_Request $request The request sent to the WP REST API.
2759 * @param string $param Name of the parameter passed to endpoint holding $value.
2760 *
2761 * @return bool|WP_Error
2762 */
2763 public static function validate_module_list( $value, $request, $param ) {
2764 if ( ! is_array( $value ) ) {
2765 return new WP_Error(
2766 'invalid_param_value',
2767 sprintf(
2768 /* Translators: Placeholder is a parameter name. */
2769 esc_html__( '%s must be an array', 'jetpack' ),
2770 $param
2771 )
2772 );
2773 }
2774
2775 $modules = Jetpack::get_available_modules();
2776
2777 if ( count( array_intersect( $value, $modules ) ) !== count( $value ) ) {
2778 return new WP_Error(
2779 'invalid_param_value',
2780 sprintf(
2781 /* Translators: Placeholder is a parameter name. */
2782 esc_html__( '%s must be a list of valid modules', 'jetpack' ),
2783 $param
2784 )
2785 );
2786 }
2787
2788 return true;
2789 }
2790
2791 /**
2792 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
2793 *
2794 * @since 4.3.0
2795 *
2796 * @param string $value Value to check.
2797 * @param WP_REST_Request $request The request sent to the WP REST API.
2798 * @param string $param Name of the parameter passed to endpoint holding $value.
2799 *
2800 * @return bool|WP_Error
2801 */
2802 public static function validate_alphanum( $value, $request, $param ) {
2803 if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
2804 return new WP_Error(
2805 'invalid_param',
2806 sprintf(
2807 /* Translators: Placeholder is a parameter name. */
2808 esc_html__( '%s must be an alphanumeric string.', 'jetpack' ),
2809 $param
2810 )
2811 );
2812 }
2813 return true;
2814 }
2815
2816 /**
2817 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
2818 *
2819 * @since 4.6.0
2820 *
2821 * @param string $value Value to check.
2822 * @param WP_REST_Request $request The request sent to the WP REST API.
2823 * @param string $param Name of the parameter passed to endpoint holding $value.
2824 *
2825 * @return bool|WP_Error
2826 */
2827 public static function validate_verification_service( $value, $request, $param ) {
2828 if ( ! empty( $value ) && ( ! is_string( $value ) || false === jetpack_verification_validate_code( $value ) ) ) {
2829 return new WP_Error(
2830 'invalid_param',
2831 sprintf(
2832 /* Translators: Placeholder is a verification string used to verify a service like Google Webmaster Console. */
2833 esc_html__( '%s must be a valid verification code or verification tag.', 'jetpack' ),
2834 $param
2835 )
2836 );
2837 }
2838 return true;
2839 }
2840
2841 /**
2842 * Validates that the parameter is among the roles allowed for Stats.
2843 *
2844 * @since 4.3.0
2845 *
2846 * @param mixed $value Value to check.
2847 * @param WP_REST_Request $request The request sent to the WP REST API.
2848 * @param string $param Name of the parameter passed to endpoint holding $value.
2849 *
2850 * @return bool|WP_Error
2851 */
2852 public static function validate_stats_roles( $value, $request, $param ) {
2853 // An empty value clears the setting; sanitize_stats_allowed_roles() falls back to 'administrator'.
2854 if ( empty( $value ) ) {
2855 return true;
2856 }
2857
2858 // Enforce the schema's list-of-strings contract before array_intersect() below sees the value.
2859 if ( ! is_array( $value ) || count( array_filter( $value, 'is_string' ) ) !== count( $value ) ) {
2860 return new WP_Error(
2861 'invalid_param',
2862 sprintf(
2863 /* Translators: Placeholder is a parameter name. */
2864 esc_html__( '%s must be an array of user roles.', 'jetpack' ),
2865 $param
2866 )
2867 );
2868 }
2869
2870 if ( ! function_exists( 'get_editable_roles' ) ) {
2871 require_once ABSPATH . 'wp-admin/includes/user.php';
2872 }
2873 $editable_roles = array_keys( get_editable_roles() );
2874 if ( ! array_intersect( $editable_roles, $value ) ) {
2875 return new WP_Error(
2876 'invalid_param',
2877 sprintf(
2878 /* 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. */
2879 esc_html__( '%1$s must be %2$s.', 'jetpack' ),
2880 $param,
2881 implode( ', ', $editable_roles )
2882 )
2883 );
2884 }
2885 return true;
2886 }
2887
2888 /**
2889 * Validates that the parameter is among the views where the Sharing can be displayed.
2890 *
2891 * @since 4.3.0
2892 *
2893 * @param string|bool $value Value to check.
2894 * @param WP_REST_Request $request The request sent to the WP REST API.
2895 * @param string $param Name of the parameter passed to endpoint holding $value.
2896 *
2897 * @return bool|WP_Error
2898 */
2899 public static function validate_sharing_show( $value, $request, $param ) {
2900 $views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
2901 if ( ! is_array( $value ) ) {
2902 return new WP_Error(
2903 'invalid_param',
2904 sprintf(
2905 /* Translators: Placeholder is a parameter name. */
2906 esc_html__( '%s must be an array of post types.', 'jetpack' ),
2907 $param
2908 )
2909 );
2910 }
2911 if ( ! array_intersect( $views, $value ) ) {
2912 return new WP_Error(
2913 'invalid_param',
2914 sprintf(
2915 /* 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 */
2916 esc_html__( '%1$s must be %2$s.', 'jetpack' ),
2917 $param,
2918 implode( ', ', $views )
2919 )
2920 );
2921 }
2922 return true;
2923 }
2924
2925 /**
2926 * Validates that the parameter is among the valid reply-to types for subscriptions.
2927 *
2928 * @since 4.3.0
2929 *
2930 * @param string|bool $value Value to check.
2931 * @param WP_REST_Request $request The request sent to the WP REST API.
2932 * @param string $param Name of the parameter passed to endpoint holding $value.
2933 *
2934 * @return bool|WP_Error
2935 */
2936 public static function validate_subscriptions_reply_to( $value, $request, $param ) {
2937 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
2938 if ( ! empty( $value ) && ! Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value ) ) {
2939 return new WP_Error(
2940 'invalid_param',
2941 sprintf(
2942 /* Translators: Placeholder is a parameter name. */
2943 esc_html__( '%s must be a valid type.', 'jetpack' ),
2944 $param
2945 )
2946 );
2947 }
2948 return true;
2949 }
2950
2951 /**
2952 * Validates that the parameter is among the valid reply-to types for subscriptions.
2953 *
2954 * @since 4.3.0
2955 *
2956 * @param string|bool $value Value to check.
2957 * @param WP_REST_Request $request The request sent to the WP REST API.
2958 * @param string $param Name of the parameter passed to endpoint holding $value.
2959 *
2960 * @return bool|WP_Error
2961 */
2962 public static function validate_subscriptions_reply_to_name( $value, $request, $param ) {
2963 if ( ! empty( $value ) && ! is_string( $value ) ) {
2964 return new WP_Error(
2965 'invalid_param',
2966 sprintf(
2967 /* Translators: Placeholder is a parameter name. */
2968 esc_html__( '%s must be a valid type.', 'jetpack' ),
2969 $param
2970 )
2971 );
2972 }
2973 return true;
2974 }
2975
2976 /**
2977 * Validates that the parameter is among the views where the Sharing can be displayed.
2978 *
2979 * @since 4.3.0
2980 *
2981 * @param string|bool $value {
2982 * Value to check received by request.
2983 *
2984 * @type array $visible List of slug of services to share to that are displayed directly in the page.
2985 * @type array $hidden List of slug of services to share to that are concealed in a folding menu.
2986 * }
2987 * @param WP_REST_Request $request The request sent to the WP REST API.
2988 * @param string $param Name of the parameter passed to endpoint holding $value.
2989 *
2990 * @return bool|WP_Error
2991 */
2992 public static function validate_services( $value, $request, $param ) {
2993 if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
2994 return new WP_Error(
2995 'invalid_param',
2996 sprintf(
2997 /* Translators: Placeholder is a parameter name. */
2998 esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ),
2999 $param
3000 )
3001 );
3002 }
3003
3004 // Allow to clear everything.
3005 if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
3006 return true;
3007 }
3008
3009 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3010 return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
3011 }
3012 $sharer = new Sharing_Service();
3013 $services = array_keys( $sharer->get_all_services() );
3014
3015 if (
3016 ( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
3017 ||
3018 ( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) ) {
3019 return new WP_Error(
3020 'invalid_param',
3021 sprintf(
3022 /* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
3023 esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ),
3024 $param,
3025 implode( ', ', $services )
3026 )
3027 );
3028 }
3029 return true;
3030 }
3031
3032 /**
3033 * Validates that the parameter has enough information to build a custom sharing button.
3034 *
3035 * @since 4.3.0
3036 *
3037 * @param string|bool $value Value to check.
3038 * @param WP_REST_Request $request The request sent to the WP REST API.
3039 * @param string $param Name of the parameter passed to endpoint holding $value.
3040 *
3041 * @return bool|WP_Error
3042 */
3043 public static function validate_custom_service( $value, $request, $param ) {
3044 if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
3045 return new WP_Error(
3046 'invalid_param',
3047 sprintf(
3048 /* Translators: Placeholder is a parameter name. */
3049 esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ),
3050 $param
3051 )
3052 );
3053 }
3054
3055 // Allow to clear everything.
3056 if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
3057 return true;
3058 }
3059
3060 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3061 return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
3062 }
3063
3064 if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
3065 || ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
3066 || ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
3067 return new WP_Error(
3068 'invalid_param',
3069 sprintf(
3070 /* Translators: Placeholder is a parameter name. */
3071 esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ),
3072 $param
3073 )
3074 );
3075 }
3076 return true;
3077 }
3078
3079 /**
3080 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
3081 *
3082 * @since 4.3.0
3083 *
3084 * @param string $value Value to check.
3085 * @param WP_REST_Request $request The request sent to the WP REST API.
3086 * @param string $param Name of the parameter passed to endpoint holding $value.
3087 *
3088 * @return bool|WP_Error
3089 */
3090 public static function validate_custom_service_id( $value, $request, $param ) {
3091 if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
3092 return new WP_Error(
3093 'invalid_param',
3094 sprintf(
3095 /* Translators: Placeholder is a parameter name. */
3096 esc_html__( "%s must be a string prefixed with 'custom-' and followed by a numeric ID.", 'jetpack' ),
3097 $param
3098 )
3099 );
3100 }
3101
3102 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3103 return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
3104 }
3105 $sharer = new Sharing_Service();
3106 $services = $sharer->get_all_services();
3107
3108 if ( ! empty( $value ) && ! isset( $services[ $value ] ) ) {
3109 return new WP_Error(
3110 'invalid_param',
3111 sprintf(
3112 /* Translators: Placeholder is a parameter name. */
3113 esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ),
3114 $param
3115 )
3116 );
3117 }
3118
3119 return true;
3120 }
3121
3122 /**
3123 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
3124 *
3125 * @since 4.3.0
3126 *
3127 * @param string $value Value to check.
3128 * @param WP_REST_Request $request The request sent to the WP REST API.
3129 * @param string $param Name of the parameter passed to endpoint holding $value.
3130 *
3131 * @return bool|WP_Error
3132 */
3133 public static function validate_twitter_username( $value, $request, $param ) {
3134 if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
3135 return new WP_Error(
3136 'invalid_param',
3137 sprintf(
3138 /* Translators: Placeholder is a twitter name. */
3139 esc_html__( '%s must be a Twitter username.', 'jetpack' ),
3140 $param
3141 )
3142 );
3143 }
3144 return true;
3145 }
3146
3147 /**
3148 * Validates that the parameter is a string.
3149 *
3150 * @since 4.3.0
3151 *
3152 * @param string $value Value to check.
3153 * @param WP_REST_Request $request The request sent to the WP REST API.
3154 * @param string $param Name of the parameter passed to endpoint holding $value.
3155 *
3156 * @return bool|WP_Error
3157 */
3158 public static function validate_string( $value, $request, $param ) {
3159 if ( ! is_string( $value ) ) {
3160 return new WP_Error(
3161 'invalid_param',
3162 sprintf(
3163 /* Translators: Placeholder is a parameter name. */
3164 esc_html__( '%s must be a string.', 'jetpack' ),
3165 $param
3166 )
3167 );
3168 }
3169 return true;
3170 }
3171
3172 /**
3173 * Validates that the parameter is an array of strings.
3174 *
3175 * @param array $value Value to check.
3176 * @param WP_REST_Request $request The request sent to the WP REST API.
3177 * @param string $param Name of the parameter passed to the endpoint holding $value.
3178 *
3179 * @return bool|WP_Error
3180 */
3181 public static function validate_array_of_strings( $value, $request, $param ) {
3182 foreach ( $value as $array_item ) {
3183 $validate = self::validate_string( $array_item, $request, $param );
3184 if ( is_wp_error( $validate ) ) {
3185 return $validate;
3186 }
3187 }
3188
3189 return true;
3190 }
3191
3192 /**
3193 * Validates the subscription_options parameter.
3194 *
3195 * @param array $values Value to check.
3196 *
3197 * @return bool|WP_Error
3198 */
3199 public static function validate_subscription_options( $values ) {
3200 // A REST "object" decodes to a PHP associative array. Reject any other
3201 // type (object, string, int, null, ...) up front so the array_keys()
3202 // loop below never runs against a non-array and triggers a PHP warning.
3203 if ( ! is_array( $values ) ) {
3204 return new WP_Error(
3205 'invalid_param',
3206 /* Translators: subscription_options is a variable name, and shouldn't be translated. */
3207 esc_html__( 'subscription_options must be an object.', 'jetpack' )
3208 );
3209 }
3210 foreach ( array_keys( $values ) as $key ) {
3211 if ( ! in_array( $key, array( 'welcome', 'invitation', 'comment_follow', 'subscribe_modal_heading', 'free_tier_description', 'hide_free_tier' ), true ) ) {
3212 return new WP_Error(
3213 'invalid_param',
3214 sprintf(
3215 /* Translators: Placeholder is the invalid param being sent. */
3216 esc_html__( '%s is not one of the allowed members of subscription_options.', 'jetpack' ),
3217 $key
3218 )
3219 );
3220 }
3221 }
3222 return true;
3223 }
3224
3225 /**
3226 * Validates that the parameter is an array.
3227 *
3228 * @param array $values Value to check.
3229 * @param WP_REST_Request $request The request sent to the WP REST API.
3230 * @param string $param Name of the parameter passed to the endpoint holding $value.
3231 *
3232 * @return bool|WP_Error
3233 */
3234 public static function validate_array( $values, $request, $param ) {
3235 if ( ! is_array( $values ) ) {
3236 return new WP_Error(
3237 'invalid_param',
3238 sprintf(
3239 /* Translators: Placeholder is a parameter name. */
3240 esc_html__( '%s must be an object.', 'jetpack' ),
3241 $param
3242 )
3243 );
3244 }
3245 return true;
3246 }
3247
3248 /**
3249 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
3250 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
3251 *
3252 * @since 4.3.0
3253 *
3254 * @param mixed $value Value to check.
3255 *
3256 * @return mixed The value as submitted, or an array holding only 'administrator' when it is empty.
3257 */
3258 public static function sanitize_stats_allowed_roles( $value ) {
3259 if ( empty( $value ) ) {
3260 return array( 'administrator' );
3261 }
3262 return $value;
3263 }
3264
3265 /**
3266 * Sanitize the AI crawler override map.
3267 *
3268 * Keeps the value package-agnostic: each key is normalized with sanitize_key()
3269 * and each value cast to bool. Catalog validation and default-pruning happen in
3270 * `Automattic\Jetpack\SEO\Ai_Crawlers::get_overrides()`.
3271 *
3272 * @param mixed $value The submitted override map.
3273 *
3274 * @return array<string, bool> Sanitized `slug => bool` map.
3275 */
3276 public static function sanitize_ai_crawler_overrides( $value ) {
3277 if ( ! is_array( $value ) ) {
3278 return array();
3279 }
3280
3281 $sanitized = array();
3282 foreach ( $value as $k => $v ) {
3283 $sanitized[ sanitize_key( $k ) ] = (bool) $v;
3284 }
3285 return $sanitized;
3286 }
3287
3288 /**
3289 * Get the currently accessed route and return the module slug in it.
3290 *
3291 * @since 4.3.0
3292 *
3293 * @param string $route Regular expression for the endpoint with the module slug to return.
3294 *
3295 * @return array|string
3296 */
3297 public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
3298
3299 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) || ! is_string( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
3300 return '';
3301 }
3302
3303 preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
3304
3305 if ( empty( $module['slug'] ) ) {
3306 return '';
3307 }
3308
3309 return $module['slug'];
3310 }
3311
3312 /**
3313 * Adds extra information for modules.
3314 *
3315 * @since 4.3.0
3316 *
3317 * @param string|array $modules Can be a single module or a list of modules.
3318 * @param null|string $slug Slug of the module in the first parameter.
3319 *
3320 * @return array|string
3321 */
3322 public static function prepare_modules_for_response( $modules = '', $slug = null ) {
3323 global $wp_rewrite;
3324
3325 /** This filter is documented in modules/sitemaps/sitemaps.php */
3326 $location = apply_filters( 'jetpack_sitemap_location', '' );
3327
3328 if ( $wp_rewrite->using_index_permalinks() ) {
3329 $sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
3330 $news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
3331 } elseif ( $wp_rewrite->using_permalinks() ) {
3332 $sitemap_url = home_url( $location . '/sitemap.xml' );
3333 $news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
3334 } else {
3335 $sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
3336 $news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
3337 }
3338
3339 if ( $slug === null && isset( $modules['sitemaps'] ) ) {
3340 // Is a list of modules.
3341 $modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
3342 $modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
3343 } elseif ( 'sitemaps' === $slug ) {
3344 // It's a single module.
3345 $modules['extra']['sitemap_url'] = $sitemap_url;
3346 $modules['extra']['news_sitemap_url'] = $news_sitemap_url;
3347 }
3348 return $modules;
3349 }
3350
3351 /**
3352 * Remove options the current user cannot read.
3353 *
3354 * Covers every `jetpack_waf_*` option, plus the two Protect options that expose the
3355 * same data under a different name: `jetpack_protect_global_whitelist` is populated
3356 * from `jetpack_waf_ip_allow_list`, and `jetpack_protect_key` is a shared secret.
3357 *
3358 * @since 16.2
3359 *
3360 * @param array $options Option definitions keyed by option name.
3361 * @return array
3362 */
3363 public static function filter_options_for_response( $options ) {
3364 if ( current_user_can( 'manage_options' ) ) {
3365 return $options;
3366 }
3367
3368 $restricted = array(
3369 'jetpack_protect_key',
3370 'jetpack_protect_global_whitelist',
3371 );
3372
3373 return array_filter(
3374 $options,
3375 static function ( $option_name ) use ( $restricted ) {
3376 return 0 !== strpos( $option_name, 'jetpack_waf_' )
3377 && ! in_array( $option_name, $restricted, true );
3378 },
3379 ARRAY_FILTER_USE_KEY
3380 );
3381 }
3382
3383 /**
3384 * Remove 'validate_callback' item from options available for module.
3385 * Fetch current option value and add to array of module options.
3386 * Prepare values of module options that need special handling, like those saved in wpcom.
3387 *
3388 * @since 4.3.0
3389 *
3390 * @param string $module Module slug.
3391 * @return array
3392 */
3393 public static function prepare_options_for_response( $module = '' ) {
3394 $options = self::get_updateable_data_list( $module );
3395
3396 if ( ! is_array( $options ) || empty( $options ) ) {
3397 return $options;
3398 }
3399
3400 // Some modules need special treatment.
3401 switch ( $module ) {
3402
3403 case 'monitor':
3404 // Status of user notifications.
3405 $options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
3406 break;
3407
3408 case 'post-by-email':
3409 // Email address.
3410 $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'] );
3411 break;
3412
3413 case 'protect':
3414 // Protect.
3415 $options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
3416 $options['jetpack_protect_global_whitelist']['current_value'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
3417 break;
3418
3419 case 'related-posts':
3420 // It's local, but it must be broken apart since it's saved as an array.
3421 $options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
3422 break;
3423
3424 case 'verification-tools':
3425 // It's local, but it must be broken apart since it's saved as an array.
3426 $options = self::split_options( $options, get_option( 'verification_services_codes' ) );
3427 break;
3428
3429 case 'google-analytics':
3430 $wga = get_option( 'jetpack_wga' );
3431 $code = '';
3432 if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
3433 $code = $wga['code'];
3434 }
3435 $options['google_analytics_tracking_id']['current_value'] = $code;
3436 break;
3437
3438 case 'sharedaddy':
3439 // It's local, but it must be broken apart since it's saved as an array.
3440 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3441 break;
3442 }
3443 $sharer = new Sharing_Service();
3444 $options = self::split_options( $options, $sharer->get_global_options() );
3445 $options['sharing_services']['current_value'] = $sharer->get_blog_services();
3446 $other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
3447 foreach ( $other_sharedaddy_options as $key ) {
3448 $default_value = $options[ $key ]['default'] ?? '';
3449 $current_value = get_option( $key, $default_value );
3450 $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
3451 }
3452 break;
3453
3454 case 'stats':
3455 // It's local, but it must be broken apart since it's saved as an array.
3456 $options = self::split_options( $options, Stats_Options::get_options() );
3457 break;
3458 default:
3459 // These option are just stored as plain WordPress options.
3460 foreach ( $options as $key => $value ) {
3461 $default_value = $options[ $key ]['default'] ?? '';
3462 $current_value = get_option( $key, $default_value );
3463 $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
3464 }
3465 }
3466 // At this point some options have current_value not set because they're options
3467 // that only get written on update, so we set current_value to the default one.
3468 foreach ( $options as $key => $value ) {
3469 // We don't need validate_callback in the response.
3470 if ( isset( $options[ $key ]['validate_callback'] ) ) {
3471 unset( $options[ $key ]['validate_callback'] );
3472 }
3473 $default_value = $options[ $key ]['default'] ?? '';
3474 if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
3475 $options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
3476 }
3477 }
3478
3479 // Filter last: the switch above assigns current_value by key without isset(),
3480 // so filtering earlier would let those assignments re-add a removed option.
3481 return self::filter_options_for_response( $options );
3482 }
3483
3484 /**
3485 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
3486 *
3487 * @since 4.3.0
3488 *
3489 * @param array $separate_options Array of options admitted by the module.
3490 * @param array $grouped_options Option saved as array to be splitted.
3491 * @param string $prefix Optional prefix for the separate option keys.
3492 *
3493 * @return array
3494 */
3495 public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
3496 if ( is_array( $grouped_options ) ) {
3497 foreach ( $grouped_options as $key => $value ) {
3498 $option_key = $prefix . $key;
3499 if ( isset( $separate_options[ $option_key ] ) ) {
3500 $separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
3501 }
3502 }
3503 }
3504 return $separate_options;
3505 }
3506
3507 /**
3508 * Perform a casting to the value specified in the option definition.
3509 *
3510 * @since 4.3.0
3511 *
3512 * @param mixed $value Value to cast to the proper type.
3513 * @param array $definition Type to cast the value to.
3514 *
3515 * @return bool|float|int|string
3516 */
3517 public static function cast_value( $value, $definition ) {
3518 if ( 'NULL' === $value ) {
3519 return null;
3520 }
3521
3522 if ( isset( $definition['type'] ) ) {
3523 switch ( $definition['type'] ) {
3524 case 'boolean':
3525 if ( 'true' === $value || 'on' === $value ) {
3526 return true;
3527 } elseif ( 'false' === $value || 'off' === $value ) {
3528 return false;
3529 }
3530 $value = (bool) $value;
3531 break;
3532
3533 case 'integer':
3534 $value = (int) $value;
3535 break;
3536
3537 case 'float':
3538 $value = (float) $value;
3539 break;
3540
3541 case 'string':
3542 $value = (string) $value;
3543 break;
3544 }
3545 }
3546 return $value;
3547 }
3548
3549 /**
3550 * Get a value not saved locally.
3551 *
3552 * @since 4.3.0
3553 *
3554 * @param string $module Module slug.
3555 * @param string $option Option name.
3556 *
3557 * @return bool Whether user is receiving notifications or not.
3558 */
3559 public static function get_remote_value( $module, $option ) {
3560
3561 if ( in_array( $module, array( 'post-by-email' ), true ) ) {
3562 $option .= get_current_user_id();
3563 }
3564
3565 // If option doesn't exist, 'does_not_exist' will be returned.
3566 $value = get_option( $option, 'does_not_exist' );
3567
3568 // If option exists, just return it.
3569 if ( 'does_not_exist' !== $value ) {
3570 return $value;
3571 }
3572
3573 // Only check a remote option if Jetpack is connected.
3574 if ( ! Jetpack::is_connection_ready() ) {
3575 return false;
3576 }
3577
3578 // Do what is necessary for each module.
3579 switch ( $module ) {
3580 case 'monitor':
3581 // Load the class to use the method. If class can't be found, do nothing.
3582 if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once Jetpack::get_module_path( $module ) ) {
3583 return false;
3584 }
3585 $value = Jetpack_Monitor::user_receives_notifications( false );
3586 break;
3587
3588 case 'post-by-email':
3589 // Load the class to use the method. If class can't be found, do nothing.
3590 if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once Jetpack::get_module_path( $module ) ) {
3591 return false;
3592 }
3593 $value = Jetpack_Post_By_Email::init()->get_post_by_email_address();
3594 if ( null === $value ) {
3595 $value = 'NULL'; // sentinel value so it actually gets set.
3596 }
3597 break;
3598 }
3599
3600 // Normalize value to boolean.
3601 if ( is_wp_error( $value ) || $value === null ) {
3602 $value = false;
3603 }
3604
3605 // Save option to use it next time.
3606 update_option( $option, $value );
3607
3608 return $value;
3609 }
3610
3611 /**
3612 * Get number of plugin updates available.
3613 *
3614 * @since 4.3.0
3615 *
3616 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
3617 */
3618 public static function get_plugin_update_count() {
3619 $updates = wp_get_update_data();
3620 if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
3621 $count = $updates['counts']['plugins'];
3622 if ( 0 === $count ) {
3623 $response = array(
3624 'code' => 'success',
3625 'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
3626 'count' => 0,
3627 );
3628 } else {
3629 $response = array(
3630 'code' => 'updates-available',
3631 'message' => esc_html(
3632 sprintf(
3633 /* Translators: placeholders are numbers. */
3634 _n( '%s plugin needs updating.', '%s plugins need updating.', $count, 'jetpack' ),
3635 $count
3636 )
3637 ),
3638 'count' => $count,
3639 );
3640 }
3641 return rest_ensure_response( $response );
3642 }
3643
3644 return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
3645 }
3646
3647 /**
3648 * Get plugins data in site.
3649 *
3650 * @since 4.2.0
3651 *
3652 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
3653 */
3654 public static function get_plugins() {
3655 $plugins = Plugins_Installer::get_plugins();
3656
3657 if ( ! empty( $plugins ) ) {
3658 return rest_ensure_response( $plugins );
3659 }
3660
3661 return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
3662 }
3663
3664 /**
3665 * Install a specific plugin and optionally activates it.
3666 *
3667 * @since 8.9.0
3668 *
3669 * @param WP_REST_Request $request {
3670 * Array of parameters received by request.
3671 *
3672 * @type string $slug Plugin slug.
3673 * @type string $status Plugin status.
3674 * @type string $source Where did the plugin installation request originate.
3675 * }
3676 *
3677 * @return WP_REST_Response|WP_Error A response object if the installation and / or activation was successful, or a WP_Error object if it failed.
3678 */
3679 public static function install_plugin( $request ) {
3680 $plugin = stripslashes( $request['slug'] );
3681
3682 // Let's make sure the plugin isn't already installed.
3683 $plugin_id = Plugins_Installer::get_plugin_id_by_slug( $plugin );
3684
3685 // If not installed, let's install now.
3686 if ( ! $plugin_id ) {
3687 $result = Plugins_Installer::install_plugin( $plugin );
3688
3689 if ( is_wp_error( $result ) ) {
3690 return new WP_Error(
3691 'install_plugin_failed',
3692 sprintf(
3693 /* translators: %1$s: plugin name. -- %2$s: error message. */
3694 __( 'Unable to install %1$s: %2$s ', 'jetpack' ),
3695 $plugin,
3696 $result->get_error_message()
3697 ),
3698 array( 'status' => 500 )
3699 );
3700 }
3701 }
3702
3703 /*
3704 * We may want to activate the plugin as well.
3705 * Let's check for the status parameter in the request to find out.
3706 * If none was passed (or something other than active), let's return now.
3707 */
3708 if ( empty( $request['status'] ) || 'active' !== $request['status'] ) {
3709 return rest_ensure_response(
3710 array(
3711 'code' => 'success',
3712 'message' => esc_html(
3713 sprintf(
3714 /* translators: placeholder is a plugin name. */
3715 __( 'Installed %s', 'jetpack' ),
3716 $plugin
3717 )
3718 ),
3719 )
3720 );
3721 }
3722
3723 /*
3724 * Proceed with plugin activation.
3725 * Let's check again for the plugin's ID if we don't already have it.
3726 */
3727 if ( ! $plugin_id ) {
3728 $plugin_id = Plugins_Installer::get_plugin_id_by_slug( $plugin );
3729 if ( ! $plugin_id ) {
3730 return new WP_Error(
3731 'unable_to_determine_installed_plugin',
3732 __( 'Unable to determine what plugin was installed.', 'jetpack' ),
3733 array( 'status' => 500 )
3734 );
3735 }
3736 }
3737
3738 $source = ! empty( $request['source'] ) ? stripslashes( $request['source'] ) : 'rest_api';
3739 $plugin_args = array(
3740 'plugin' => substr( $plugin_id, 0, - 4 ),
3741 'status' => 'active',
3742 'source' => $source,
3743 );
3744 return self::activate_plugin( $plugin_args );
3745 }
3746
3747 /**
3748 * Activate a specific plugin.
3749 *
3750 * @since 8.9.0
3751 *
3752 * @param WP_REST_Request $request {
3753 * Array of parameters received by request.
3754 *
3755 * @type string $plugin Plugin long slug (slug/index-file)
3756 * @type string $status Plugin status. We only support active in Jetpack.
3757 * @type string $source Where did the plugin installation request originate.
3758 * }
3759 *
3760 * @return WP_REST_Response|WP_Error A response object if the activation was successful, or a WP_Error object if the activation failed.
3761 */
3762 public static function activate_plugin( $request ) {
3763 /*
3764 * We need an "active" status parameter to be passed to the request
3765 * just like the core plugins endpoind we'll eventually switch to.
3766 */
3767 if ( empty( $request['status'] ) || 'active' !== $request['status'] ) {
3768 return new WP_Error(
3769 'missing_status_parameter',
3770 esc_html__( 'Status parameter missing.', 'jetpack' ),
3771 array( 'status' => 403 )
3772 );
3773 }
3774
3775 $plugins = Plugins_Installer::get_plugins();
3776
3777 if ( empty( $plugins ) ) {
3778 return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3779 }
3780
3781 if ( empty( $request['plugin'] ) ) {
3782 return new WP_Error( 'no_plugin_specified', esc_html__( 'You did not specify a plugin.', 'jetpack' ), array( 'status' => 404 ) );
3783 }
3784
3785 $plugin = $request['plugin'] . '.php';
3786
3787 // Is the plugin installed?
3788 if ( ! array_key_exists( $plugin, $plugins ) ) {
3789 return new WP_Error(
3790 'plugin_not_found',
3791 esc_html(
3792 sprintf(
3793 /* translators: placeholder is a plugin slug. */
3794 __( 'Plugin %s is not installed.', 'jetpack' ),
3795 $plugin
3796 )
3797 ),
3798 array( 'status' => 404 )
3799 );
3800 }
3801
3802 // Is the plugin active already?
3803 $status = Plugins_Installer::get_plugin_status( $plugin );
3804 if ( in_array( $status, array( 'active', 'network-active' ), true ) ) {
3805 return new WP_Error(
3806 'plugin_already_active',
3807 esc_html(
3808 sprintf(
3809 /* translators: placeholder is a plugin slug. */
3810 __( 'Plugin %s is already active.', 'jetpack' ),
3811 $plugin
3812 )
3813 ),
3814 array( 'status' => 404 )
3815 );
3816 }
3817
3818 // Now try to activate the plugin.
3819 $activated = activate_plugin( $plugin );
3820
3821 if ( is_wp_error( $activated ) ) {
3822 return $activated;
3823 } else {
3824 $source = ! empty( $request['source'] ) ? stripslashes( $request['source'] ) : 'rest_api';
3825 /**
3826 * Fires when Jetpack installs a plugin for you.
3827 *
3828 * @since 8.9.0
3829 *
3830 * @param string $plugin_file Plugin file.
3831 * @param string $source Where did the plugin installation originate.
3832 */
3833 do_action( 'jetpack_activated_plugin', $plugin, $source );
3834 return rest_ensure_response(
3835 array(
3836 'code' => 'success',
3837 'message' => sprintf(
3838 /* translators: placeholder is a plugin name. */
3839 esc_html__( 'Activated %s', 'jetpack' ),
3840 $plugin
3841 ),
3842 )
3843 );
3844 }
3845 }
3846
3847 /**
3848 * Check if a plugin can be activated.
3849 *
3850 * @since 8.9.0
3851 *
3852 * @param string|bool $value Value to check.
3853 * @param WP_REST_Request $request The request sent to the WP REST API.
3854 * @param string $param Name of the parameter passed to endpoint holding $value.
3855 */
3856 public static function validate_activate_plugin( $value, $request, $param ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
3857 return 'active' === $value;
3858 }
3859
3860 /**
3861 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
3862 *
3863 * @since 4.2.0
3864 *
3865 * @param WP_REST_Request $request {
3866 * Array of parameters received by request.
3867 *
3868 * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3869 * }
3870 *
3871 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
3872 */
3873 public static function get_plugin( $request ) {
3874 $plugins = Plugins_Installer::get_plugins();
3875
3876 if ( empty( $plugins ) ) {
3877 return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3878 }
3879
3880 $plugin = stripslashes( $request['plugin'] );
3881
3882 if ( ! array_key_exists( $plugin, $plugins ) ) {
3883 return new WP_Error(
3884 'plugin_not_found',
3885 esc_html(
3886 sprintf(
3887 /* Translators: placeholder is a plugin name. */
3888 __( 'Plugin %s is not installed.', 'jetpack' ),
3889 $plugin
3890 )
3891 ),
3892 array( 'status' => 404 )
3893 );
3894 }
3895
3896 $plugin_data = $plugins[ $plugin ];
3897
3898 $plugin_data['active'] = in_array( Plugins_Installer::get_plugin_status( $plugin ), array( 'active', 'network-active' ), true );
3899
3900 return rest_ensure_response(
3901 array(
3902 'code' => 'success',
3903 'message' => esc_html__( 'Plugin found.', 'jetpack' ),
3904 'data' => $plugin_data,
3905 )
3906 );
3907 }
3908
3909 /**
3910 * Returns the Jetpack CRM data.
3911 *
3912 * @return WP_REST_Response A response object containing the Jetpack CRM data.
3913 */
3914 public static function get_jetpack_crm_data() {
3915 $jetpack_crm_data = ( new Jetpack_CRM_Data() )->get_crm_data();
3916 return rest_ensure_response( $jetpack_crm_data );
3917 }
3918
3919 /**
3920 * Activates Jetpack CRM's Jetpack Forms extension.
3921 *
3922 * @param WP_REST_Request $request The request sent to the WP REST API.
3923 * @return WP_REST_Response|WP_Error A response object if the extension activation was successful, or a WP_Error object if it failed.
3924 */
3925 public static function activate_crm_jetpack_forms_extension( $request ) {
3926 if ( ! isset( $request['extension'] ) || 'jetpackforms' !== $request['extension'] ) {
3927 return new WP_Error( 'invalid_param', esc_html__( 'Missing or invalid extension parameter.', 'jetpack' ), array( 'status' => 404 ) );
3928 }
3929
3930 $result = ( new Jetpack_CRM_Data() )->activate_crm_jetpackforms_extension();
3931
3932 if ( is_wp_error( $result ) ) {
3933 return $result;
3934 }
3935
3936 return rest_ensure_response( array( 'code' => 'success' ) );
3937 }
3938
3939 /**
3940 * Verifies that the current user has the required permission for accessing the CRM data.
3941 *
3942 * @return true|WP_Error Returns true if the user has the required capability, else a WP_Error object.
3943 */
3944 public static function jetpack_crm_data_permission_check() {
3945 if ( current_user_can( 'publish_posts' ) ) {
3946 return true;
3947 }
3948
3949 return new WP_Error(
3950 'invalid_user_permission_jetpack_crm_data',
3951 REST_Connector::get_user_permissions_error_msg(),
3952 array( 'status' => rest_authorization_required_code() )
3953 );
3954 }
3955
3956 /**
3957 * Verifies that the current user has the required capability for activating Jetpack CRM extensions.
3958 *
3959 * @return true|WP_Error Returns true if the user has the required capability, else a WP_Error object.
3960 */
3961 public static function activate_crm_extensions_permission_check() {
3962 // phpcs:ignore WordPress.WP.Capabilities.Unknown
3963 if ( current_user_can( 'admin_zerobs_manage_options' ) ) {
3964 return true;
3965 }
3966
3967 return new WP_Error(
3968 'invalid_user_permission_activate_jetpack_crm_ext',
3969 REST_Connector::get_user_permissions_error_msg(),
3970 array( 'status' => rest_authorization_required_code() )
3971 );
3972 }
3973
3974 /**
3975 * Set hasSeenWCConnectionModal to true when the site has displayed it
3976 *
3977 * @since 10.4.0
3978 *
3979 * @return bool
3980 */
3981 public static function set_has_seen_wc_connection_modal() {
3982 $updated_option = Jetpack_Options::update_option( 'has_seen_wc_connection_modal', true );
3983
3984 return rest_ensure_response( array( 'success' => $updated_option ) );
3985 }
3986
3987 /**
3988 * Return the list of available features.
3989 *
3990 * @return array
3991 */
3992 public static function get_features_available() {
3993 $raw_modules = Jetpack::get_available_modules();
3994 $modules = array();
3995 foreach ( $raw_modules as $module ) {
3996 $modules[] = Jetpack::get_module_slug( $module );
3997 }
3998
3999 return $modules;
4000 }
4001
4002 /**
4003 * Returns what features are enabled. Uses the slug of the modules files.
4004 *
4005 * @return array
4006 */
4007 public static function get_features_enabled() {
4008 $raw_modules = Jetpack::get_active_modules();
4009 $modules = array();
4010 foreach ( $raw_modules as $module ) {
4011 $modules[] = Jetpack::get_module_slug( $module );
4012 }
4013
4014 return $modules;
4015 }
4016
4017 /**
4018 * Verify that the API client is allowed to replace user token.
4019 *
4020 * @since 1.29.0
4021 *
4022 * @return bool|WP_Error
4023 */
4024 public static function get_features_permission_check() {
4025 if ( ! Rest_Authentication::is_signed_with_blog_token() ) {
4026 $message = esc_html__(
4027 'You do not have the correct user permissions to perform this action. Please contact your site admin if you think this is a mistake.',
4028 'jetpack'
4029 );
4030 return new WP_Error( 'invalid_permission_fetch_features', $message, array( 'status' => rest_authorization_required_code() ) );
4031 }
4032
4033 return true;
4034 }
4035 } // class end
4036