PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 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 All 507 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-beta, at _inc/lib/class.core-rest-api-endpoints.php

4,050 lines 135.9 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 'enable_blocks_comments' => array(
1733 'description' => esc_html__( 'Enable blocks in comments', 'jetpack' ),
1734 'type' => 'boolean',
1735 'default' => 1,
1736 'validate_callback' => __CLASS__ . '::validate_boolean',
1737 'jp_group' => 'comments',
1738 ),
1739
1740 // Custom Content Types.
1741 'jetpack_portfolio' => array(
1742 'description' => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ),
1743 'type' => 'boolean',
1744 'default' => 0,
1745 'validate_callback' => __CLASS__ . '::validate_boolean',
1746 'jp_group' => 'settings',
1747 ),
1748 'jetpack_portfolio_posts_per_page' => array(
1749 'description' => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ),
1750 'type' => 'integer',
1751 'default' => 10,
1752 'validate_callback' => __CLASS__ . '::validate_posint',
1753 'jp_group' => 'settings',
1754 ),
1755 'jetpack_testimonial' => array(
1756 'description' => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ),
1757 'type' => 'boolean',
1758 'default' => 0,
1759 'validate_callback' => __CLASS__ . '::validate_boolean',
1760 'jp_group' => 'settings',
1761 ),
1762 'jetpack_testimonial_posts_per_page' => array(
1763 'description' => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ),
1764 'type' => 'integer',
1765 'default' => 10,
1766 'validate_callback' => __CLASS__ . '::validate_posint',
1767 'jp_group' => 'settings',
1768 ),
1769 // WAF.
1770 'jetpack_waf_automatic_rules' => array(
1771 'description' => esc_html__( 'Enable automatic rules - Protect your site against untrusted traffic sources with automatic security rules.', 'jetpack' ),
1772 'type' => 'boolean',
1773 'default' => Waf_Compatibility::get_default_automatic_rules_option(),
1774 'validate_callback' => __CLASS__ . '::validate_boolean',
1775 'jp_group' => 'waf',
1776 ),
1777 'jetpack_waf_ip_block_list_enabled' => array(
1778 'description' => esc_html__( 'Block list - Block a specific request IP.', 'jetpack' ),
1779 'type' => 'boolean',
1780 'default' => 0,
1781 'validate_callback' => __CLASS__ . '::validate_boolean',
1782 'jp_group' => 'waf',
1783 ),
1784 'jetpack_waf_ip_block_list' => array(
1785 'description' => esc_html__( 'Blocked IP addresses', 'jetpack' ),
1786 'type' => 'string',
1787 'default' => '',
1788 'validate_callback' => __CLASS__ . '::validate_string',
1789 'sanitize_callback' => 'esc_textarea',
1790 'jp_group' => 'waf',
1791 ),
1792 'jetpack_waf_ip_allow_list_enabled' => array(
1793 'description' => esc_html__( 'Allow list - Allow a specific request IP.', 'jetpack' ),
1794 'type' => 'boolean',
1795 'default' => 0,
1796 'validate_callback' => __CLASS__ . '::validate_boolean',
1797 'jp_group' => 'settings',
1798 ),
1799 'jetpack_waf_ip_allow_list' => array(
1800 'description' => esc_html__( 'Always allowed IP addresses', 'jetpack' ),
1801 'type' => 'string',
1802 'default' => '',
1803 'validate_callback' => __CLASS__ . '::validate_string',
1804 'sanitize_callback' => 'esc_textarea',
1805 'jp_group' => 'settings',
1806 ),
1807 'jetpack_waf_share_data' => array(
1808 'description' => esc_html__( 'Share basic data with Jetpack.', 'jetpack' ),
1809 'type' => 'boolean',
1810 'default' => 0,
1811 'validate_callback' => __CLASS__ . '::validate_boolean',
1812 'jp_group' => 'waf',
1813 ),
1814 'jetpack_waf_share_debug_data' => array(
1815 'description' => esc_html__( 'Share detailed data with Jetpack.', 'jetpack' ),
1816 'type' => 'boolean',
1817 'default' => 0,
1818 'validate_callback' => __CLASS__ . '::validate_boolean',
1819 'jp_group' => 'waf',
1820 ),
1821 // Galleries.
1822 'tiled_galleries' => array(
1823 'description' => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ),
1824 'type' => 'boolean',
1825 'default' => 0,
1826 'validate_callback' => __CLASS__ . '::validate_boolean',
1827 'jp_group' => 'tiled-gallery',
1828 ),
1829
1830 'gravatar_disable_hovercards' => array(
1831 'description' => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ),
1832 'type' => 'string',
1833 'default' => 'enabled',
1834 // Not visible. This is used as the checkbox value.
1835 'enum' => array(
1836 'enabled',
1837 'disabled',
1838 ),
1839 'enum_labels' => array(
1840 'enabled' => esc_html__( 'Enabled', 'jetpack' ),
1841 'disabled' => esc_html__( 'Disabled', 'jetpack' ),
1842 ),
1843 'validate_callback' => __CLASS__ . '::validate_list_item',
1844 'jp_group' => 'gravatar-hovercards',
1845 ),
1846
1847 // Infinite Scroll.
1848 'infinite_scroll' => array(
1849 'description' => esc_html__( 'To infinity and beyond', 'jetpack' ),
1850 'type' => 'boolean',
1851 'default' => 1,
1852 'validate_callback' => __CLASS__ . '::validate_boolean',
1853 'jp_group' => 'infinite-scroll',
1854 ),
1855 'infinite_scroll_google_analytics' => array(
1856 'description' => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ),
1857 'type' => 'boolean',
1858 'default' => 0,
1859 'validate_callback' => __CLASS__ . '::validate_boolean',
1860 'jp_group' => 'infinite-scroll',
1861 ),
1862
1863 // Likes.
1864 'wpl_default' => array(
1865 'description' => esc_html__( 'WordPress.com Likes are', 'jetpack' ),
1866 'type' => 'string',
1867 'default' => 'on',
1868 'enum' => array(
1869 'on',
1870 'off',
1871 ),
1872 'enum_labels' => array(
1873 'on' => esc_html__( 'On for all posts', 'jetpack' ),
1874 'off' => esc_html__( 'Turned on per post', 'jetpack' ),
1875 ),
1876 'validate_callback' => __CLASS__ . '::validate_list_item',
1877 'jp_group' => 'likes',
1878 ),
1879 'social_notifications_like' => array(
1880 'description' => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ),
1881 'type' => 'boolean',
1882 'default' => 1,
1883 'validate_callback' => __CLASS__ . '::validate_boolean',
1884 'jp_group' => 'likes',
1885 ),
1886
1887 // Markdown.
1888 'wpcom_publish_comments_with_markdown' => array(
1889 'description' => esc_html__( 'Use Markdown for comments.', 'jetpack' ),
1890 'type' => 'boolean',
1891 'default' => 0,
1892 'validate_callback' => __CLASS__ . '::validate_boolean',
1893 'jp_group' => 'markdown',
1894 ),
1895 'wpcom_publish_posts_with_markdown' => array(
1896 'description' => esc_html__( 'Use Markdown for posts.', 'jetpack' ),
1897 'type' => 'boolean',
1898 'default' => 0,
1899 'validate_callback' => __CLASS__ . '::validate_boolean',
1900 'jp_group' => 'markdown',
1901 ),
1902
1903 // Monitor.
1904 'monitor_receive_notifications' => array(
1905 'description' => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ),
1906 'type' => 'boolean',
1907 'default' => 0,
1908 'validate_callback' => __CLASS__ . '::validate_boolean',
1909 'jp_group' => 'monitor',
1910 ),
1911
1912 // Post by Email.
1913 'post_by_email_address' => array(
1914 'description' => esc_html__( 'Email Address', 'jetpack' ),
1915 'type' => 'string',
1916 'default' => 'noop',
1917 'enum' => array(
1918 'noop',
1919 'create',
1920 'regenerate',
1921 'delete',
1922 ),
1923 'enum_labels' => array(
1924 'noop' => '',
1925 'create' => esc_html__( 'Create Post by Email address', 'jetpack' ),
1926 'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ),
1927 'delete' => esc_html__( 'Delete Post by Email address', 'jetpack' ),
1928 ),
1929 'validate_callback' => __CLASS__ . '::validate_list_item',
1930 'jp_group' => 'post-by-email',
1931 ),
1932
1933 // Protect.
1934 'jetpack_protect_key' => array(
1935 'description' => esc_html__( 'Protect API key', 'jetpack' ),
1936 'type' => 'string',
1937 'default' => '',
1938 'validate_callback' => __CLASS__ . '::validate_alphanum',
1939 'jp_group' => 'protect',
1940 ),
1941 'jetpack_protect_global_whitelist' => array(
1942 'description' => esc_html__( 'Protect global IP allow list', 'jetpack' ),
1943 'type' => 'string',
1944 'default' => '',
1945 'validate_callback' => __CLASS__ . '::validate_string',
1946 'sanitize_callback' => 'esc_textarea',
1947 'jp_group' => 'protect',
1948 ),
1949
1950 // Sharing.
1951 'sharing_services' => array(
1952 'description' => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ),
1953 'type' => 'object',
1954 'default' => array(
1955 'visible' => array( 'facebook', 'x' ),
1956 'hidden' => array(),
1957 ),
1958 'validate_callback' => __CLASS__ . '::validate_services',
1959 'jp_group' => 'sharedaddy',
1960 ),
1961 'button_style' => array(
1962 'description' => esc_html__( 'Button Style', 'jetpack' ),
1963 'type' => 'string',
1964 'default' => 'icon',
1965 'enum' => array(
1966 'icon-text',
1967 'icon',
1968 'text',
1969 'official',
1970 ),
1971 'enum_labels' => array(
1972 'icon-text' => esc_html__( 'Icon + text', 'jetpack' ),
1973 'icon' => esc_html__( 'Icon only', 'jetpack' ),
1974 'text' => esc_html__( 'Text only', 'jetpack' ),
1975 'official' => esc_html__( 'Official buttons', 'jetpack' ),
1976 ),
1977 'validate_callback' => __CLASS__ . '::validate_list_item',
1978 'jp_group' => 'sharedaddy',
1979 ),
1980 'sharing_label' => array(
1981 'description' => esc_html__( 'Sharing Label', 'jetpack' ),
1982 'type' => 'string',
1983 'default' => '',
1984 'validate_callback' => __CLASS__ . '::validate_string',
1985 'sanitize_callback' => 'esc_html',
1986 'jp_group' => 'sharedaddy',
1987 ),
1988 'show' => array(
1989 'description' => esc_html__( 'Views where buttons are shown', 'jetpack' ),
1990 'type' => 'array',
1991 'items' => array(
1992 'type' => 'string',
1993 ),
1994 'default' => array( 'post' ),
1995 'validate_callback' => __CLASS__ . '::validate_sharing_show',
1996 'jp_group' => 'sharedaddy',
1997 ),
1998 'jetpack-twitter-cards-site-tag' => array(
1999 'description' => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ),
2000 'type' => 'string',
2001 'default' => '',
2002 'validate_callback' => __CLASS__ . '::validate_twitter_username',
2003 'sanitize_callback' => 'esc_html',
2004 'jp_group' => 'sharedaddy',
2005 ),
2006 'sharedaddy_disable_resources' => array(
2007 'description' => esc_html__( 'Disable CSS and JS', 'jetpack' ),
2008 'type' => 'boolean',
2009 'default' => 0,
2010 'validate_callback' => __CLASS__ . '::validate_boolean',
2011 'jp_group' => 'sharedaddy',
2012 ),
2013 'custom' => array(
2014 'description' => esc_html__( 'Custom sharing services added by user.', 'jetpack' ),
2015 'type' => 'object',
2016 'default' => array(
2017 'sharing_name' => '',
2018 'sharing_url' => '',
2019 'sharing_icon' => '',
2020 ),
2021 'validate_callback' => __CLASS__ . '::validate_custom_service',
2022 'jp_group' => 'sharedaddy',
2023 ),
2024 // Not an option, but an action that can be performed on the list of custom services passing the service ID.
2025 'sharing_delete_service' => array(
2026 'description' => esc_html__( 'Delete custom sharing service.', 'jetpack' ),
2027 'type' => 'string',
2028 'default' => '',
2029 'validate_callback' => __CLASS__ . '::validate_custom_service_id',
2030 'jp_group' => 'sharedaddy',
2031 ),
2032
2033 // SSO.
2034 'jetpack_sso_require_two_step' => array(
2035 'description' => esc_html__( 'Require Two-Step Authentication', 'jetpack' ),
2036 'type' => 'boolean',
2037 'default' => SSO\Helpers::is_require_two_step_checkbox_disabled(),
2038 'validate_callback' => __CLASS__ . '::validate_boolean',
2039 'jp_group' => 'sso',
2040 ),
2041 'jetpack_sso_match_by_email' => array(
2042 'description' => esc_html__( 'Match by Email', 'jetpack' ),
2043 'type' => 'boolean',
2044 'default' => 1,
2045 'validate_callback' => __CLASS__ . '::validate_boolean',
2046 'jp_group' => 'sso',
2047 ),
2048
2049 // Subscriptions.
2050 'stb_enabled' => array(
2051 'description' => esc_html__( "Show a <em>'follow blog'</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 'stc_enabled' => array(
2058 'description' => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ),
2059 'type' => 'boolean',
2060 'default' => 1,
2061 'validate_callback' => __CLASS__ . '::validate_boolean',
2062 'jp_group' => 'subscriptions',
2063 ),
2064 'wpcom_newsletter_categories' => array(
2065 'description' => esc_html__( 'Array of post category ids that are marked as newsletter categories', 'jetpack' ),
2066 'type' => 'array',
2067 'default' => array(),
2068 'validate_callback' => __CLASS__ . '::validate_array',
2069 'jp_group' => 'subscriptions',
2070 ),
2071 'wpcom_newsletter_categories_enabled' => array(
2072 'description' => esc_html__( 'Whether the newsletter categories are enabled or not', 'jetpack' ),
2073 'type' => 'boolean',
2074 'default' => 0,
2075 'validate_callback' => __CLASS__ . '::validate_boolean',
2076 'jp_group' => 'subscriptions',
2077 ),
2078 'wpcom_newsletter_send_default' => array(
2079 'description' => esc_html__( 'Whether to send newsletter emails by default when publishing a post', 'jetpack' ),
2080 'type' => 'boolean',
2081 'default' => 1,
2082 'validate_callback' => __CLASS__ . '::validate_boolean',
2083 'jp_group' => 'subscriptions',
2084 ),
2085 'wpcom_featured_image_in_email' => array(
2086 'description' => esc_html__( 'Whether to include the featured image in the email or not', 'jetpack' ),
2087 'type' => 'boolean',
2088 'default' => 0,
2089 'validate_callback' => __CLASS__ . '::validate_boolean',
2090 'jp_group' => 'subscriptions',
2091 ),
2092 'jetpack_gravatar_in_email' => array(
2093 'description' => esc_html__( 'Whether to show author avatar in the email byline', 'jetpack' ),
2094 'type' => 'boolean',
2095 'default' => 1,
2096 'validate_callback' => __CLASS__ . '::validate_boolean',
2097 'jp_group' => 'subscriptions',
2098 ),
2099 'jetpack_author_in_email' => array(
2100 'description' => esc_html__( 'Whether to show author display name in the email byline', 'jetpack' ),
2101 'type' => 'boolean',
2102 'default' => 1,
2103 'validate_callback' => __CLASS__ . '::validate_boolean',
2104 'jp_group' => 'subscriptions',
2105 ),
2106 'jetpack_post_date_in_email' => array(
2107 'description' => esc_html__( 'Whether to show date in the email byline', 'jetpack' ),
2108 'type' => 'boolean',
2109 'default' => 1,
2110 'validate_callback' => __CLASS__ . '::validate_boolean',
2111 'jp_group' => 'subscriptions',
2112 ),
2113 'wpcom_subscription_emails_use_excerpt' => array(
2114 'description' => esc_html__( 'Whether to use the excerpt in the email or not', 'jetpack' ),
2115 'type' => 'boolean',
2116 'default' => 0,
2117 'validate_callback' => __CLASS__ . '::validate_boolean',
2118 'jp_group' => 'subscriptions',
2119 ),
2120 'jetpack_subscriptions_reply_to' => array(
2121 'description' => esc_html__( 'Reply to email behaviour for newsletters emails', 'jetpack' ),
2122 'type' => 'string',
2123 'default' => Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to,
2124 'validate_callback' => __CLASS__ . '::validate_subscriptions_reply_to',
2125 'jp_group' => 'subscriptions',
2126 ),
2127 'jetpack_subscriptions_from_name' => array(
2128 'description' => esc_html__( 'From name for newsletters emails', 'jetpack' ),
2129 'type' => 'string',
2130 'default' => '',
2131 'validate_callback' => __CLASS__ . '::validate_subscriptions_reply_to_name',
2132 'jp_group' => 'subscriptions',
2133 ),
2134 'sm_enabled' => array(
2135 'description' => esc_html__( 'Show popup Subscribe modal to readers.', 'jetpack' ),
2136 'type' => 'boolean',
2137 'default' => 0,
2138 'validate_callback' => __CLASS__ . '::validate_boolean',
2139 'jp_group' => 'subscriptions',
2140 ),
2141 'jetpack_subscribe_overlay_enabled' => array(
2142 'description' => esc_html__( 'Show subscribe overlay on homepage.', 'jetpack' ),
2143 'type' => 'boolean',
2144 'default' => 0,
2145 'validate_callback' => __CLASS__ . '::validate_boolean',
2146 'jp_group' => 'subscriptions',
2147 ),
2148 'jetpack_subscribe_floating_button_enabled' => array(
2149 'description' => esc_html__( 'Show a floating subscribe button.', 'jetpack' ),
2150 'type' => 'boolean',
2151 'default' => 0,
2152 'validate_callback' => __CLASS__ . '::validate_boolean',
2153 'jp_group' => 'subscriptions',
2154 ),
2155 'jetpack_subscriptions_subscribe_post_end_enabled' => array(
2156 'description' => esc_html__( 'Add Subscribe block at the end of each post.', 'jetpack' ),
2157 'type' => 'boolean',
2158 'default' => 0,
2159 'validate_callback' => __CLASS__ . '::validate_boolean',
2160 'jp_group' => 'subscriptions',
2161 ),
2162 'jetpack_subscriptions_login_navigation_enabled' => array(
2163 'description' => esc_html__( 'Add Subscriber Login block to the navigation.', 'jetpack' ),
2164 'type' => 'boolean',
2165 'default' => 0,
2166 'validate_callback' => __CLASS__ . '::validate_boolean',
2167 'jp_group' => 'subscriptions',
2168 ),
2169 'jetpack_subscriptions_subscribe_navigation_enabled' => array(
2170 'description' => esc_html__( 'Add Subscribe block to the navigation.', 'jetpack' ),
2171 'type' => 'boolean',
2172 'default' => 0,
2173 'validate_callback' => __CLASS__ . '::validate_boolean',
2174 'jp_group' => 'subscriptions',
2175 ),
2176 'social_notifications_subscribe' => array(
2177 'description' => esc_html__( 'Send email notification when someone subscribes to my blog', 'jetpack' ),
2178 'type' => 'boolean',
2179 'default' => 0,
2180 'validate_callback' => __CLASS__ . '::validate_boolean',
2181 'jp_group' => 'subscriptions',
2182 ),
2183 'subscription_options' => array(
2184 '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' ),
2185 'type' => 'object',
2186 'default' => array(
2187 'invitation' => '',
2188 'welcome' => '',
2189 'comment_follow' => '',
2190 'subscribe_modal_heading' => '',
2191 'free_tier_description' => '',
2192 'hide_free_tier' => false,
2193 ),
2194 'validate_callback' => __CLASS__ . '::validate_subscription_options',
2195 'jp_group' => 'subscriptions',
2196 ),
2197
2198 // Related Posts.
2199 'show_headline' => array(
2200 'description' => esc_html__( 'Highlight related content with a heading', 'jetpack' ),
2201 'type' => 'boolean',
2202 'default' => 1,
2203 'validate_callback' => __CLASS__ . '::validate_boolean',
2204 'jp_group' => 'related-posts',
2205 ),
2206 'show_thumbnails' => array(
2207 'description' => esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
2208 'type' => 'boolean',
2209 'default' => 0,
2210 'validate_callback' => __CLASS__ . '::validate_boolean',
2211 'jp_group' => 'related-posts',
2212 ),
2213
2214 // Search.
2215 'instant_search_enabled' => array(
2216 'description' => esc_html__( 'Enable Instant Search', 'jetpack' ),
2217 'type' => 'boolean',
2218 'default' => 0,
2219 'validate_callback' => __CLASS__ . '::validate_boolean',
2220 'jp_group' => 'search',
2221 ),
2222
2223 'has_jetpack_search_product' => array(
2224 'description' => esc_html__( 'Has an active Jetpack Search product purchase', 'jetpack' ),
2225 'type' => 'boolean',
2226 'default' => 0,
2227 'validate_callback' => __CLASS__ . '::validate_boolean',
2228 'jp_group' => 'settings',
2229 ),
2230
2231 'search_auto_config' => array(
2232 'description' => esc_html__( 'Trigger an auto config of instant search', 'jetpack' ),
2233 'type' => 'boolean',
2234 'default' => 0,
2235 'validate_callback' => __CLASS__ . '::validate_boolean',
2236 'jp_group' => 'search',
2237 ),
2238
2239 // Verification Tools.
2240 'google' => array(
2241 'description' => esc_html__( 'Google Search Console', 'jetpack' ),
2242 'type' => 'string',
2243 'default' => '',
2244 'validate_callback' => __CLASS__ . '::validate_verification_service',
2245 'jp_group' => 'verification-tools',
2246 ),
2247 'bing' => array(
2248 'description' => esc_html__( 'Bing Webmaster Center', 'jetpack' ),
2249 'type' => 'string',
2250 'default' => '',
2251 'validate_callback' => __CLASS__ . '::validate_verification_service',
2252 'jp_group' => 'verification-tools',
2253 ),
2254 'pinterest' => array(
2255 'description' => esc_html__( 'Pinterest Site Verification', 'jetpack' ),
2256 'type' => 'string',
2257 'default' => '',
2258 'validate_callback' => __CLASS__ . '::validate_verification_service',
2259 'jp_group' => 'verification-tools',
2260 ),
2261 'yandex' => array(
2262 'description' => esc_html__( 'Yandex Site Verification', 'jetpack' ),
2263 'type' => 'string',
2264 'default' => '',
2265 'validate_callback' => __CLASS__ . '::validate_verification_service',
2266 'jp_group' => 'verification-tools',
2267 ),
2268 'facebook' => array(
2269 'description' => esc_html__( 'Facebook Domain Verification', 'jetpack' ),
2270 'type' => 'string',
2271 'default' => '',
2272 'validate_callback' => __CLASS__ . '::validate_verification_service',
2273 'jp_group' => 'verification-tools',
2274 ),
2275
2276 // WordAds.
2277 'enable_header_ad' => array(
2278 'description' => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ),
2279 'type' => 'boolean',
2280 'default' => 1,
2281 'validate_callback' => __CLASS__ . '::validate_boolean',
2282 'jp_group' => 'wordads',
2283 ),
2284 'wordads_approved' => array(
2285 'description' => esc_html__( 'Is site approved for WordAds?', 'jetpack' ),
2286 'type' => 'boolean',
2287 'default' => 0,
2288 'validate_callback' => __CLASS__ . '::validate_boolean',
2289 'jp_group' => 'wordads',
2290 ),
2291 'wordads_second_belowpost' => array(
2292 'description' => esc_html__( 'Display second ad below post?', 'jetpack' ),
2293 'type' => 'boolean',
2294 'default' => 1,
2295 'validate_callback' => __CLASS__ . '::validate_boolean',
2296 'jp_group' => 'wordads',
2297 ),
2298 'wordads_inline_enabled' => array(
2299 'description' => esc_html__( 'Display inline ad within post content?', 'jetpack' ),
2300 'type' => 'boolean',
2301 'default' => 1,
2302 'validate_callback' => __CLASS__ . '::validate_boolean',
2303 'jp_group' => 'wordads',
2304 ),
2305 'wordads_display_front_page' => array(
2306 'description' => esc_html__( 'Display ads on the front page?', 'jetpack' ),
2307 'type' => 'boolean',
2308 'default' => 1,
2309 'validate_callback' => __CLASS__ . '::validate_boolean',
2310 'jp_group' => 'wordads',
2311 ),
2312 'wordads_display_post' => array(
2313 'description' => esc_html__( 'Display ads on posts?', 'jetpack' ),
2314 'type' => 'boolean',
2315 'default' => 1,
2316 'validate_callback' => __CLASS__ . '::validate_boolean',
2317 'jp_group' => 'wordads',
2318 ),
2319 'wordads_display_page' => array(
2320 'description' => esc_html__( 'Display ads on pages?', 'jetpack' ),
2321 'type' => 'boolean',
2322 'default' => 1,
2323 'validate_callback' => __CLASS__ . '::validate_boolean',
2324 'jp_group' => 'wordads',
2325 ),
2326 'wordads_display_archive' => array(
2327 'description' => esc_html__( 'Display ads on archive pages?', 'jetpack' ),
2328 'type' => 'boolean',
2329 'default' => 1,
2330 'validate_callback' => __CLASS__ . '::validate_boolean',
2331 'jp_group' => 'wordads',
2332 ),
2333 'wordads_custom_adstxt_enabled' => array(
2334 'description' => esc_html__( 'Custom ads.txt', 'jetpack' ),
2335 'type' => 'boolean',
2336 'default' => 0,
2337 'validate_callback' => __CLASS__ . '::validate_boolean',
2338 'jp_group' => 'wordads',
2339 ),
2340 'wordads_custom_adstxt' => array(
2341 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
2342 'type' => 'string',
2343 'default' => '',
2344 'validate_callback' => __CLASS__ . '::validate_string',
2345 'sanitize_callback' => 'sanitize_textarea_field',
2346 'jp_group' => 'wordads',
2347 ),
2348 'wordads_ccpa_enabled' => array(
2349 'description' => esc_html__( 'Enable support for California Consumer Privacy Act', 'jetpack' ),
2350 'type' => 'boolean',
2351 'default' => 0,
2352 'validate_callback' => __CLASS__ . '::validate_boolean',
2353 'jp_group' => 'wordads',
2354 ),
2355 'wordads_ccpa_privacy_policy_url' => array(
2356 'description' => esc_html__( 'Privacy Policy URL', 'jetpack' ),
2357 'type' => 'string',
2358 'default' => '',
2359 'validate_callback' => __CLASS__ . '::validate_string',
2360 'sanitize_callback' => 'sanitize_text_field',
2361 'jp_group' => 'wordads',
2362 ),
2363 'wordads_cmp_enabled' => array(
2364 'description' => esc_html__( 'Enable GDPR Consent Management Banner for WordAds', 'jetpack' ),
2365 'type' => 'boolean',
2366 'default' => 0,
2367 'validate_callback' => __CLASS__ . '::validate_boolean',
2368 'jp_group' => 'wordads',
2369 ),
2370
2371 // Google Analytics.
2372 'google_analytics_tracking_id' => array(
2373 'description' => esc_html__( 'Google Analytics', 'jetpack' ),
2374 'type' => 'string',
2375 'default' => '',
2376 'validate_callback' => __CLASS__ . '::validate_alphanum',
2377 'jp_group' => 'google-analytics',
2378 ),
2379 'jetpack_wga' => array(
2380 'description' => esc_html__( 'Google Analytics', 'jetpack' ),
2381 'type' => 'object',
2382 'jp_group' => 'settings',
2383 ),
2384
2385 // Stats.
2386 'admin_bar' => array(
2387 'description' => esc_html__( 'Include a small chart in your admin bar with a 48-hour traffic snapshot.', 'jetpack' ),
2388 'type' => 'boolean',
2389 'default' => 1,
2390 'validate_callback' => __CLASS__ . '::validate_boolean',
2391 'jp_group' => 'stats',
2392 ),
2393 'enable_odyssey_stats' => array(
2394 'description' => esc_html__( 'Preview the new Jetpack Stats experience (Experimental).', 'jetpack' ),
2395 'type' => 'boolean',
2396 'default' => 1,
2397 'validate_callback' => __CLASS__ . '::validate_boolean',
2398 'jp_group' => 'stats',
2399 ),
2400 'roles' => array(
2401 'description' => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ),
2402 'type' => 'array',
2403 'items' => array(
2404 'type' => 'string',
2405 ),
2406 'default' => array( 'administrator' ),
2407 'validate_callback' => __CLASS__ . '::validate_stats_roles',
2408 'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles',
2409 'jp_group' => 'stats',
2410 ),
2411 'count_roles' => array(
2412 'description' => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ),
2413 'type' => 'array',
2414 'items' => array(
2415 'type' => 'string',
2416 ),
2417 'default' => array( 'administrator' ),
2418 'validate_callback' => __CLASS__ . '::validate_stats_roles',
2419 'jp_group' => 'stats',
2420 ),
2421 'blog_id' => array(
2422 'description' => esc_html__( 'Blog ID.', 'jetpack' ),
2423 'type' => 'boolean',
2424 'default' => 0,
2425 'validate_callback' => __CLASS__ . '::validate_boolean',
2426 'jp_group' => 'stats',
2427 ),
2428 'do_not_track' => array(
2429 'description' => esc_html__( 'Do not track.', 'jetpack' ),
2430 'type' => 'boolean',
2431 'default' => 1,
2432 'validate_callback' => __CLASS__ . '::validate_boolean',
2433 'jp_group' => 'stats',
2434 ),
2435 'version' => array(
2436 'description' => esc_html__( 'Version.', 'jetpack' ),
2437 'type' => 'integer',
2438 'default' => 9,
2439 'validate_callback' => __CLASS__ . '::validate_posint',
2440 'jp_group' => 'stats',
2441 ),
2442 'collapse_nudges' => array(
2443 'description' => esc_html__( 'Collapse upgrade nudges', 'jetpack' ),
2444 'type' => 'boolean',
2445 'default' => 0,
2446 'validate_callback' => __CLASS__ . '::validate_boolean',
2447 'jp_group' => 'stats',
2448 ),
2449
2450 // Whether to share stats views with WordPress.com Reader.
2451 'wpcom_reader_views_enabled' => array(
2452 'description' => esc_html__( 'Show post views in the WordPress.com Reader.', 'jetpack' ),
2453 'type' => 'boolean',
2454 'default' => 1,
2455 'validate_callback' => __CLASS__ . '::validate_boolean',
2456 'jp_group' => 'settings',
2457 ),
2458
2459 // Akismet - Not a module, but a plugin. The options can be passed and handled differently.
2460 'akismet_show_user_comments_approved' => array(
2461 'description' => '',
2462 'type' => 'boolean',
2463 'default' => 0,
2464 'validate_callback' => __CLASS__ . '::validate_boolean',
2465 'jp_group' => 'settings',
2466 ),
2467
2468 'wordpress_api_key' => array(
2469 'description' => '',
2470 'type' => 'string',
2471 'default' => '',
2472 'validate_callback' => __CLASS__ . '::validate_alphanum',
2473 'jp_group' => 'settings',
2474 ),
2475
2476 // Empty stats card dismiss.
2477 'dismiss_empty_stats_card' => array(
2478 'description' => '',
2479 'type' => 'boolean',
2480 'default' => 0,
2481 'validate_callback' => __CLASS__ . '::validate_boolean',
2482 'jp_group' => 'settings',
2483 ),
2484
2485 // Backup Getting Started card on dashboard.
2486 'dismiss_dash_backup_getting_started' => array(
2487 'description' => '',
2488 'type' => 'boolean',
2489 'default' => 0,
2490 'validate_callback' => __CLASS__ . '::validate_boolean',
2491 'jp_group' => 'settings',
2492 ),
2493
2494 // Agencies Learn More card on dashboard.
2495 'dismiss_dash_agencies_learn_more' => array(
2496 'description' => '',
2497 'type' => 'boolean',
2498 'default' => 0,
2499 'validate_callback' => __CLASS__ . '::validate_boolean',
2500 'jp_group' => 'settings',
2501 ),
2502
2503 'lang_id' => array(
2504 'description' => esc_html__( 'Primary language for the site.', 'jetpack' ),
2505 'type' => 'string',
2506 'default' => 'en_US',
2507 'jp_group' => 'settings',
2508 ),
2509
2510 // SEO Tools.
2511 'advanced_seo_front_page_description' => array(
2512 'description' => esc_html__( 'Front page meta description.', 'jetpack' ),
2513 'type' => 'string',
2514 'default' => '',
2515 'sanitize_callback' => 'Jetpack_SEO_Utils::sanitize_front_page_meta_description',
2516 'jp_group' => 'seo-tools',
2517 ),
2518
2519 'advanced_seo_title_formats' => array(
2520 'description' => esc_html__( 'SEO page title structures.', 'jetpack' ),
2521 'type' => 'object',
2522 'default' => array(
2523 'archives' => array(),
2524 'front_page' => array(),
2525 'groups' => array(),
2526 'pages' => array(),
2527 'posts' => array(),
2528 ),
2529 'jp_group' => 'seo-tools',
2530 'validate_callback' => 'Jetpack_SEO_Titles::are_valid_title_formats',
2531 'sanitize_callback' => 'Jetpack_SEO_Titles::sanitize_title_formats',
2532 ),
2533
2534 // AI tab (Jetpack > SEO). Plain option the SEO package reads to serve
2535 // /llms.txt. The front-end behavior is gated inside the package; this
2536 // only round-trips the persisted state alongside the other seo-tools
2537 // settings.
2538 'jetpack_seo_llms_txt_enabled' => array(
2539 'description' => esc_html__( 'Generate an llms.txt file to guide AI assistants around your content.', 'jetpack' ),
2540 'type' => 'boolean',
2541 'default' => 0,
2542 'validate_callback' => __CLASS__ . '::validate_boolean',
2543 'jp_group' => 'seo-tools',
2544 ),
2545
2546 // AI tab (Jetpack > SEO). Sparse per-crawler override map the SEO
2547 // package reads to emit robots.txt directives for blocked AI crawlers.
2548 // Stored as `slug => bool` (true = blocked); catalog validation and
2549 // default-pruning happen in `Ai_Crawlers::get_overrides()`.
2550 'jetpack_seo_ai_crawler_overrides' => array(
2551 'description' => esc_html__( 'AI crawler allow/block overrides.', 'jetpack' ),
2552 'type' => 'object',
2553 'default' => array(),
2554 'jp_group' => 'seo-tools',
2555 'sanitize_callback' => __CLASS__ . '::sanitize_ai_crawler_overrides',
2556 ),
2557
2558 // VideoPress.
2559 'videopress_private_enabled_for_site' => array(
2560 'description' => esc_html__( 'Video Privacy: Restrict views to members of this site', 'jetpack' ),
2561 'type' => 'boolean',
2562 'default' => 0,
2563 'validate_callback' => __CLASS__ . '::validate_boolean',
2564 'jp_group' => 'videopress',
2565 ),
2566 'videopress_share_menu_disabled' => array(
2567 'description' => esc_html__( 'Hide the share menu on every video, overriding each video’s own setting', 'jetpack' ),
2568 'type' => 'boolean',
2569 'default' => 0,
2570 'validate_callback' => __CLASS__ . '::validate_boolean',
2571 'jp_group' => 'videopress',
2572 ),
2573 );
2574
2575 // SEO Tools - SEO Enhancer.
2576 // TODO: move this to the main options array? The filter was there while developing the feature.
2577 // It might come in handy to hold its availability behind the filter since it still depends on AI to be available.
2578 if ( apply_filters( 'ai_seo_enhancer_enabled', true ) ) {
2579 $options['ai_seo_enhancer_enabled'] = array(
2580 'description' => esc_html__( 'Automatically generate SEO title, SEO description, and image alt text for new posts.', 'jetpack' ),
2581 'type' => 'boolean',
2582 'default' => 0,
2583 'validate_callback' => __CLASS__ . '::validate_boolean',
2584 'jp_group' => 'seo-tools',
2585 );
2586 }
2587
2588 // Add modules to list so they can be toggled.
2589 $modules = Jetpack::get_available_modules();
2590 if ( is_array( $modules ) && ! empty( $modules ) ) {
2591 $module_args = array(
2592 'description' => '',
2593 'type' => 'boolean',
2594 'default' => 0,
2595 'validate_callback' => __CLASS__ . '::validate_boolean',
2596 'jp_group' => 'modules',
2597 );
2598 foreach ( $modules as $module ) {
2599 $options[ $module ] = $module_args;
2600 }
2601 }
2602
2603 if ( is_array( $selector ) ) {
2604
2605 // Return only those options whose keys match $selector keys.
2606 return array_intersect_key( $options, $selector );
2607 }
2608
2609 if ( 'any' === $selector ) {
2610
2611 // Toggle module or update any module option or any general setting.
2612 return $options;
2613 }
2614
2615 // We're updating the options for a single module.
2616 if ( empty( $selector ) ) {
2617 $selector = self::get_module_requested();
2618 }
2619 $selected = array();
2620 foreach ( $options as $option => $attributes ) {
2621
2622 // Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail.
2623 if ( $selector === $attributes['jp_group'] ) {
2624 $selected[ $option ] = $attributes;
2625 }
2626 }
2627 return $selected;
2628 }
2629
2630 /**
2631 * Validates that the parameters are proper values that can be set during Jetpack onboarding.
2632 *
2633 * @since 5.4.0
2634 *
2635 * @deprecated since 13.9
2636 *
2637 * @param array $onboarding_data Values to check.
2638 * @param WP_REST_Request $request The request sent to the WP REST API.
2639 * @param string $param Name of the parameter passed to endpoint holding $value.
2640 *
2641 * @return bool|WP_Error
2642 */
2643 public static function validate_onboarding( $onboarding_data, $request, $param ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
2644 _deprecated_function( __METHOD__, '13.9' );
2645 return true;
2646 }
2647
2648 /**
2649 * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean.
2650 *
2651 * @since 4.3.0
2652 *
2653 * @param string|bool $value Value to check.
2654 * @param WP_REST_Request $request The request sent to the WP REST API.
2655 * @param string $param Name of the parameter passed to endpoint holding $value.
2656 *
2657 * @return bool|WP_Error
2658 */
2659 public static function validate_boolean( $value, $request, $param ) {
2660 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- Other code depends on loose comparison here.
2661 if ( ! is_bool( $value ) && ! ( ctype_digit( (string) $value ) && in_array( $value, array( 0, 1 ) ) ) ) {
2662 return new WP_Error(
2663 'invalid_param',
2664 sprintf(
2665 /* Translators: Placeholder is a parameter name. */
2666 esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ),
2667 $param
2668 )
2669 );
2670 }
2671 return true;
2672 }
2673
2674 /**
2675 * Validates that the parameter is a positive integer.
2676 *
2677 * @since 4.3.0
2678 *
2679 * @param int $value Value to check.
2680 * @param WP_REST_Request $request The request sent to the WP REST API.
2681 * @param string $param Name of the parameter passed to endpoint holding $value.
2682 *
2683 * @return bool|WP_Error
2684 */
2685 public static function validate_posint( $value, $request, $param ) {
2686 if ( ! is_numeric( $value ) || $value <= 0 ) {
2687 return new WP_Error(
2688 'invalid_param',
2689 sprintf(
2690 /* Translators: Placeholder is a parameter name. */
2691 esc_html__( '%s must be a positive integer.', 'jetpack' ),
2692 $param
2693 )
2694 );
2695 }
2696 return true;
2697 }
2698
2699 /**
2700 * Validates that the parameter is a non-negative integer (includes 0).
2701 *
2702 * @since 10.4.0
2703 *
2704 * @param int $value Value to check.
2705 * @param WP_REST_Request $request The request sent to the WP REST API.
2706 * @param string $param Name of the parameter passed to endpoint holding $value.
2707 *
2708 * @return bool|WP_Error
2709 */
2710 public static function validate_non_neg_int( $value, $request, $param ) {
2711 if ( ! is_numeric( $value ) || $value < 0 ) {
2712 return new WP_Error(
2713 'invalid_param',
2714 /* translators: %s: The literal parameter name. Should not be translated. */
2715 sprintf( esc_html__( '%s must be a non-negative integer.', 'jetpack' ), $param )
2716 );
2717 }
2718 return true;
2719 }
2720
2721 /**
2722 * Validates that the parameter belongs to a list of admitted values.
2723 *
2724 * @since 4.3.0
2725 *
2726 * @param string $value Value to check.
2727 * @param WP_REST_Request $request The request sent to the WP REST API.
2728 * @param string $param Name of the parameter passed to endpoint holding $value.
2729 *
2730 * @return bool|WP_Error
2731 */
2732 public static function validate_list_item( $value, $request, $param ) {
2733 $attributes = $request->get_attributes();
2734 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
2735 return new WP_Error(
2736 'invalid_param',
2737 sprintf(
2738 /* Translators: Placeholder is a parameter name. */
2739 esc_html__( '%s not recognized', 'jetpack' ),
2740 $param
2741 )
2742 );
2743 }
2744 $args = $attributes['args'][ $param ];
2745 if ( ! empty( $args['enum'] ) ) {
2746 // If it's an associative array, use the keys to check that the value is among those admitted.
2747 $enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 )
2748 ? array_keys( $args['enum'] )
2749 : $args['enum'];
2750 $enum = array_map( 'strval', $enum );
2751 if ( ! in_array( $value, $enum, true ) ) {
2752 return new WP_Error(
2753 'invalid_param_value',
2754 sprintf(
2755 /* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */
2756 esc_html__( '%1$s must be one of %2$s', 'jetpack' ),
2757 $param,
2758 implode( ', ', $enum )
2759 )
2760 );
2761 }
2762 }
2763 return true;
2764 }
2765
2766 /**
2767 * Validates that the parameter belongs to a list of admitted values.
2768 *
2769 * @since 4.3.0
2770 *
2771 * @param string $value Value to check.
2772 * @param WP_REST_Request $request The request sent to the WP REST API.
2773 * @param string $param Name of the parameter passed to endpoint holding $value.
2774 *
2775 * @return bool|WP_Error
2776 */
2777 public static function validate_module_list( $value, $request, $param ) {
2778 if ( ! is_array( $value ) ) {
2779 return new WP_Error(
2780 'invalid_param_value',
2781 sprintf(
2782 /* Translators: Placeholder is a parameter name. */
2783 esc_html__( '%s must be an array', 'jetpack' ),
2784 $param
2785 )
2786 );
2787 }
2788
2789 $modules = Jetpack::get_available_modules();
2790
2791 if ( count( array_intersect( $value, $modules ) ) !== count( $value ) ) {
2792 return new WP_Error(
2793 'invalid_param_value',
2794 sprintf(
2795 /* Translators: Placeholder is a parameter name. */
2796 esc_html__( '%s must be a list of valid modules', 'jetpack' ),
2797 $param
2798 )
2799 );
2800 }
2801
2802 return true;
2803 }
2804
2805 /**
2806 * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field).
2807 *
2808 * @since 4.3.0
2809 *
2810 * @param string $value Value to check.
2811 * @param WP_REST_Request $request The request sent to the WP REST API.
2812 * @param string $param Name of the parameter passed to endpoint holding $value.
2813 *
2814 * @return bool|WP_Error
2815 */
2816 public static function validate_alphanum( $value, $request, $param ) {
2817 if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) {
2818 return new WP_Error(
2819 'invalid_param',
2820 sprintf(
2821 /* Translators: Placeholder is a parameter name. */
2822 esc_html__( '%s must be an alphanumeric string.', 'jetpack' ),
2823 $param
2824 )
2825 );
2826 }
2827 return true;
2828 }
2829
2830 /**
2831 * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field).
2832 *
2833 * @since 4.6.0
2834 *
2835 * @param string $value Value to check.
2836 * @param WP_REST_Request $request The request sent to the WP REST API.
2837 * @param string $param Name of the parameter passed to endpoint holding $value.
2838 *
2839 * @return bool|WP_Error
2840 */
2841 public static function validate_verification_service( $value, $request, $param ) {
2842 if ( ! empty( $value ) && ( ! is_string( $value ) || false === jetpack_verification_validate_code( $value ) ) ) {
2843 return new WP_Error(
2844 'invalid_param',
2845 sprintf(
2846 /* Translators: Placeholder is a verification string used to verify a service like Google Webmaster Console. */
2847 esc_html__( '%s must be a valid verification code or verification tag.', 'jetpack' ),
2848 $param
2849 )
2850 );
2851 }
2852 return true;
2853 }
2854
2855 /**
2856 * Validates that the parameter is among the roles allowed for Stats.
2857 *
2858 * @since 4.3.0
2859 *
2860 * @param mixed $value Value to check.
2861 * @param WP_REST_Request $request The request sent to the WP REST API.
2862 * @param string $param Name of the parameter passed to endpoint holding $value.
2863 *
2864 * @return bool|WP_Error
2865 */
2866 public static function validate_stats_roles( $value, $request, $param ) {
2867 // An empty value clears the setting; sanitize_stats_allowed_roles() falls back to 'administrator'.
2868 if ( empty( $value ) ) {
2869 return true;
2870 }
2871
2872 // Enforce the schema's list-of-strings contract before array_intersect() below sees the value.
2873 if ( ! is_array( $value ) || count( array_filter( $value, 'is_string' ) ) !== count( $value ) ) {
2874 return new WP_Error(
2875 'invalid_param',
2876 sprintf(
2877 /* Translators: Placeholder is a parameter name. */
2878 esc_html__( '%s must be an array of user roles.', 'jetpack' ),
2879 $param
2880 )
2881 );
2882 }
2883
2884 if ( ! function_exists( 'get_editable_roles' ) ) {
2885 require_once ABSPATH . 'wp-admin/includes/user.php';
2886 }
2887 $editable_roles = array_keys( get_editable_roles() );
2888 if ( ! array_intersect( $editable_roles, $value ) ) {
2889 return new WP_Error(
2890 'invalid_param',
2891 sprintf(
2892 /* 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. */
2893 esc_html__( '%1$s must be %2$s.', 'jetpack' ),
2894 $param,
2895 implode( ', ', $editable_roles )
2896 )
2897 );
2898 }
2899 return true;
2900 }
2901
2902 /**
2903 * Validates that the parameter is among the views where the Sharing can be displayed.
2904 *
2905 * @since 4.3.0
2906 *
2907 * @param string|bool $value Value to check.
2908 * @param WP_REST_Request $request The request sent to the WP REST API.
2909 * @param string $param Name of the parameter passed to endpoint holding $value.
2910 *
2911 * @return bool|WP_Error
2912 */
2913 public static function validate_sharing_show( $value, $request, $param ) {
2914 $views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' );
2915 if ( ! is_array( $value ) ) {
2916 return new WP_Error(
2917 'invalid_param',
2918 sprintf(
2919 /* Translators: Placeholder is a parameter name. */
2920 esc_html__( '%s must be an array of post types.', 'jetpack' ),
2921 $param
2922 )
2923 );
2924 }
2925 if ( ! array_intersect( $views, $value ) ) {
2926 return new WP_Error(
2927 'invalid_param',
2928 sprintf(
2929 /* 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 */
2930 esc_html__( '%1$s must be %2$s.', 'jetpack' ),
2931 $param,
2932 implode( ', ', $views )
2933 )
2934 );
2935 }
2936 return true;
2937 }
2938
2939 /**
2940 * Validates that the parameter is among the valid reply-to types for subscriptions.
2941 *
2942 * @since 4.3.0
2943 *
2944 * @param string|bool $value Value to check.
2945 * @param WP_REST_Request $request The request sent to the WP REST API.
2946 * @param string $param Name of the parameter passed to endpoint holding $value.
2947 *
2948 * @return bool|WP_Error
2949 */
2950 public static function validate_subscriptions_reply_to( $value, $request, $param ) {
2951 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
2952 if ( ! empty( $value ) && ! Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value ) ) {
2953 return new WP_Error(
2954 'invalid_param',
2955 sprintf(
2956 /* Translators: Placeholder is a parameter name. */
2957 esc_html__( '%s must be a valid type.', 'jetpack' ),
2958 $param
2959 )
2960 );
2961 }
2962 return true;
2963 }
2964
2965 /**
2966 * Validates that the parameter is among the valid reply-to types for subscriptions.
2967 *
2968 * @since 4.3.0
2969 *
2970 * @param string|bool $value Value to check.
2971 * @param WP_REST_Request $request The request sent to the WP REST API.
2972 * @param string $param Name of the parameter passed to endpoint holding $value.
2973 *
2974 * @return bool|WP_Error
2975 */
2976 public static function validate_subscriptions_reply_to_name( $value, $request, $param ) {
2977 if ( ! empty( $value ) && ! is_string( $value ) ) {
2978 return new WP_Error(
2979 'invalid_param',
2980 sprintf(
2981 /* Translators: Placeholder is a parameter name. */
2982 esc_html__( '%s must be a valid type.', 'jetpack' ),
2983 $param
2984 )
2985 );
2986 }
2987 return true;
2988 }
2989
2990 /**
2991 * Validates that the parameter is among the views where the Sharing can be displayed.
2992 *
2993 * @since 4.3.0
2994 *
2995 * @param string|bool $value {
2996 * Value to check received by request.
2997 *
2998 * @type array $visible List of slug of services to share to that are displayed directly in the page.
2999 * @type array $hidden List of slug of services to share to that are concealed in a folding menu.
3000 * }
3001 * @param WP_REST_Request $request The request sent to the WP REST API.
3002 * @param string $param Name of the parameter passed to endpoint holding $value.
3003 *
3004 * @return bool|WP_Error
3005 */
3006 public static function validate_services( $value, $request, $param ) {
3007 if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) {
3008 return new WP_Error(
3009 'invalid_param',
3010 sprintf(
3011 /* Translators: Placeholder is a parameter name. */
3012 esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ),
3013 $param
3014 )
3015 );
3016 }
3017
3018 // Allow to clear everything.
3019 if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) {
3020 return true;
3021 }
3022
3023 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3024 return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
3025 }
3026 $sharer = new Sharing_Service();
3027 $services = array_keys( $sharer->get_all_services() );
3028
3029 if (
3030 ( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) )
3031 ||
3032 ( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) ) {
3033 return new WP_Error(
3034 'invalid_param',
3035 sprintf(
3036 /* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */
3037 esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ),
3038 $param,
3039 implode( ', ', $services )
3040 )
3041 );
3042 }
3043 return true;
3044 }
3045
3046 /**
3047 * Validates that the parameter has enough information to build a custom sharing button.
3048 *
3049 * @since 4.3.0
3050 *
3051 * @param string|bool $value Value to check.
3052 * @param WP_REST_Request $request The request sent to the WP REST API.
3053 * @param string $param Name of the parameter passed to endpoint holding $value.
3054 *
3055 * @return bool|WP_Error
3056 */
3057 public static function validate_custom_service( $value, $request, $param ) {
3058 if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) {
3059 return new WP_Error(
3060 'invalid_param',
3061 sprintf(
3062 /* Translators: Placeholder is a parameter name. */
3063 esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ),
3064 $param
3065 )
3066 );
3067 }
3068
3069 // Allow to clear everything.
3070 if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) {
3071 return true;
3072 }
3073
3074 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3075 return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
3076 }
3077
3078 if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) )
3079 || ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) )
3080 || ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) {
3081 return new WP_Error(
3082 'invalid_param',
3083 sprintf(
3084 /* Translators: Placeholder is a parameter name. */
3085 esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ),
3086 $param
3087 )
3088 );
3089 }
3090 return true;
3091 }
3092
3093 /**
3094 * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'.
3095 *
3096 * @since 4.3.0
3097 *
3098 * @param string $value Value to check.
3099 * @param WP_REST_Request $request The request sent to the WP REST API.
3100 * @param string $param Name of the parameter passed to endpoint holding $value.
3101 *
3102 * @return bool|WP_Error
3103 */
3104 public static function validate_custom_service_id( $value, $request, $param ) {
3105 if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) {
3106 return new WP_Error(
3107 'invalid_param',
3108 sprintf(
3109 /* Translators: Placeholder is a parameter name. */
3110 esc_html__( "%s must be a string prefixed with 'custom-' and followed by a numeric ID.", 'jetpack' ),
3111 $param
3112 )
3113 );
3114 }
3115
3116 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3117 return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) );
3118 }
3119 $sharer = new Sharing_Service();
3120 $services = $sharer->get_all_services();
3121
3122 if ( ! empty( $value ) && ! isset( $services[ $value ] ) ) {
3123 return new WP_Error(
3124 'invalid_param',
3125 sprintf(
3126 /* Translators: Placeholder is a parameter name. */
3127 esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ),
3128 $param
3129 )
3130 );
3131 }
3132
3133 return true;
3134 }
3135
3136 /**
3137 * Validates that the parameter is a Twitter username or empty string (to be able to clear the field).
3138 *
3139 * @since 4.3.0
3140 *
3141 * @param string $value Value to check.
3142 * @param WP_REST_Request $request The request sent to the WP REST API.
3143 * @param string $param Name of the parameter passed to endpoint holding $value.
3144 *
3145 * @return bool|WP_Error
3146 */
3147 public static function validate_twitter_username( $value, $request, $param ) {
3148 if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) {
3149 return new WP_Error(
3150 'invalid_param',
3151 sprintf(
3152 /* Translators: Placeholder is a twitter name. */
3153 esc_html__( '%s must be a Twitter username.', 'jetpack' ),
3154 $param
3155 )
3156 );
3157 }
3158 return true;
3159 }
3160
3161 /**
3162 * Validates that the parameter is a string.
3163 *
3164 * @since 4.3.0
3165 *
3166 * @param string $value Value to check.
3167 * @param WP_REST_Request $request The request sent to the WP REST API.
3168 * @param string $param Name of the parameter passed to endpoint holding $value.
3169 *
3170 * @return bool|WP_Error
3171 */
3172 public static function validate_string( $value, $request, $param ) {
3173 if ( ! is_string( $value ) ) {
3174 return new WP_Error(
3175 'invalid_param',
3176 sprintf(
3177 /* Translators: Placeholder is a parameter name. */
3178 esc_html__( '%s must be a string.', 'jetpack' ),
3179 $param
3180 )
3181 );
3182 }
3183 return true;
3184 }
3185
3186 /**
3187 * Validates that the parameter is an array of strings.
3188 *
3189 * @param array $value Value to check.
3190 * @param WP_REST_Request $request The request sent to the WP REST API.
3191 * @param string $param Name of the parameter passed to the endpoint holding $value.
3192 *
3193 * @return bool|WP_Error
3194 */
3195 public static function validate_array_of_strings( $value, $request, $param ) {
3196 foreach ( $value as $array_item ) {
3197 $validate = self::validate_string( $array_item, $request, $param );
3198 if ( is_wp_error( $validate ) ) {
3199 return $validate;
3200 }
3201 }
3202
3203 return true;
3204 }
3205
3206 /**
3207 * Validates the subscription_options parameter.
3208 *
3209 * @param array $values Value to check.
3210 *
3211 * @return bool|WP_Error
3212 */
3213 public static function validate_subscription_options( $values ) {
3214 // A REST "object" decodes to a PHP associative array. Reject any other
3215 // type (object, string, int, null, ...) up front so the array_keys()
3216 // loop below never runs against a non-array and triggers a PHP warning.
3217 if ( ! is_array( $values ) ) {
3218 return new WP_Error(
3219 'invalid_param',
3220 /* Translators: subscription_options is a variable name, and shouldn't be translated. */
3221 esc_html__( 'subscription_options must be an object.', 'jetpack' )
3222 );
3223 }
3224 foreach ( array_keys( $values ) as $key ) {
3225 if ( ! in_array( $key, array( 'welcome', 'invitation', 'comment_follow', 'subscribe_modal_heading', 'free_tier_description', 'hide_free_tier' ), true ) ) {
3226 return new WP_Error(
3227 'invalid_param',
3228 sprintf(
3229 /* Translators: Placeholder is the invalid param being sent. */
3230 esc_html__( '%s is not one of the allowed members of subscription_options.', 'jetpack' ),
3231 $key
3232 )
3233 );
3234 }
3235 }
3236 return true;
3237 }
3238
3239 /**
3240 * Validates that the parameter is an array.
3241 *
3242 * @param array $values Value to check.
3243 * @param WP_REST_Request $request The request sent to the WP REST API.
3244 * @param string $param Name of the parameter passed to the endpoint holding $value.
3245 *
3246 * @return bool|WP_Error
3247 */
3248 public static function validate_array( $values, $request, $param ) {
3249 if ( ! is_array( $values ) ) {
3250 return new WP_Error(
3251 'invalid_param',
3252 sprintf(
3253 /* Translators: Placeholder is a parameter name. */
3254 esc_html__( '%s must be an object.', 'jetpack' ),
3255 $param
3256 )
3257 );
3258 }
3259 return true;
3260 }
3261
3262 /**
3263 * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes),
3264 * return an array with only 'administrator' as the allowed role and save it for 'roles' option.
3265 *
3266 * @since 4.3.0
3267 *
3268 * @param mixed $value Value to check.
3269 *
3270 * @return mixed The value as submitted, or an array holding only 'administrator' when it is empty.
3271 */
3272 public static function sanitize_stats_allowed_roles( $value ) {
3273 if ( empty( $value ) ) {
3274 return array( 'administrator' );
3275 }
3276 return $value;
3277 }
3278
3279 /**
3280 * Sanitize the AI crawler override map.
3281 *
3282 * Keeps the value package-agnostic: each key is normalized with sanitize_key()
3283 * and each value cast to bool. Catalog validation and default-pruning happen in
3284 * `Automattic\Jetpack\SEO\Ai_Crawlers::get_overrides()`.
3285 *
3286 * @param mixed $value The submitted override map.
3287 *
3288 * @return array<string, bool> Sanitized `slug => bool` map.
3289 */
3290 public static function sanitize_ai_crawler_overrides( $value ) {
3291 if ( ! is_array( $value ) ) {
3292 return array();
3293 }
3294
3295 $sanitized = array();
3296 foreach ( $value as $k => $v ) {
3297 $sanitized[ sanitize_key( $k ) ] = (bool) $v;
3298 }
3299 return $sanitized;
3300 }
3301
3302 /**
3303 * Get the currently accessed route and return the module slug in it.
3304 *
3305 * @since 4.3.0
3306 *
3307 * @param string $route Regular expression for the endpoint with the module slug to return.
3308 *
3309 * @return array|string
3310 */
3311 public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) {
3312
3313 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) || ! is_string( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
3314 return '';
3315 }
3316
3317 preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module );
3318
3319 if ( empty( $module['slug'] ) ) {
3320 return '';
3321 }
3322
3323 return $module['slug'];
3324 }
3325
3326 /**
3327 * Adds extra information for modules.
3328 *
3329 * @since 4.3.0
3330 *
3331 * @param string|array $modules Can be a single module or a list of modules.
3332 * @param null|string $slug Slug of the module in the first parameter.
3333 *
3334 * @return array|string
3335 */
3336 public static function prepare_modules_for_response( $modules = '', $slug = null ) {
3337 global $wp_rewrite;
3338
3339 /** This filter is documented in modules/sitemaps/sitemaps.php */
3340 $location = apply_filters( 'jetpack_sitemap_location', '' );
3341
3342 if ( $wp_rewrite->using_index_permalinks() ) {
3343 $sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' );
3344 $news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' );
3345 } elseif ( $wp_rewrite->using_permalinks() ) {
3346 $sitemap_url = home_url( $location . '/sitemap.xml' );
3347 $news_sitemap_url = home_url( $location . '/news-sitemap.xml' );
3348 } else {
3349 $sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' );
3350 $news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' );
3351 }
3352
3353 if ( $slug === null && isset( $modules['sitemaps'] ) ) {
3354 // Is a list of modules.
3355 $modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url;
3356 $modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url;
3357 } elseif ( 'sitemaps' === $slug ) {
3358 // It's a single module.
3359 $modules['extra']['sitemap_url'] = $sitemap_url;
3360 $modules['extra']['news_sitemap_url'] = $news_sitemap_url;
3361 }
3362 return $modules;
3363 }
3364
3365 /**
3366 * Remove options the current user cannot read.
3367 *
3368 * Covers every `jetpack_waf_*` option, plus the two Protect options that expose the
3369 * same data under a different name: `jetpack_protect_global_whitelist` is populated
3370 * from `jetpack_waf_ip_allow_list`, and `jetpack_protect_key` is a shared secret.
3371 *
3372 * @since 16.2
3373 *
3374 * @param array $options Option definitions keyed by option name.
3375 * @return array
3376 */
3377 public static function filter_options_for_response( $options ) {
3378 if ( current_user_can( 'manage_options' ) ) {
3379 return $options;
3380 }
3381
3382 $restricted = array(
3383 'jetpack_protect_key',
3384 'jetpack_protect_global_whitelist',
3385 );
3386
3387 return array_filter(
3388 $options,
3389 static function ( $option_name ) use ( $restricted ) {
3390 return 0 !== strpos( $option_name, 'jetpack_waf_' )
3391 && ! in_array( $option_name, $restricted, true );
3392 },
3393 ARRAY_FILTER_USE_KEY
3394 );
3395 }
3396
3397 /**
3398 * Remove 'validate_callback' item from options available for module.
3399 * Fetch current option value and add to array of module options.
3400 * Prepare values of module options that need special handling, like those saved in wpcom.
3401 *
3402 * @since 4.3.0
3403 *
3404 * @param string $module Module slug.
3405 * @return array
3406 */
3407 public static function prepare_options_for_response( $module = '' ) {
3408 $options = self::get_updateable_data_list( $module );
3409
3410 if ( ! is_array( $options ) || empty( $options ) ) {
3411 return $options;
3412 }
3413
3414 // Some modules need special treatment.
3415 switch ( $module ) {
3416
3417 case 'monitor':
3418 // Status of user notifications.
3419 $options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] );
3420 break;
3421
3422 case 'post-by-email':
3423 // Email address.
3424 $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'] );
3425 break;
3426
3427 case 'protect':
3428 // Protect.
3429 $options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false );
3430 $options['jetpack_protect_global_whitelist']['current_value'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
3431 break;
3432
3433 case 'related-posts':
3434 // It's local, but it must be broken apart since it's saved as an array.
3435 $options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) );
3436 break;
3437
3438 case 'verification-tools':
3439 // It's local, but it must be broken apart since it's saved as an array.
3440 $options = self::split_options( $options, get_option( 'verification_services_codes' ) );
3441 break;
3442
3443 case 'google-analytics':
3444 $wga = get_option( 'jetpack_wga' );
3445 $code = '';
3446 if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
3447 $code = $wga['code'];
3448 }
3449 $options['google_analytics_tracking_id']['current_value'] = $code;
3450 break;
3451
3452 case 'sharedaddy':
3453 // It's local, but it must be broken apart since it's saved as an array.
3454 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
3455 break;
3456 }
3457 $sharer = new Sharing_Service();
3458 $options = self::split_options( $options, $sharer->get_global_options() );
3459 $options['sharing_services']['current_value'] = $sharer->get_blog_services();
3460 $other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' );
3461 foreach ( $other_sharedaddy_options as $key ) {
3462 $default_value = $options[ $key ]['default'] ?? '';
3463 $current_value = get_option( $key, $default_value );
3464 $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
3465 }
3466 break;
3467
3468 case 'stats':
3469 // It's local, but it must be broken apart since it's saved as an array.
3470 $options = self::split_options( $options, Stats_Options::get_options() );
3471 break;
3472 default:
3473 // These option are just stored as plain WordPress options.
3474 foreach ( $options as $key => $value ) {
3475 $default_value = $options[ $key ]['default'] ?? '';
3476 $current_value = get_option( $key, $default_value );
3477 $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] );
3478 }
3479 }
3480 // At this point some options have current_value not set because they're options
3481 // that only get written on update, so we set current_value to the default one.
3482 foreach ( $options as $key => $value ) {
3483 // We don't need validate_callback in the response.
3484 if ( isset( $options[ $key ]['validate_callback'] ) ) {
3485 unset( $options[ $key ]['validate_callback'] );
3486 }
3487 $default_value = $options[ $key ]['default'] ?? '';
3488 if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) {
3489 $options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] );
3490 }
3491 }
3492
3493 // Filter last: the switch above assigns current_value by key without isset(),
3494 // so filtering earlier would let those assignments re-add a removed option.
3495 return self::filter_options_for_response( $options );
3496 }
3497
3498 /**
3499 * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response.
3500 *
3501 * @since 4.3.0
3502 *
3503 * @param array $separate_options Array of options admitted by the module.
3504 * @param array $grouped_options Option saved as array to be splitted.
3505 * @param string $prefix Optional prefix for the separate option keys.
3506 *
3507 * @return array
3508 */
3509 public static function split_options( $separate_options, $grouped_options, $prefix = '' ) {
3510 if ( is_array( $grouped_options ) ) {
3511 foreach ( $grouped_options as $key => $value ) {
3512 $option_key = $prefix . $key;
3513 if ( isset( $separate_options[ $option_key ] ) ) {
3514 $separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] );
3515 }
3516 }
3517 }
3518 return $separate_options;
3519 }
3520
3521 /**
3522 * Perform a casting to the value specified in the option definition.
3523 *
3524 * @since 4.3.0
3525 *
3526 * @param mixed $value Value to cast to the proper type.
3527 * @param array $definition Type to cast the value to.
3528 *
3529 * @return bool|float|int|string
3530 */
3531 public static function cast_value( $value, $definition ) {
3532 if ( 'NULL' === $value ) {
3533 return null;
3534 }
3535
3536 if ( isset( $definition['type'] ) ) {
3537 switch ( $definition['type'] ) {
3538 case 'boolean':
3539 if ( 'true' === $value || 'on' === $value ) {
3540 return true;
3541 } elseif ( 'false' === $value || 'off' === $value ) {
3542 return false;
3543 }
3544 $value = (bool) $value;
3545 break;
3546
3547 case 'integer':
3548 $value = (int) $value;
3549 break;
3550
3551 case 'float':
3552 $value = (float) $value;
3553 break;
3554
3555 case 'string':
3556 $value = (string) $value;
3557 break;
3558 }
3559 }
3560 return $value;
3561 }
3562
3563 /**
3564 * Get a value not saved locally.
3565 *
3566 * @since 4.3.0
3567 *
3568 * @param string $module Module slug.
3569 * @param string $option Option name.
3570 *
3571 * @return bool Whether user is receiving notifications or not.
3572 */
3573 public static function get_remote_value( $module, $option ) {
3574
3575 if ( in_array( $module, array( 'post-by-email' ), true ) ) {
3576 $option .= get_current_user_id();
3577 }
3578
3579 // If option doesn't exist, 'does_not_exist' will be returned.
3580 $value = get_option( $option, 'does_not_exist' );
3581
3582 // If option exists, just return it.
3583 if ( 'does_not_exist' !== $value ) {
3584 return $value;
3585 }
3586
3587 // Only check a remote option if Jetpack is connected.
3588 if ( ! Jetpack::is_connection_ready() ) {
3589 return false;
3590 }
3591
3592 // Do what is necessary for each module.
3593 switch ( $module ) {
3594 case 'monitor':
3595 // Load the class to use the method. If class can't be found, do nothing.
3596 if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once Jetpack::get_module_path( $module ) ) {
3597 return false;
3598 }
3599 $value = Jetpack_Monitor::user_receives_notifications( false );
3600 break;
3601
3602 case 'post-by-email':
3603 // Load the class to use the method. If class can't be found, do nothing.
3604 if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once Jetpack::get_module_path( $module ) ) {
3605 return false;
3606 }
3607 $value = Jetpack_Post_By_Email::init()->get_post_by_email_address();
3608 if ( null === $value ) {
3609 $value = 'NULL'; // sentinel value so it actually gets set.
3610 }
3611 break;
3612 }
3613
3614 // Normalize value to boolean.
3615 if ( is_wp_error( $value ) || $value === null ) {
3616 $value = false;
3617 }
3618
3619 // Save option to use it next time.
3620 update_option( $option, $value );
3621
3622 return $value;
3623 }
3624
3625 /**
3626 * Get number of plugin updates available.
3627 *
3628 * @since 4.3.0
3629 *
3630 * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error.
3631 */
3632 public static function get_plugin_update_count() {
3633 $updates = wp_get_update_data();
3634 if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) {
3635 $count = $updates['counts']['plugins'];
3636 if ( 0 === $count ) {
3637 $response = array(
3638 'code' => 'success',
3639 'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ),
3640 'count' => 0,
3641 );
3642 } else {
3643 $response = array(
3644 'code' => 'updates-available',
3645 'message' => esc_html(
3646 sprintf(
3647 /* Translators: placeholders are numbers. */
3648 _n( '%s plugin needs updating.', '%s plugins need updating.', $count, 'jetpack' ),
3649 $count
3650 )
3651 ),
3652 'count' => $count,
3653 );
3654 }
3655 return rest_ensure_response( $response );
3656 }
3657
3658 return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) );
3659 }
3660
3661 /**
3662 * Get plugins data in site.
3663 *
3664 * @since 4.2.0
3665 *
3666 * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error.
3667 */
3668 public static function get_plugins() {
3669 $plugins = Plugins_Installer::get_plugins();
3670
3671 if ( ! empty( $plugins ) ) {
3672 return rest_ensure_response( $plugins );
3673 }
3674
3675 return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) );
3676 }
3677
3678 /**
3679 * Install a specific plugin and optionally activates it.
3680 *
3681 * @since 8.9.0
3682 *
3683 * @param WP_REST_Request $request {
3684 * Array of parameters received by request.
3685 *
3686 * @type string $slug Plugin slug.
3687 * @type string $status Plugin status.
3688 * @type string $source Where did the plugin installation request originate.
3689 * }
3690 *
3691 * @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.
3692 */
3693 public static function install_plugin( $request ) {
3694 $plugin = stripslashes( $request['slug'] );
3695
3696 // Let's make sure the plugin isn't already installed.
3697 $plugin_id = Plugins_Installer::get_plugin_id_by_slug( $plugin );
3698
3699 // If not installed, let's install now.
3700 if ( ! $plugin_id ) {
3701 $result = Plugins_Installer::install_plugin( $plugin );
3702
3703 if ( is_wp_error( $result ) ) {
3704 return new WP_Error(
3705 'install_plugin_failed',
3706 sprintf(
3707 /* translators: %1$s: plugin name. -- %2$s: error message. */
3708 __( 'Unable to install %1$s: %2$s ', 'jetpack' ),
3709 $plugin,
3710 $result->get_error_message()
3711 ),
3712 array( 'status' => 500 )
3713 );
3714 }
3715 }
3716
3717 /*
3718 * We may want to activate the plugin as well.
3719 * Let's check for the status parameter in the request to find out.
3720 * If none was passed (or something other than active), let's return now.
3721 */
3722 if ( empty( $request['status'] ) || 'active' !== $request['status'] ) {
3723 return rest_ensure_response(
3724 array(
3725 'code' => 'success',
3726 'message' => esc_html(
3727 sprintf(
3728 /* translators: placeholder is a plugin name. */
3729 __( 'Installed %s', 'jetpack' ),
3730 $plugin
3731 )
3732 ),
3733 )
3734 );
3735 }
3736
3737 /*
3738 * Proceed with plugin activation.
3739 * Let's check again for the plugin's ID if we don't already have it.
3740 */
3741 if ( ! $plugin_id ) {
3742 $plugin_id = Plugins_Installer::get_plugin_id_by_slug( $plugin );
3743 if ( ! $plugin_id ) {
3744 return new WP_Error(
3745 'unable_to_determine_installed_plugin',
3746 __( 'Unable to determine what plugin was installed.', 'jetpack' ),
3747 array( 'status' => 500 )
3748 );
3749 }
3750 }
3751
3752 $source = ! empty( $request['source'] ) ? stripslashes( $request['source'] ) : 'rest_api';
3753 $plugin_args = array(
3754 'plugin' => substr( $plugin_id, 0, - 4 ),
3755 'status' => 'active',
3756 'source' => $source,
3757 );
3758 return self::activate_plugin( $plugin_args );
3759 }
3760
3761 /**
3762 * Activate a specific plugin.
3763 *
3764 * @since 8.9.0
3765 *
3766 * @param WP_REST_Request $request {
3767 * Array of parameters received by request.
3768 *
3769 * @type string $plugin Plugin long slug (slug/index-file)
3770 * @type string $status Plugin status. We only support active in Jetpack.
3771 * @type string $source Where did the plugin installation request originate.
3772 * }
3773 *
3774 * @return WP_REST_Response|WP_Error A response object if the activation was successful, or a WP_Error object if the activation failed.
3775 */
3776 public static function activate_plugin( $request ) {
3777 /*
3778 * We need an "active" status parameter to be passed to the request
3779 * just like the core plugins endpoind we'll eventually switch to.
3780 */
3781 if ( empty( $request['status'] ) || 'active' !== $request['status'] ) {
3782 return new WP_Error(
3783 'missing_status_parameter',
3784 esc_html__( 'Status parameter missing.', 'jetpack' ),
3785 array( 'status' => 403 )
3786 );
3787 }
3788
3789 $plugins = Plugins_Installer::get_plugins();
3790
3791 if ( empty( $plugins ) ) {
3792 return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3793 }
3794
3795 if ( empty( $request['plugin'] ) ) {
3796 return new WP_Error( 'no_plugin_specified', esc_html__( 'You did not specify a plugin.', 'jetpack' ), array( 'status' => 404 ) );
3797 }
3798
3799 $plugin = $request['plugin'] . '.php';
3800
3801 // Is the plugin installed?
3802 if ( ! array_key_exists( $plugin, $plugins ) ) {
3803 return new WP_Error(
3804 'plugin_not_found',
3805 esc_html(
3806 sprintf(
3807 /* translators: placeholder is a plugin slug. */
3808 __( 'Plugin %s is not installed.', 'jetpack' ),
3809 $plugin
3810 )
3811 ),
3812 array( 'status' => 404 )
3813 );
3814 }
3815
3816 // Is the plugin active already?
3817 $status = Plugins_Installer::get_plugin_status( $plugin );
3818 if ( in_array( $status, array( 'active', 'network-active' ), true ) ) {
3819 return new WP_Error(
3820 'plugin_already_active',
3821 esc_html(
3822 sprintf(
3823 /* translators: placeholder is a plugin slug. */
3824 __( 'Plugin %s is already active.', 'jetpack' ),
3825 $plugin
3826 )
3827 ),
3828 array( 'status' => 404 )
3829 );
3830 }
3831
3832 // Now try to activate the plugin.
3833 $activated = activate_plugin( $plugin );
3834
3835 if ( is_wp_error( $activated ) ) {
3836 return $activated;
3837 } else {
3838 $source = ! empty( $request['source'] ) ? stripslashes( $request['source'] ) : 'rest_api';
3839 /**
3840 * Fires when Jetpack installs a plugin for you.
3841 *
3842 * @since 8.9.0
3843 *
3844 * @param string $plugin_file Plugin file.
3845 * @param string $source Where did the plugin installation originate.
3846 */
3847 do_action( 'jetpack_activated_plugin', $plugin, $source );
3848 return rest_ensure_response(
3849 array(
3850 'code' => 'success',
3851 'message' => sprintf(
3852 /* translators: placeholder is a plugin name. */
3853 esc_html__( 'Activated %s', 'jetpack' ),
3854 $plugin
3855 ),
3856 )
3857 );
3858 }
3859 }
3860
3861 /**
3862 * Check if a plugin can be activated.
3863 *
3864 * @since 8.9.0
3865 *
3866 * @param string|bool $value Value to check.
3867 * @param WP_REST_Request $request The request sent to the WP REST API.
3868 * @param string $param Name of the parameter passed to endpoint holding $value.
3869 */
3870 public static function validate_activate_plugin( $value, $request, $param ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
3871 return 'active' === $value;
3872 }
3873
3874 /**
3875 * Get data about the queried plugin. Currently it only returns whether the plugin is active or not.
3876 *
3877 * @since 4.2.0
3878 *
3879 * @param WP_REST_Request $request {
3880 * Array of parameters received by request.
3881 *
3882 * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
3883 * }
3884 *
3885 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
3886 */
3887 public static function get_plugin( $request ) {
3888 $plugins = Plugins_Installer::get_plugins();
3889
3890 if ( empty( $plugins ) ) {
3891 return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) );
3892 }
3893
3894 $plugin = stripslashes( $request['plugin'] );
3895
3896 if ( ! array_key_exists( $plugin, $plugins ) ) {
3897 return new WP_Error(
3898 'plugin_not_found',
3899 esc_html(
3900 sprintf(
3901 /* Translators: placeholder is a plugin name. */
3902 __( 'Plugin %s is not installed.', 'jetpack' ),
3903 $plugin
3904 )
3905 ),
3906 array( 'status' => 404 )
3907 );
3908 }
3909
3910 $plugin_data = $plugins[ $plugin ];
3911
3912 $plugin_data['active'] = in_array( Plugins_Installer::get_plugin_status( $plugin ), array( 'active', 'network-active' ), true );
3913
3914 return rest_ensure_response(
3915 array(
3916 'code' => 'success',
3917 'message' => esc_html__( 'Plugin found.', 'jetpack' ),
3918 'data' => $plugin_data,
3919 )
3920 );
3921 }
3922
3923 /**
3924 * Returns the Jetpack CRM data.
3925 *
3926 * @return WP_REST_Response A response object containing the Jetpack CRM data.
3927 */
3928 public static function get_jetpack_crm_data() {
3929 $jetpack_crm_data = ( new Jetpack_CRM_Data() )->get_crm_data();
3930 return rest_ensure_response( $jetpack_crm_data );
3931 }
3932
3933 /**
3934 * Activates Jetpack CRM's Jetpack Forms extension.
3935 *
3936 * @param WP_REST_Request $request The request sent to the WP REST API.
3937 * @return WP_REST_Response|WP_Error A response object if the extension activation was successful, or a WP_Error object if it failed.
3938 */
3939 public static function activate_crm_jetpack_forms_extension( $request ) {
3940 if ( ! isset( $request['extension'] ) || 'jetpackforms' !== $request['extension'] ) {
3941 return new WP_Error( 'invalid_param', esc_html__( 'Missing or invalid extension parameter.', 'jetpack' ), array( 'status' => 404 ) );
3942 }
3943
3944 $result = ( new Jetpack_CRM_Data() )->activate_crm_jetpackforms_extension();
3945
3946 if ( is_wp_error( $result ) ) {
3947 return $result;
3948 }
3949
3950 return rest_ensure_response( array( 'code' => 'success' ) );
3951 }
3952
3953 /**
3954 * Verifies that the current user has the required permission for accessing the CRM data.
3955 *
3956 * @return true|WP_Error Returns true if the user has the required capability, else a WP_Error object.
3957 */
3958 public static function jetpack_crm_data_permission_check() {
3959 if ( current_user_can( 'publish_posts' ) ) {
3960 return true;
3961 }
3962
3963 return new WP_Error(
3964 'invalid_user_permission_jetpack_crm_data',
3965 REST_Connector::get_user_permissions_error_msg(),
3966 array( 'status' => rest_authorization_required_code() )
3967 );
3968 }
3969
3970 /**
3971 * Verifies that the current user has the required capability for activating Jetpack CRM extensions.
3972 *
3973 * @return true|WP_Error Returns true if the user has the required capability, else a WP_Error object.
3974 */
3975 public static function activate_crm_extensions_permission_check() {
3976 // phpcs:ignore WordPress.WP.Capabilities.Unknown
3977 if ( current_user_can( 'admin_zerobs_manage_options' ) ) {
3978 return true;
3979 }
3980
3981 return new WP_Error(
3982 'invalid_user_permission_activate_jetpack_crm_ext',
3983 REST_Connector::get_user_permissions_error_msg(),
3984 array( 'status' => rest_authorization_required_code() )
3985 );
3986 }
3987
3988 /**
3989 * Set hasSeenWCConnectionModal to true when the site has displayed it
3990 *
3991 * @since 10.4.0
3992 *
3993 * @return bool
3994 */
3995 public static function set_has_seen_wc_connection_modal() {
3996 $updated_option = Jetpack_Options::update_option( 'has_seen_wc_connection_modal', true );
3997
3998 return rest_ensure_response( array( 'success' => $updated_option ) );
3999 }
4000
4001 /**
4002 * Return the list of available features.
4003 *
4004 * @return array
4005 */
4006 public static function get_features_available() {
4007 $raw_modules = Jetpack::get_available_modules();
4008 $modules = array();
4009 foreach ( $raw_modules as $module ) {
4010 $modules[] = Jetpack::get_module_slug( $module );
4011 }
4012
4013 return $modules;
4014 }
4015
4016 /**
4017 * Returns what features are enabled. Uses the slug of the modules files.
4018 *
4019 * @return array
4020 */
4021 public static function get_features_enabled() {
4022 $raw_modules = Jetpack::get_active_modules();
4023 $modules = array();
4024 foreach ( $raw_modules as $module ) {
4025 $modules[] = Jetpack::get_module_slug( $module );
4026 }
4027
4028 return $modules;
4029 }
4030
4031 /**
4032 * Verify that the API client is allowed to replace user token.
4033 *
4034 * @since 1.29.0
4035 *
4036 * @return bool|WP_Error
4037 */
4038 public static function get_features_permission_check() {
4039 if ( ! Rest_Authentication::is_signed_with_blog_token() ) {
4040 $message = esc_html__(
4041 'You do not have the correct user permissions to perform this action. Please contact your site admin if you think this is a mistake.',
4042 'jetpack'
4043 );
4044 return new WP_Error( 'invalid_permission_fetch_features', $message, array( 'status' => rest_authorization_required_code() ) );
4045 }
4046
4047 return true;
4048 }
4049 } // class end
4050