diff options
Diffstat (limited to 'plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php')
-rw-r--r-- | plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php | 2982 |
1 files changed, 1913 insertions, 1069 deletions
diff --git a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php index 742767b0..342c85b2 100644 --- a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php +++ b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php @@ -2,9 +2,12 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; -use Automattic\Jetpack\JITM; -use Automattic\Jetpack\Tracking; -use Automattic\Jetpack\Status; +use Automattic\Jetpack\Connection\Rest_Authentication; +use Automattic\Jetpack\Connection\REST_Connector; +use Automattic\Jetpack\Jetpack_CRM_Data; +use Automattic\Jetpack\Licensing; +use Automattic\Jetpack\Search\REST_Controller as Search_REST_Controller; +use Automattic\Jetpack\Status\Host; /** * Register WP REST API endpoints for Jetpack. @@ -28,6 +31,9 @@ add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register // Each of these is a class that will register its own routes on 'rest_api_init'. require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php'; +// Load Search endpoints when WP REST API is initialized. +add_action( 'rest_api_init', array( new Search_REST_Controller(), 'register_rest_routes' ) ); + /** * Class Jetpack_Core_Json_Api_Endpoints * @@ -37,6 +43,8 @@ class Jetpack_Core_Json_Api_Endpoints { /** * @var string Generic error message when user is not allowed to perform an action. + * + * @deprecated 8.8.0 Use `REST_Connector::get_user_permissions_error_msg()` instead. */ public static $user_permissions_error_msg; @@ -60,157 +68,168 @@ class Jetpack_Core_Json_Api_Endpoints { require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php'; - self::$user_permissions_error_msg = esc_html__( - 'You do not have the correct user permissions to perform this action. - Please contact your site admin if you think this is a mistake.', - 'jetpack' - ); + self::$user_permissions_error_msg = REST_Connector::get_user_permissions_error_msg(); self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); - $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); - $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); - $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); - $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); + $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); + $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); + $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); + $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); $module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() ); - $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); - $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); - - register_rest_route( 'jetpack/v4', 'plans', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plans', - 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', - ) ); - - register_rest_route( 'jetpack/v4', 'products', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_products', - 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', - ) ); - - register_rest_route( 'jetpack/v4', 'marketing/survey', array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => __CLASS__ . '::submit_survey', - 'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback', - ) ); - - register_rest_route( 'jetpack/v4', '/jitm', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_jitm_message', - ) ); - - register_rest_route( 'jetpack/v4', '/jitm', array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => __CLASS__ . '::delete_jitm_message' - ) ); - - // Authorize a remote user - register_rest_route( 'jetpack/v4', '/remote_authorize', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::remote_authorize', - ) ); - - // Get current connection status of Jetpack - register_rest_route( 'jetpack/v4', '/connection', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jetpack_connection_status', - ) ); + $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); + $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); + + register_rest_route( + 'jetpack/v4', + 'plans', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_plans', + 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', + ) + ); + + register_rest_route( + 'jetpack/v4', + 'products', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_products', + 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', + ) + ); + + register_rest_route( + 'jetpack/v4', + 'marketing/survey', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => __CLASS__ . '::submit_survey', + 'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback', + ) + ); // Test current connection status of Jetpack - register_rest_route( 'jetpack/v4', '/connection/test', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jetpack_connection_test', - 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/connection/test', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::jetpack_connection_test', + 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', + ) + ); // Endpoint specific for privileged servers to request detailed debug information. - register_rest_route( 'jetpack/v4', '/connection/test-wpcom/', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jetpack_connection_test_for_external', - 'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check', - ) ); - - register_rest_route( 'jetpack/v4', '/rewind', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_rewind_data', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); - - // Fetches a fresh connect URL - register_rest_route( 'jetpack/v4', '/connection/url', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::build_connect_url', - 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', - ) ); - - // Get current user connection data - register_rest_route( 'jetpack/v4', '/connection/data', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_user_connection_data', - 'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback', - ) ); - - // Start the connection process by registering the site on WordPress.com servers. - register_rest_route( 'jetpack/v4', '/connection/register', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::register_site', - 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', - 'args' => array( - 'registration_nonce' => array( 'type' => 'string' ), - ), - ) ); - - // Set the connection owner - register_rest_route( 'jetpack/v4', '/connection/owner', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::set_connection_owner', - 'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback', - ) ); + register_rest_route( + 'jetpack/v4', + '/connection/test-wpcom/', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::jetpack_connection_test_for_external', + 'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check', + ) + ); - // Current user: get or set tracking settings. - register_rest_route( 'jetpack/v4', '/tracking/settings', array( + register_rest_route( + 'jetpack/v4', + '/rewind', array( 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_user_tracking_settings', + 'callback' => __CLASS__ . '::get_rewind_data', 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ), + ) + ); + + register_rest_route( + 'jetpack/v4', + '/scan', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_user_tracking_settings', + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_scan_state', 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); + + // Fetches a fresh connect URL + register_rest_route( + 'jetpack/v4', + '/connection/url', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::build_connect_url', + 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', 'args' => array( - 'tracks_opt_out' => array( 'type' => 'boolean' ), + 'from' => array( 'type' => 'string' ), + 'redirect' => array( 'type' => 'string' ), ), - ), - ) ); + ) + ); - // Disconnect site from WordPress.com servers - register_rest_route( 'jetpack/v4', '/connection', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::disconnect_site', - 'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback', - ) ); + // Current user: get or set tracking settings. + register_rest_route( + 'jetpack/v4', + '/tracking/settings', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_user_tracking_settings', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::update_user_tracking_settings', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + 'args' => array( + 'tracks_opt_out' => array( 'type' => 'boolean' ), + ), + ), + ) + ); // Disconnect/unlink user from WordPress.com servers - register_rest_route( 'jetpack/v4', '/connection/user', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::unlink_user', - 'permission_callback' => __CLASS__ . '::unlink_user_permission_callback', - ) ); + register_rest_route( + 'jetpack/v4', + '/connection/user', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::unlink_user', + 'permission_callback' => __CLASS__ . '::unlink_user_permission_callback', + ) + ); // Get current site data - register_rest_route( 'jetpack/v4', '/site', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_site_data', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/site', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_site_data', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); // Get current site data - register_rest_route( 'jetpack/v4', '/site/features', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $site_endpoint, 'get_features' ), - 'permission_callback' => array( $site_endpoint , 'can_request' ), - ) ); + register_rest_route( + 'jetpack/v4', + '/site/features', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $site_endpoint, 'get_features' ), + 'permission_callback' => array( $site_endpoint, 'can_request' ), + ) + ); + + register_rest_route( + 'jetpack/v4', + '/site/products', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $site_endpoint, 'get_products' ), + 'permission_callback' => array( $site_endpoint, 'can_request' ), + ) + ); // Get current site purchases. register_rest_route( @@ -224,276 +243,790 @@ class Jetpack_Core_Json_Api_Endpoints { ); // Get current site benefits - register_rest_route( 'jetpack/v4', '/site/benefits', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $site_endpoint, 'get_benefits' ), - 'permission_callback' => array( $site_endpoint, 'can_request' ), - ) ); + register_rest_route( + 'jetpack/v4', + '/site/benefits', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $site_endpoint, 'get_benefits' ), + 'permission_callback' => array( $site_endpoint, 'can_request' ), + ) + ); // Get Activity Log data for this site. - register_rest_route( 'jetpack/v4', '/site/activity', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_site_activity', - 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', - ) ); - - // Confirm that a site in identity crisis should be in staging mode - register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::confirm_safe_mode', - 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check', - ) ); - - // IDC resolve: create an entirely new shadow site for this URL. - register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::start_fresh_connection', - 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check', - ) ); - - // Handles the request to migrate stats and subscribers during an identity crisis. - register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::migrate_stats_and_subscribers', - 'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/site/activity', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_site_activity', + 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', + ) + ); // Return all modules - register_rest_route( 'jetpack/v4', '/module/all', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $module_list_endpoint, 'process' ), - 'permission_callback' => array( $module_list_endpoint, 'can_request' ), - ) ); + register_rest_route( + 'jetpack/v4', + '/module/all', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $module_list_endpoint, 'process' ), + 'permission_callback' => array( $module_list_endpoint, 'can_request' ), + ) + ); // Activate many modules - register_rest_route( 'jetpack/v4', '/module/all/active', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $module_list_endpoint, 'process' ), - 'permission_callback' => array( $module_list_endpoint, 'can_request' ), - 'args' => array( - 'modules' => array( - 'default' => '', - 'type' => 'array', - 'items' => array( - 'type' => 'string', + register_rest_route( + 'jetpack/v4', + '/module/all/active', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $module_list_endpoint, 'process' ), + 'permission_callback' => array( $module_list_endpoint, 'can_request' ), + 'args' => array( + 'modules' => array( + 'default' => '', + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + 'required' => true, + 'validate_callback' => __CLASS__ . '::validate_module_list', + ), + 'active' => array( + 'default' => true, + 'type' => 'boolean', + 'required' => false, + 'validate_callback' => __CLASS__ . '::validate_boolean', ), - 'required' => true, - 'validate_callback' => __CLASS__ . '::validate_module_list', - ), - 'active' => array( - 'default' => true, - 'type' => 'boolean', - 'required' => false, - 'validate_callback' => __CLASS__ . '::validate_boolean', ), ) - ) ); + ); // Return a single module and update it when needed - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - ) ); + register_rest_route( + 'jetpack/v4', + '/module/(?P<slug>[a-z\-]+)', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $core_api_endpoint, 'process' ), + 'permission_callback' => array( $core_api_endpoint, 'can_request' ), + ) + ); // Activate and deactivate a module - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $module_toggle_endpoint, 'process' ), - 'permission_callback' => array( $module_toggle_endpoint, 'can_request' ), - 'args' => array( - 'active' => array( - 'default' => true, - 'type' => 'boolean', - 'required' => true, - 'validate_callback' => __CLASS__ . '::validate_boolean', + register_rest_route( + 'jetpack/v4', + '/module/(?P<slug>[a-z\-]+)/active', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $module_toggle_endpoint, 'process' ), + 'permission_callback' => array( $module_toggle_endpoint, 'can_request' ), + 'args' => array( + 'active' => array( + 'default' => true, + 'type' => 'boolean', + 'required' => true, + 'validate_callback' => __CLASS__ . '::validate_boolean', + ), ), ) - ) ); + ); // Update a module - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - 'args' => self::get_updateable_parameters( 'any' ) - ) ); + register_rest_route( + 'jetpack/v4', + '/module/(?P<slug>[a-z\-]+)', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $core_api_endpoint, 'process' ), + 'permission_callback' => array( $core_api_endpoint, 'can_request' ), + 'args' => self::get_updateable_parameters( 'any' ), + ) + ); // Get data for a specific module, i.e. Protect block count, WPCOM stats, // Akismet spam count, etc. - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $module_data_endpoint, 'process' ), - 'permission_callback' => array( $module_data_endpoint, 'can_request' ), - 'args' => array( - 'range' => array( - 'default' => 'day', - 'type' => 'string', - 'required' => false, - 'validate_callback' => __CLASS__ . '::validate_string', + register_rest_route( + 'jetpack/v4', + '/module/(?P<slug>[a-z\-]+)/data', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $module_data_endpoint, 'process' ), + 'permission_callback' => array( $module_data_endpoint, 'can_request' ), + 'args' => array( + 'range' => array( + 'default' => 'day', + 'type' => 'string', + 'required' => false, + 'validate_callback' => __CLASS__ . '::validate_string', + ), ), ) - ) ); + ); // Check if the API key for a specific service is valid or not - register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $module_data_endpoint, 'key_check' ), - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'sanitize_callback' => 'sanitize_text_field', - ) ); - - register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $module_data_endpoint, 'key_check' ), - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'sanitize_callback' => 'sanitize_text_field', - 'args' => array( - 'api_key' => array( - 'default' => '', - 'type' => 'string', - 'validate_callback' => __CLASS__ . '::validate_alphanum', + register_rest_route( + 'jetpack/v4', + '/module/(?P<service>[a-z\-]+)/key/check', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $module_data_endpoint, 'key_check' ), + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + 'sanitize_callback' => 'sanitize_text_field', + ) + ); + + register_rest_route( + 'jetpack/v4', + '/module/(?P<service>[a-z\-]+)/key/check', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $module_data_endpoint, 'key_check' ), + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + 'sanitize_callback' => 'sanitize_text_field', + 'args' => array( + 'api_key' => array( + 'default' => '', + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_alphanum', + ), ), ) - ) ); + ); // Update any Jetpack module option or setting - register_rest_route( 'jetpack/v4', '/settings', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - 'args' => self::get_updateable_parameters( 'any' ) - ) ); + register_rest_route( + 'jetpack/v4', + '/settings', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $core_api_endpoint, 'process' ), + 'permission_callback' => array( $core_api_endpoint, 'can_request' ), + 'args' => self::get_updateable_parameters( 'any' ), + ) + ); // Update a module - register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - 'args' => self::get_updateable_parameters() - ) ); + register_rest_route( + 'jetpack/v4', + '/settings/(?P<slug>[a-z\-]+)', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $core_api_endpoint, 'process' ), + 'permission_callback' => array( $core_api_endpoint, 'can_request' ), + 'args' => self::get_updateable_parameters(), + ) + ); // Return all module settings - register_rest_route( 'jetpack/v4', '/settings/', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - ) ); + register_rest_route( + 'jetpack/v4', + '/settings/', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $core_api_endpoint, 'process' ), + 'permission_callback' => array( $core_api_endpoint, 'can_request' ), + ) + ); // Reset all Jetpack options - register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::reset_jetpack_options', - 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/options/(?P<options>[a-z\-]+)', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::reset_jetpack_options', + 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', + ) + ); // Updates: get number of plugin updates available - register_rest_route( 'jetpack/v4', '/updates/plugins', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plugin_update_count', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/updates/plugins', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_plugin_update_count', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); // Dismiss Jetpack Notices - register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::dismiss_notice', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); - - // Plugins: get list of all plugins. - register_rest_route( 'jetpack/v4', '/plugins', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plugins', - 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', - ) ); - - register_rest_route( 'jetpack/v4', '/plugins/akismet/activate', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::activate_akismet', - 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/notice/(?P<notice>[a-z\-_]+)', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::dismiss_notice', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); + + /* + * Plugins: manage plugins on your site. + * + * @since 8.9.0 + * + * @to-do: deprecate and switch to /wp/v2/plugins when WordPress 5.5 is the minimum required version. + * Noting that the `source` parameter is Jetpack-specific (not implemented in Core). + */ + register_rest_route( + 'jetpack/v4', + '/plugins', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_plugins', + 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => __CLASS__ . '::install_plugin', + 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', + 'args' => array( + 'slug' => array( + 'type' => 'string', + 'required' => true, + 'description' => __( 'WordPress.org plugin directory slug.', 'jetpack' ), + 'pattern' => '[\w\-]+', + ), + 'status' => array( + 'description' => __( 'The plugin activation status.', 'jetpack' ), + 'type' => 'string', + 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), + 'default' => 'inactive', + ), + 'source' => array( + 'required' => false, + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_string', + ), + ), + ), + ) + ); + + /* + * Plugins: activate a specific plugin. + * + * @since 8.9.0 + * + * @to-do: deprecate and switch to /wp/v2/plugins when WordPress 5.5 is the minimum required version. + * Noting that the `source` parameter is Jetpack-specific (not implemented in Core). + */ + register_rest_route( + 'jetpack/v4', + '/plugins/(?P<plugin>[^.\/]+(?:\/[^.\/]+)?)', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::activate_plugin', + 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', + 'args' => array( + 'status' => array( + 'required' => true, + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_activate_plugin', + ), + 'source' => array( + 'required' => false, + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_string', + ), + ), + ) + ); // Plugins: check if the plugin is active. - register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plugin', - 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/plugin/(?P<plugin>[a-z\/\.\-_]+)', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_plugin', + 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', + ) + ); // Widgets: get information about a widget that supports it. - register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $widget_endpoint, 'process' ), - 'permission_callback' => array( $widget_endpoint, 'can_request' ), - ) ); + register_rest_route( + 'jetpack/v4', + '/widgets/(?P<id>[0-9a-z\-_]+)', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $widget_endpoint, 'process' ), + 'permission_callback' => array( $widget_endpoint, 'can_request' ), + ) + ); // Site Verify: check if the site is verified, and a get verification token if not - register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::is_site_verified_and_token', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - ) ); - - register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::is_site_verified_and_token', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - ) ); + register_rest_route( + 'jetpack/v4', + '/verify-site/(?P<service>[a-z\-_]+)', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::is_site_verified_and_token', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + ) + ); + + register_rest_route( + 'jetpack/v4', + '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::is_site_verified_and_token', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + ) + ); // Site Verify: tell a service to verify the site - register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::verify_site', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'args' => array( - 'keyring_id' => array( - 'required' => true, - 'type' => 'integer', - 'validate_callback' => __CLASS__ . '::validate_posint', + register_rest_route( + 'jetpack/v4', + '/verify-site/(?P<service>[a-z\-_]+)', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::verify_site', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + 'args' => array( + 'keyring_id' => array( + 'required' => true, + 'type' => 'integer', + 'validate_callback' => __CLASS__ . '::validate_posint', + ), + ), + ) + ); + + register_rest_route( + 'jetpack/v4', + '/mobile/send-login-email', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::send_mobile_magic_link', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); + + register_rest_route( + 'jetpack/v4', + '/recommendations/data', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_recommendations_data', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::update_recommendations_data', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + 'args' => array( + 'data' => array( + 'required' => true, + 'type' => 'object', + 'validate_callback' => __CLASS__ . '::validate_recommendations_data', + ), + ), + ), + ) + ); + + register_rest_route( + 'jetpack/v4', + '/recommendations/step', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_recommendations_step', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::update_recommendations_step', + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', + 'args' => array( + 'step' => array( + 'required' => true, + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_string', + ), + ), ), ) - ) ); + ); - // Get and set API keys. - // Note: permission_callback intentionally omitted from the GET method. - // Map block requires open access to API keys on the front end. register_rest_route( 'jetpack/v4', - '/service-api-keys/(?P<service>[a-z\-_]+)', + '/recommendations/product-suggestions', array( array( 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_service_api_key', + 'callback' => __CLASS__ . '::get_recommendations_product_suggestions', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ), + ) + ); + + register_rest_route( + 'jetpack/v4', + '/recommendations/upsell', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_recommendations_upsell', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ), + ) + ); + + /* + * Get and update the last licensing error message. + */ + register_rest_route( + 'jetpack/v4', + '/licensing/error', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_licensing_error', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', ), array( 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_service_api_key', - 'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ), + 'callback' => __CLASS__ . '::update_licensing_error', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', 'args' => array( - 'service_api_key' => array( + 'error' => array( + 'required' => true, + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_string', + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + ), + ) + ); + + // Return all module settings. + register_rest_route( + 'jetpack/v4', + '/licensing/set-license', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::set_jetpack_license', + 'permission_callback' => __CLASS__ . '::set_jetpack_license_key_permission_check', + 'args' => array( + 'license' => array( + 'required' => true, + 'type' => 'string', + 'validate_callback' => __CLASS__ . '::validate_string', + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + ) + ); + + /** + * Get Jetpack user license counts. + */ + register_rest_route( + 'jetpack/v4', + 'licensing/user/counts', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_user_license_counts', + 'permission_callback' => __CLASS__ . '::user_licensing_permission_check', + ) + ); + + /** + * Update user-licensing activation notice dismiss info. + */ + register_rest_route( + 'jetpack/v4', + 'licensing/user/activation-notice-dismiss', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::update_licensing_activation_notice_dismiss', + 'permission_callback' => __CLASS__ . '::user_licensing_permission_check', + 'args' => array( + 'last_detached_count' => array( + 'required' => true, + 'type' => 'integer', + 'validate_callback' => __CLASS__ . '::validate_non_neg_int', + ), + ), + ) + ); + + /** + * Attach licenses to user account + */ + register_rest_route( + 'jetpack/v4', + '/licensing/attach-licenses', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::attach_jetpack_licenses', + 'permission_callback' => __CLASS__ . '::user_licensing_permission_check', + 'args' => array( + 'licenses' => array( + 'required' => true, + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + ) + ); + + /* + * Manage the Jetpack CRM plugin's integration with Jetpack contact forms. + */ + register_rest_route( + 'jetpack/v4', + 'jetpack_crm', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_jetpack_crm_data', + 'permission_callback' => __CLASS__ . '::jetpack_crm_data_permission_check', + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::activate_crm_jetpack_forms_extension', + 'permission_callback' => __CLASS__ . '::activate_crm_extensions_permission_check', + 'args' => array( + 'extension' => array( 'required' => true, 'type' => 'text', ), ), ), + ) + ); + + register_rest_route( + 'jetpack/v4', + 'purchase-token', + array( array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => __CLASS__ . '::delete_service_api_key', - 'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ), + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_purchase_token', + 'permission_callback' => __CLASS__ . '::purchase_token_permission_check', + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => __CLASS__ . '::delete_purchase_token', + 'permission_callback' => __CLASS__ . '::purchase_token_permission_check', ), ) ); + /* + * Set the Jetpack Option `has_see_wc_connection_modal` to true + */ register_rest_route( 'jetpack/v4', - '/mobile/send-login-email', + 'seen-wc-connection-modal', array( 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::send_mobile_magic_link', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + 'callback' => __CLASS__ . '::set_has_seen_wc_connection_modal', + 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', + ) + ); + } + + /** + * Get the data for the recommendations + * + * @return array Recommendations data + */ + public static function get_recommendations_data() { + return Jetpack_Recommendations::get_recommendations_data(); + } + + /** + * Update the data for the recommendations + * + * @param WP_REST_Request $request The request. + * + * @return bool true + */ + public static function update_recommendations_data( $request ) { + $data = $request['data']; + Jetpack_Recommendations::update_recommendations_data( $data ); + + return true; + } + + /** + * Get the data for the recommendations + * + * @return array Recommendations data + */ + public static function get_recommendations_step() { + return Jetpack_Recommendations::get_recommendations_step(); + } + + /** + * Update the step for the recommendations + * + * @param WP_REST_Request $request The request. + * + * @return bool true + */ + public static function update_recommendations_step( $request ) { + $step = $request['step']; + Jetpack_Recommendations::update_recommendations_step( $step ); + + return true; + } + + /** + * Get product suggestions for the recommendations + * + * @return string|WP_Error The response from the wpcom product suggestions endpoint as a JSON object. + */ + public static function get_recommendations_product_suggestions() { + $blog_id = Jetpack_Options::get_option( 'id' ); + if ( ! $blog_id ) { + return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); + } + + $user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected( get_current_user_id() ); + if ( ! $user_connected ) { + return wp_json_encode( array() ); + } + + $request_path = sprintf( '/sites/%s/jetpack-recommendations/product-suggestions?locale=' . get_user_locale(), $blog_id ); + $wpcom_request = Client::wpcom_json_api_request_as_user( + $request_path, + '2', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), + ) + ); + + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); + if ( 200 === $response_code ) { + return json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + } else { + return new WP_Error( + 'failed_to_fetch_data', + esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), + array( 'status' => $response_code ) + ); + } + } + + /** + * Get the upsell for the recommendations + * + * @return string The response from the wpcom upsell endpoint as a JSON object + */ + public static function get_recommendations_upsell() { + $blog_id = Jetpack_Options::get_option( 'id' ); + if ( ! $blog_id ) { + return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); + } + + $user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected( get_current_user_id() ); + if ( ! $user_connected ) { + $response = array( + 'hide_upsell' => true, + ); + + return $response; + } + + $request_path = sprintf( '/sites/%s/jetpack-recommendations/upsell?locale=' . get_user_locale(), $blog_id ); + $wpcom_request = Client::wpcom_json_api_request_as_user( + $request_path, + '2', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), ) ); + + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); + if ( 200 === $response_code ) { + return json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + } else { + return new WP_Error( + 'failed_to_fetch_data', + esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), + array( 'status' => $response_code ) + ); + } + } + + /** + * Validate the recommendations data + * + * @param array $value Value to check received by request. + * @param WP_REST_Request $request The request sent to the WP REST API. + * @param string $param Name of the parameter passed to endpoint holding $value. + * + * @return bool|WP_Error + */ + public static function validate_recommendations_data( $value, $request, $param ) { + if ( ! is_array( $value ) ) { + /* translators: Name of a parameter that must be an object */ + return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an object.', 'jetpack' ), $param ) ); + } + + foreach ( $value as $answer ) { + if ( is_array( $answer ) ) { + $validate = self::validate_array_of_strings( $answer, $request, $param ); + } elseif ( is_string( $answer ) ) { + $validate = self::validate_string( $answer, $request, $param ); + } else { + $validate = self::validate_boolean( $answer, $request, $param ); + } + + if ( is_wp_error( $validate ) ) { + return $validate; + } + } + + return true; + } + + /** + * Return a purchase token used for site-connected (non user-authenticated) checkout. + * + * @return string|WP_Error The current purchase token or WP_Error with error details. + */ + public static function get_purchase_token() { + $blog_id = Jetpack_Options::get_option( 'id' ); + if ( ! $blog_id ) { + return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); + } + + return Jetpack_Options::get_option( 'purchase_token', '' ); + } + + /** + * Delete the current purchase token. + * + * @return boolean|WP_Error Whether the token was deleted or WP_Error with error details. + */ + public static function delete_purchase_token() { + $blog_id = Jetpack_Options::get_option( 'id' ); + if ( ! $blog_id ) { + return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); + } + + return Jetpack_Options::delete_option( 'purchase_token' ); } public static function get_plans( $request ) { @@ -552,63 +1085,99 @@ class Jetpack_Core_Json_Api_Endpoints { } } - public static function submit_survey( $request ) { - + /** + * Gets the users licenses counts. + * + * @since 10.4.0 + * + * @return string|WP_Error A JSON object of user license counts if the request was successful, or a WP_Error otherwise. + */ + public static function get_user_license_counts() { $wpcom_request = Client::wpcom_json_api_request_as_user( - '/marketing/survey', - 'v2', + '/jetpack-licensing/user/licenses/counts', + '2', array( - 'method' => 'POST', + 'method' => 'GET', 'headers' => array( 'Content-Type' => 'application/json', 'X-Forwarded-For' => Jetpack::current_user_ip( true ), ), - ), - $request->get_json_params() + ) ); - $wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) ); - if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) { - $data = $wpcom_request_body; + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); + if ( 200 === $response_code ) { + $license_counts = json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + return $license_counts; } else { - // something went wrong so we'll just return the response without caching - return $wpcom_request_body; + return new WP_Error( + 'failed_to_fetch_data', + esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), + array( 'status' => $response_code ) + ); } - - return $data; } /** - * Asks for a jitm, unless they've been disabled, in which case it returns an empty array + * Update the user-licenses activation notice dismissal data. + * + * @since 10.4.0 * - * @param $request WP_REST_Request + * @param WP_REST_Request $request The request sent to the WP REST API. * - * @return array An array of jitms + * @return array|WP_Error */ - public static function get_jitm_message( $request ) { - $jitm = new JITM(); + public static function update_licensing_activation_notice_dismiss( $request ) { - if ( ! $jitm->register() ) { - return array(); + if ( ! isset( $request['last_detached_count'] ) ) { + return new WP_Error( 'invalid_param', esc_html__( 'Missing parameter "last_detached_count".', 'jetpack' ), array( 'status' => 404 ) ); } - return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) ); + $default = array( + 'last_detached_count' => null, + 'last_dismissed_time' => null, + ); + $last_detached_count = ( '' === $request['last_detached_count'] ) + ? $default['last_detached_count'] + : $request['last_detached_count']; + $last_dismissed_time = ( '' === $request['last_detached_count'] ) + ? $default['last_dismissed_time'] + // Use UTC timezone and convert to ISO8601 format(DateTime::W3C) for best compatibility with JavaScript Date in all browsers. + : ( new DateTime( 'NOW', new DateTimeZone( 'UTC' ) ) )->format( DateTime::W3C ); + + $notice_data = array( + 'last_detached_count' => $last_detached_count, + 'last_dismissed_time' => $last_dismissed_time, + ); + + Jetpack_Options::update_option( 'licensing_activation_notice_dismiss', $notice_data, true ); + return rest_ensure_response( $notice_data ); } - /** - * Dismisses a jitm - * @param $request WP_REST_Request The request - * - * @return bool Always True - */ - public static function delete_jitm_message( $request ) { - $jitm = new JITM(); + public static function submit_survey( $request ) { - if ( ! $jitm->register() ) { - return true; + $wpcom_request = Client::wpcom_json_api_request_as_user( + '/marketing/survey', + 'v2', + array( + 'method' => 'POST', + 'headers' => array( + 'Content-Type' => 'application/json', + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), + ), + $request->get_json_params() + ); + + $wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) { + $data = $wpcom_request_body; + } else { + // something went wrong so we'll just return the response without caching + return $wpcom_request_body; } - return $jitm->dismiss( $request['id'], $request['feature_class'] ); + return $data; } /** @@ -661,17 +1230,19 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) ); } - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); + $xml = new Jetpack_IXR_Client( + array( + 'user_id' => get_current_user_id(), + ) + ); $args = array( 'user_id' => get_current_user_id(), - 'service' => $request[ 'service' ], + 'service' => $request['service'], ); - if ( isset( $request[ 'keyring_id' ] ) ) { - $args[ 'keyring_id' ] = $request[ 'keyring_id' ]; + if ( isset( $request['keyring_id'] ) ) { + $args['keyring_id'] = $request['keyring_id']; } $xml->query( 'jetpack.isSiteVerified', $args ); @@ -683,19 +1254,21 @@ class Jetpack_Core_Json_Api_Endpoints { } } - - public static function verify_site( $request ) { - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); + $xml = new Jetpack_IXR_Client( + array( + 'user_id' => get_current_user_id(), + ) + ); $params = $request->get_json_params(); - $xml->query( 'jetpack.verifySite', array( - 'user_id' => get_current_user_id(), - 'service' => $request[ 'service' ], - 'keyring_id' => $params[ 'keyring_id' ], + $xml->query( + 'jetpack.verifySite', + array( + 'user_id' => get_current_user_id(), + 'service' => $request['service'], + 'keyring_id' => $params['keyring_id'], ) ); @@ -705,7 +1278,7 @@ class Jetpack_Core_Json_Api_Endpoints { $response = $xml->getResponse(); if ( ! empty( $response['errors'] ) ) { - $error = new WP_Error; + $error = new WP_Error(); $error->errors = $response['errors']; return $error; } @@ -715,26 +1288,6 @@ class Jetpack_Core_Json_Api_Endpoints { } /** - * Handles verification that a site is registered - * - * @since 5.4.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return array|wp-error - */ - public static function remote_authorize( $request ) { - $xmlrpc_server = new Jetpack_XMLRPC_Server(); - $result = $xmlrpc_server->remote_authorize( $request ); - - if ( is_a( $result, 'IXR_Error' ) ) { - $result = new WP_Error( $result->code, $result->message ); - } - - return $result; - } - - /** * Handles dismissing of Jetpack Notices * * @since 4.3.0 @@ -751,10 +1304,10 @@ class Jetpack_Core_Json_Api_Endpoints { } if ( isset( $notice ) && ! empty( $notice ) ) { - switch( $notice ) { + switch ( $notice ) { case 'feedback_dash_request': case 'welcome': - $notices = get_option( 'jetpack_dismissed_notices', array() ); + $notices = get_option( 'jetpack_dismissed_notices', array() ); $notices[ $notice ] = true; update_option( 'jetpack_dismissed_notices', $notices ); return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) ); @@ -779,7 +1332,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } @@ -795,42 +1348,8 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_jetpack_connect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - - } - - /** - * Verify that a user can get the data about the current user. - * Only those who can connect. - * - * @since 4.3.0 - * - * @uses Jetpack::is_user_connected(); - * - * @return bool|WP_Error True if user is able to unlink. - */ - public static function get_user_connection_data_permission_callback() { - if ( current_user_can( 'jetpack_connect_user' ) ) { - return true; - } + return new WP_Error( 'invalid_user_permission_jetpack_connect', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); - return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Check that user has permission to change the master user. - * - * @since 6.2.0 - * @since 7.7.0 Update so that any user with jetpack_disconnect privs can set owner. - * - * @return bool|WP_Error True if user is able to change master user. - */ - public static function set_connection_owner_permission_callback() { - if ( current_user_can( 'jetpack_disconnect' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); } /** @@ -838,16 +1357,16 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @uses Jetpack::is_user_connected(); + * @uses Automattic\Jetpack\Connection\Manager::is_user_connected();) * * @return bool|WP_Error True if user is able to unlink. */ public static function unlink_user_permission_callback() { - if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) { + if ( current_user_can( 'jetpack_connect_user' ) && ( new Connection_Manager( 'jetpack' ) )->is_user_connected( get_current_user_id() ) ) { return true; } - return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** @@ -862,7 +1381,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** @@ -877,7 +1396,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** @@ -892,22 +1411,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can mitigate an identity crisis. - * - * @since 4.4.0 - * - * @return bool Whether user has capability 'jetpack_disconnect'. - */ - public static function identity_crisis_mitigation_permission_check() { - if ( current_user_can( 'jetpack_disconnect' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** @@ -922,7 +1426,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** @@ -937,7 +1441,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_activate_plugins', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** @@ -950,44 +1454,39 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** - * Contextual HTTP error code for authorization failure. - * - * Taken from rest_authorization_required_code() in WP-API plugin until is added to core. - * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6 + * Verify that site can view and delete the site's purchase token. * - * @since 4.3.0 - * - * @return int + * @return bool Whether site has level-site auth or user has the capability 'manage_options'. */ - public static function rest_authorization_required_code() { - return is_user_logged_in() ? 403 : 401; + public static function purchase_token_permission_check() { + if ( Rest_Authentication::is_signed_with_blog_token() ) { + return true; + } + + if ( current_user_can( 'manage_options' ) ) { + return true; + } + + return new WP_Error( 'invalid_permission_manage_purchase_token', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** - * Get connection status for this Jetpack site. - * - * @since 4.3.0 + * Verify that user can view and update user-licensing data. * - * @return bool True if site is connected + * @return bool Whether the user is currently connected and they are the connection owner. */ - public static function jetpack_connection_status() { - $status = new Status(); - return rest_ensure_response( array( - 'isActive' => Jetpack::is_active(), - 'isStaging' => $status->is_staging_site(), - 'isRegistered' => Jetpack::connection()->is_registered(), - 'devMode' => array( - 'isActive' => $status->is_development_mode(), - 'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG, - 'url' => site_url() && false === strpos( site_url(), '.' ), - 'filter' => apply_filters( 'jetpack_development_mode', false ), - ), - ) - ); + public static function user_licensing_permission_check() { + $connection_manager = new Connection_Manager( 'jetpack' ); + + if ( $connection_manager->is_user_connected() && $connection_manager->is_connection_owner() ) { + return true; + } + + return new WP_Error( 'invalid_permission_manage_user_licenses', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** @@ -1029,14 +1528,14 @@ class Jetpack_Core_Json_Api_Endpoints { $signature_data = wp_json_encode( array( 'rest_route' => $_GET['rest_route'], - 'timestamp' => intval( $_GET['timestamp'] ), + 'timestamp' => (int) $_GET['timestamp'], 'url' => wp_unslash( $_GET['url'] ), ) ); if ( ! function_exists( 'openssl_verify' ) - || ! openssl_verify( + || 1 !== openssl_verify( $signature_data, $signature, JETPACK__DEBUGGER_PUBLIC_KEY @@ -1046,7 +1545,7 @@ class Jetpack_Core_Json_Api_Endpoints { } // signature timestamp must be within 5min of current time - if ( abs( time() - intval( $_GET['timestamp'] ) ) > 300 ) { + if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) { return false; } @@ -1086,7 +1585,7 @@ class Jetpack_Core_Json_Api_Endpoints { } } - $result = $errors[0]; + $result = ( ! empty( $errors ) ) ? $errors[0] : null; if ( count( $errors ) > 1 ) { // Remove the primary error. array_shift( $errors ); @@ -1125,15 +1624,24 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'site_id_missing' ); } - $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' ); + if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $rewind_state = get_transient( 'jetpack_rewind_state' ); + if ( $rewind_state ) { + return $rewind_state; + } + } + + $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' ); if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { return new WP_Error( 'rewind_data_fetch_failed' ); } - $body = wp_remote_retrieve_body( $response ); + $body = wp_remote_retrieve_body( $response ); + $result = json_decode( $body ); + set_transient( 'jetpack_rewind_state', $result, 30 * MINUTE_IN_SECONDS ); - return json_decode( $body ); + return $result; } /** @@ -1147,10 +1655,11 @@ class Jetpack_Core_Json_Api_Endpoints { $rewind_data = self::rewind_data(); if ( ! is_wp_error( $rewind_data ) ) { - return rest_ensure_response( array( - 'code' => 'success', + return rest_ensure_response( + array( + 'code' => 'success', 'message' => esc_html__( 'Backup & Scan data correctly received.', 'jetpack' ), - 'data' => wp_json_encode( $rewind_data ), + 'data' => wp_json_encode( $rewind_data ), ) ); } @@ -1171,8 +1680,94 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Gets Scan state data. + * + * @since 8.5.0 + * + * @return array|WP_Error Result from WPCOM API or error. + */ + public static function scan_state() { + + if ( ! isset( $_GET['_cacheBuster'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $scan_state = get_transient( 'jetpack_scan_state' ); + if ( ! empty( $scan_state ) ) { + return $scan_state; + } + } + $site_id = Jetpack_Options::get_option( 'id' ); + + if ( ! $site_id ) { + return new WP_Error( 'site_id_missing' ); + } + // The default timeout was too short in come cases. + add_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 ); + $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' ); + remove_filter( 'http_request_timeout', array( __CLASS__, 'increase_timeout_30' ), PHP_INT_MAX - 1 ); + + if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { + return new WP_Error( 'scan_state_fetch_failed' ); + } + + $body = wp_remote_retrieve_body( $response ); + $result = json_decode( $body ); + set_transient( 'jetpack_scan_state', $result, 30 * MINUTE_IN_SECONDS ); + + return $result; + } + + /** + * Increases the request timeout value to 30 seconds. + * + * @return int Always returns 30. + */ + public static function increase_timeout_30() { + return 30; // 30 Seconds + } + + /** + * Get Scan state for API. + * + * @since 8.5.0 + * + * @return WP_REST_Response|WP_Error REST response or error state. + */ + public static function get_scan_state() { + $scan_state = self::scan_state(); + + if ( ! is_wp_error( $scan_state ) ) { + if ( ( new Host() )->is_woa_site() && ! empty( $scan_state->threats ) ) { + $scan_state->threats = array(); + } + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'Scan state correctly received.', 'jetpack' ), + 'data' => wp_json_encode( $scan_state ), + ) + ); + } + + if ( $scan_state->get_error_code() === 'scan_state_fetch_failed' ) { + return new WP_Error( 'scan_state_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) ); + } + + if ( $scan_state->get_error_code() === 'site_id_missing' ) { + return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) ); + } + + return new WP_Error( + 'error_get_rewind_data', + esc_html__( 'Could not retrieve Scan state.', 'jetpack' ), + array( 'status' => 500 ) + ); + } + + /** * Disconnects Jetpack from the WordPress.com Servers * + * @deprecated since Jetpack 10.0.0 + * @see Automattic\Jetpack\Connection\REST_Connector::disconnect_site() + * * @uses Jetpack::disconnect(); * @since 4.3.0 * @@ -1181,35 +1776,41 @@ class Jetpack_Core_Json_Api_Endpoints { * @return bool|WP_Error True if Jetpack successfully disconnected. */ public static function disconnect_site( $request ) { + _deprecated_function( __METHOD__, 'jetpack-10.0.0', '\Automattic\Jetpack\Connection\REST_Connector::disconnect_site' ); if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } - if ( Jetpack::is_active() ) { + if ( Jetpack::is_connection_ready() ) { Jetpack::disconnect(); return rest_ensure_response( array( 'code' => 'success' ) ); } - return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site. Please try again.', 'jetpack' ), array( 'status' => 400 ) ); + return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site. Please try again.', 'jetpack' ), array( 'status' => 400 ) ); } /** * Registers the Jetpack site * - * @uses Jetpack::try_registration(); - * @since 7.7.0 + * @deprecated since Jetpack 9.7.0 + * @see Automattic\Jetpack\Connection\REST_Connector::connection_register() * * @param WP_REST_Request $request The request sent to the WP REST API. * * @return bool|WP_Error True if Jetpack successfully registered */ public static function register_site( $request ) { + _deprecated_function( __METHOD__, 'jetpack-9.7.0', '\Automattic\Jetpack\Connection\REST_Connector::connection_register' ); + if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) { return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack' ), array( 'status' => 403 ) ); } - $response = Jetpack::try_registration(); + if ( isset( $request['from'] ) ) { + Jetpack::connection()->add_register_request_param( 'from', (string) $request['from'] ); + } + $response = Jetpack::connection()->try_registration(); if ( is_wp_error( $response ) ) { return $response; @@ -1217,8 +1818,9 @@ class Jetpack_Core_Json_Api_Endpoints { return rest_ensure_response( array( - 'authorizeUrl' => Jetpack::build_authorize_url( false, true ) - ) ); + 'authorizeUrl' => Jetpack::build_authorize_url( false, true ), + ) + ); } /** @@ -1231,13 +1833,16 @@ class Jetpack_Core_Json_Api_Endpoints { * * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise. */ - public static function build_connect_url() { - $url = Jetpack::init()->build_connect_url( true, false, false ); + public static function build_connect_url( $request = array() ) { + $from = isset( $request['from'] ) ? $request['from'] : false; + $redirect = isset( $request['redirect'] ) ? $request['redirect'] : false; + + $url = Jetpack::init()->build_connect_url( true, $redirect, $from ); if ( $url ) { return rest_ensure_response( $url ); } - 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 ) ); + 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 ) ); } /** @@ -1245,102 +1850,33 @@ class Jetpack_Core_Json_Api_Endpoints { * Information about the master/primary user. * Information about the current user. * - * @since 4.3.0 + * @deprecated since Jetpack 10.0.0 + * @see Automattic\Jetpack\Connection\REST_Connector::get_user_connection_data() * - * @param WP_REST_Request $request The request sent to the WP REST API. + * @since 4.3.0 * * @return object */ public static function get_user_connection_data() { - require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' ); - - $response = array( -// 'othersLinked' => Jetpack::get_other_linked_admins(), - 'currentUser' => jetpack_current_user_data(), - ); - return rest_ensure_response( $response ); - } - - /** - * Change the master user. - * - * @since 6.2.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if owner successfully changed. - */ - public static function set_connection_owner( $request ) { - if ( ! isset( $request['owner'] ) ) { - return new WP_Error( - 'invalid_param', - esc_html__( 'Invalid Parameter', 'jetpack' ), - array( 'status' => 400 ) - ); - } + _deprecated_function( __METHOD__, 'jetpack-10.0.0', '\Automattic\Jetpack\Connection\REST_Connector::get_user_connection_data' ); - $new_owner_id = $request['owner']; - if ( ! user_can( $new_owner_id, 'administrator' ) ) { - return new WP_Error( - 'new_owner_not_admin', - esc_html__( 'New owner is not admin', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - if ( $new_owner_id === get_current_user_id() ) { - return new WP_Error( - 'new_owner_is_current_user', - esc_html__( 'New owner is same as current user', 'jetpack' ), - array( 'status' => 400 ) - ); - } + require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php'; - if ( ! Jetpack::is_user_connected( $new_owner_id ) ) { - return new WP_Error( - 'new_owner_not_connected', - esc_html__( 'New owner is not connected', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - // Update the master user in Jetpack - $updated = Jetpack_Options::update_option( 'master_user', $new_owner_id ); - - // Notify WPCOM about the master user change - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); - $xml->query( 'jetpack.switchBlogOwner', array( - 'new_blog_owner' => $new_owner_id, - ) ); - - if ( $updated && ! $xml->isError() ) { - - // Track it - if ( class_exists( 'Automattic\Jetpack\Tracking' ) ) { - $tracking = new Tracking(); - $tracking->record_user_event( 'set_connection_owner_success' ); - } + $connection_owner = ( new Connection_Manager() )->get_connection_owner(); + $owner_display_name = false === $connection_owner ? null : $connection_owner->data->display_name; - return rest_ensure_response( - array( - 'code' => 'success', - ) - ); - } - return new WP_Error( - 'error_setting_new_owner', - esc_html__( 'Could not confirm new owner.', 'jetpack' ), - array( 'status' => 500 ) + $response = array( + 'currentUser' => jetpack_current_user_data(), + 'connectionOwner' => $owner_display_name, ); + return rest_ensure_response( $response ); } /** * Unlinks current user from the WordPress.com Servers. * * @since 4.3.0 - * @uses Automattic\Jetpack\Connection\Manager::disconnect_user + * @uses Automattic\Jetpack\Connection\Manager->disconnect_user * * @param WP_REST_Request $request The request sent to the WP REST API. * @@ -1352,15 +1888,15 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } - if ( Connection_Manager::disconnect_user() ) { + if ( ( new Connection_Manager( 'jetpack' ) )->disconnect_user() ) { return rest_ensure_response( array( - 'code' => 'success' + 'code' => 'success', ) ); } - return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user. Please try again.', 'jetpack' ), array( 'status' => 400 ) ); + return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user. Please try again.', 'jetpack' ), array( 'status' => 400 ) ); } /** @@ -1373,7 +1909,7 @@ class Jetpack_Core_Json_Api_Endpoints { * @return WP_REST_Response|WP_Error Response, else error. */ public static function get_user_tracking_settings( $request ) { - if ( ! Jetpack::is_user_connected() ) { + if ( ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) { $response = array( 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. ); @@ -1406,7 +1942,7 @@ class Jetpack_Core_Json_Api_Endpoints { * @return WP_REST_Response|WP_Error Response, else error. */ public static function update_user_tracking_settings( $request ) { - if ( ! Jetpack::is_user_connected() ) { + if ( ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) { $response = array( 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. ); @@ -1432,17 +1968,17 @@ class Jetpack_Core_Json_Api_Endpoints { } /** - * Fetch site data from .com including the site's current plan. + * Fetch site data from .com including the site's current plan and the site's products. * * @since 5.5.0 * - * @return array Array of site properties. + * @return stdClass|WP_Error */ public static function site_data() { $site_id = Jetpack_Options::get_option( 'id' ); if ( ! $site_id ) { - new WP_Error( 'site_id_missing' ); + return new WP_Error( 'site_id_missing', '', array( 'api_error_code' => __( 'site_id_missing', 'jetpack' ) ) ); } $args = array( 'headers' => array() ); @@ -1453,43 +1989,72 @@ class Jetpack_Core_Json_Api_Endpoints { $args['headers']['Cookie'] = "store_sandbox=$secret;"; } - $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1', $args ); + $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) . '?force=wpcom', '1.1', $args ); + $body = wp_remote_retrieve_body( $response ); + $data = $body ? json_decode( $body ) : null; if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { - return new WP_Error( 'site_data_fetch_failed' ); + $error_info = array( + 'api_error_code' => null, + 'api_http_code' => wp_remote_retrieve_response_code( $response ), + ); + + if ( is_wp_error( $response ) ) { + $error_info['api_error_code'] = $response->get_error_code() ? wp_strip_all_tags( $response->get_error_code() ) : null; + } elseif ( $data && ! empty( $data->error ) ) { + $error_info['api_error_code'] = $data->error; + } + + return new WP_Error( 'site_data_fetch_failed', '', $error_info ); } Jetpack_Plan::update_from_sites_response( $response ); - $body = wp_remote_retrieve_body( $response ); - - return json_decode( $body ); + return $data; } /** * Get site data, including for example, the site's current plan. * + * @return WP_Error|WP_HTTP_Response|WP_REST_Response * @since 4.3.0 - * - * @return array Array of site properties. */ public static function get_site_data() { $site_data = self::site_data(); if ( ! is_wp_error( $site_data ) ) { - return rest_ensure_response( array( - 'code' => 'success', + /** + * Fires when the site data was successfully returned from the /sites/%d wpcom endpoint. + * + * @since 8.7.0 + */ + do_action( 'jetpack_get_site_data_success' ); + return rest_ensure_response( + array( + 'code' => 'success', 'message' => esc_html__( 'Site data correctly received.', 'jetpack' ), 'data' => json_encode( $site_data ), ) ); } - if ( $site_data->get_error_code() === 'site_data_fetch_failed' ) { - return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) ); - } - if ( $site_data->get_error_code() === 'site_id_missing' ) { - return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) ); + $error_data = $site_data->get_error_data(); + + if ( empty( $error_data['api_error_code'] ) ) { + $error_message = esc_html__( 'Failed fetching site data from WordPress.com. If the problem persists, try reconnecting Jetpack.', 'jetpack' ); + } else { + /* translators: %s is an error code (e.g. `token_mismatch`) */ + $error_message = sprintf( esc_html__( 'Failed fetching site data from WordPress.com (%s). If the problem persists, try reconnecting Jetpack.', 'jetpack' ), $error_data['api_error_code'] ); } + + return new WP_Error( + $site_data->get_error_code(), + $error_message, + array( + 'status' => 400, + 'api_error_code' => empty( $error_data['api_error_code'] ) ? null : $error_data['api_error_code'], + 'api_http_code' => empty( $error_data['api_http_code'] ) ? null : $error_data['api_http_code'], + ) + ); } /** @@ -1510,12 +2075,18 @@ class Jetpack_Core_Json_Api_Endpoints { ); } - $response = Client::wpcom_json_api_request_as_user( "/sites/$site_id/activity", '2', array( - 'method' => 'GET', - 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + $response = Client::wpcom_json_api_request_as_user( + "/sites/$site_id/activity", + '2', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), ), - ), null, 'wpcom' ); + null, + 'wpcom' + ); $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $response_code ) { @@ -1536,7 +2107,8 @@ class Jetpack_Core_Json_Api_Endpoints { ); } - return rest_ensure_response( array( + return rest_ensure_response( + array( 'code' => 'success', 'data' => $data->current->orderedItems, ) @@ -1544,75 +2116,6 @@ class Jetpack_Core_Json_Api_Endpoints { } /** - * Handles identity crisis mitigation, confirming safe mode for this site. - * - * @since 4.4.0 - * - * @return bool | WP_Error True if option is properly set. - */ - public static function confirm_safe_mode() { - $updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true ); - if ( $updated ) { - return rest_ensure_response( - array( - 'code' => 'success' - ) - ); - } - return new WP_Error( - 'error_setting_jetpack_safe_mode', - esc_html__( 'Could not confirm safe mode.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - /** - * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url. - * - * @since 4.4.0 - * - * @return bool | WP_Error True if option is properly set. - */ - public static function migrate_stats_and_subscribers() { - if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) { - return new WP_Error( - 'error_deleting_sync_error_idc', - esc_html__( 'Could not delete sync error option.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) { - return rest_ensure_response( - array( - 'code' => 'success' - ) - ); - } - return new WP_Error( - 'error_setting_jetpack_migrate', - esc_html__( 'Could not confirm migration.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - /** - * This IDC resolution will disconnect the site and re-connect to a completely new - * and separate shadow site than the original. - * - * It will first will disconnect the site without phoning home as to not disturb the production site. - * It then builds a fresh connection URL and sends it back along with the response. - * - * @since 4.4.0 - * @return bool|WP_Error - */ - public static function start_fresh_connection() { - // First clear the options / disconnect. - Jetpack::disconnect(); - return self::build_connect_url(); - } - - /** * Reset Jetpack options * * @since 4.3.0 @@ -1634,8 +2137,8 @@ class Jetpack_Core_Json_Api_Endpoints { if ( isset( $request['options'] ) ) { $data = $request['options']; - switch( $data ) { - case ( 'options' ) : + switch ( $data ) { + case ( 'options' ): $options_to_reset = Jetpack::get_jetpack_options_for_reset(); // Reset the Jetpack options @@ -1651,19 +2154,23 @@ class Jetpack_Core_Json_Api_Endpoints { $default_modules = Jetpack::get_default_modules(); Jetpack::update_active_modules( $default_modules ); - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ), - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'Jetpack options reset.', 'jetpack' ), + ) + ); break; case 'modules': $default_modules = Jetpack::get_default_modules(); Jetpack::update_active_modules( $default_modules ); - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Modules reset to default.', 'jetpack' ), - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'Modules reset to default.', 'jetpack' ), + ) + ); break; default: @@ -1686,7 +2193,7 @@ class Jetpack_Core_Json_Api_Endpoints { */ public static function get_updateable_parameters( $selector = '' ) { $parameters = array( - 'context' => array( + 'context' => array( 'default' => 'edit', ), ); @@ -1712,7 +2219,7 @@ class Jetpack_Core_Json_Api_Endpoints { $options = array( // Carousel - 'carousel_background_color' => array( + 'carousel_background_color' => array( 'description' => esc_html__( 'Color scheme.', 'jetpack' ), 'type' => 'string', 'default' => 'black', @@ -1720,31 +2227,46 @@ class Jetpack_Core_Json_Api_Endpoints { 'black', 'white', ), - 'enum_labels' => array( + 'enum_labels' => array( 'black' => esc_html__( 'Black', 'jetpack' ), 'white' => esc_html__( 'White', 'jetpack' ), ), 'validate_callback' => __CLASS__ . '::validate_list_item', 'jp_group' => 'carousel', ), - 'carousel_display_exif' => array( - 'description' => wp_kses( sprintf( __( 'Show photo metadata (<a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ), array( 'a' => array( 'href' => true, 'target' => true ) ) ), + 'carousel_display_exif' => array( + 'description' => wp_kses( + sprintf( __( 'Show photo metadata (<a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ), + array( + 'a' => array( + 'href' => true, + 'target' => true, + ), + ) + ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'carousel', ), + 'carousel_display_comments' => array( + 'description' => esc_html__( 'Show comments area in carousel', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'carousel', + ), // Comments - 'highlander_comment_form_prompt' => array( + 'highlander_comment_form_prompt' => array( 'description' => esc_html__( 'Greeting Text', 'jetpack' ), 'type' => 'string', 'default' => esc_html__( 'Leave a Reply', 'jetpack' ), 'sanitize_callback' => 'sanitize_text_field', 'jp_group' => 'comments', ), - 'jetpack_comment_form_color_scheme' => array( - 'description' => esc_html__( "Color scheme", 'jetpack' ), + 'jetpack_comment_form_color_scheme' => array( + 'description' => esc_html__( 'Color scheme', 'jetpack' ), 'type' => 'string', 'default' => 'light', 'enum' => array( @@ -1752,7 +2274,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'dark', 'transparent', ), - 'enum_labels' => array( + 'enum_labels' => array( 'light' => esc_html__( 'Light', 'jetpack' ), 'dark' => esc_html__( 'Dark', 'jetpack' ), 'transparent' => esc_html__( 'Transparent', 'jetpack' ), @@ -1762,28 +2284,28 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Custom Content Types - 'jetpack_portfolio' => array( + 'jetpack_portfolio' => array( 'description' => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'custom-content-types', ), - 'jetpack_portfolio_posts_per_page' => array( + 'jetpack_portfolio_posts_per_page' => array( 'description' => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ), 'type' => 'integer', 'default' => 10, 'validate_callback' => __CLASS__ . '::validate_posint', 'jp_group' => 'custom-content-types', ), - 'jetpack_testimonial' => array( + 'jetpack_testimonial' => array( 'description' => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'custom-content-types', ), - 'jetpack_testimonial_posts_per_page' => array( + 'jetpack_testimonial_posts_per_page' => array( 'description' => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ), 'type' => 'integer', 'default' => 10, @@ -1792,7 +2314,7 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Galleries - 'tiled_galleries' => array( + 'tiled_galleries' => array( 'description' => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -1800,7 +2322,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'tiled-gallery', ), - 'gravatar_disable_hovercards' => array( + 'gravatar_disable_hovercards' => array( 'description' => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ), 'type' => 'string', 'default' => 'enabled', @@ -1809,7 +2331,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'enabled', 'disabled', ), - 'enum_labels' => array( + 'enum_labels' => array( 'enabled' => esc_html__( 'Enabled', 'jetpack' ), 'disabled' => esc_html__( 'Disabled', 'jetpack' ), ), @@ -1818,14 +2340,14 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Infinite Scroll - 'infinite_scroll' => array( + 'infinite_scroll' => array( 'description' => esc_html__( 'To infinity and beyond', 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'infinite-scroll', ), - 'infinite_scroll_google_analytics' => array( + 'infinite_scroll_google_analytics' => array( 'description' => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -1834,7 +2356,7 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Likes - 'wpl_default' => array( + 'wpl_default' => array( 'description' => esc_html__( 'WordPress.com Likes are', 'jetpack' ), 'type' => 'string', 'default' => 'on', @@ -1842,14 +2364,14 @@ class Jetpack_Core_Json_Api_Endpoints { 'on', 'off', ), - 'enum_labels' => array( + 'enum_labels' => array( 'on' => esc_html__( 'On for all posts', 'jetpack' ), 'off' => esc_html__( 'Turned on per post', 'jetpack' ), ), 'validate_callback' => __CLASS__ . '::validate_list_item', 'jp_group' => 'likes', ), - 'social_notifications_like' => array( + 'social_notifications_like' => array( 'description' => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ), 'type' => 'boolean', 'default' => 1, @@ -1865,7 +2387,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'markdown', ), - 'wpcom_publish_posts_with_markdown' => array( + 'wpcom_publish_posts_with_markdown' => array( 'description' => esc_html__( 'Use Markdown for posts.', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -1873,31 +2395,8 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'markdown', ), - // Mobile Theme - 'wp_mobile_excerpt' => array( - 'description' => esc_html__( 'Excerpts', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'minileven', - ), - 'wp_mobile_featured_images' => array( - 'description' => esc_html__( 'Featured Images', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'minileven', - ), - 'wp_mobile_app_promos' => array( - 'description' => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'minileven', - ), - // Monitor - 'monitor_receive_notifications' => array( + 'monitor_receive_notifications' => array( 'description' => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -1906,7 +2405,7 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Post by Email - 'post_by_email_address' => array( + 'post_by_email_address' => array( 'description' => esc_html__( 'Email Address', 'jetpack' ), 'type' => 'string', 'default' => 'noop', @@ -1916,7 +2415,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'regenerate', 'delete', ), - 'enum_labels' => array( + 'enum_labels' => array( 'noop' => '', 'create' => esc_html__( 'Create Post by Email address', 'jetpack' ), 'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ), @@ -1927,14 +2426,14 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Protect - 'jetpack_protect_key' => array( + 'jetpack_protect_key' => array( 'description' => esc_html__( 'Protect API key', 'jetpack' ), 'type' => 'string', 'default' => '', 'validate_callback' => __CLASS__ . '::validate_alphanum', 'jp_group' => 'protect', ), - 'jetpack_protect_global_whitelist' => array( + 'jetpack_protect_global_whitelist' => array( 'description' => esc_html__( 'Protect global whitelist', 'jetpack' ), 'type' => 'string', 'default' => '', @@ -1944,17 +2443,17 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Sharing - 'sharing_services' => array( + 'sharing_services' => array( 'description' => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ), 'type' => 'object', 'default' => array( - 'visible' => array( 'twitter', 'facebook', 'google-plus-1' ), + 'visible' => array( 'twitter', 'facebook' ), 'hidden' => array(), ), 'validate_callback' => __CLASS__ . '::validate_services', 'jp_group' => 'sharedaddy', ), - 'button_style' => array( + 'button_style' => array( 'description' => esc_html__( 'Button Style', 'jetpack' ), 'type' => 'string', 'default' => 'icon', @@ -1964,7 +2463,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'text', 'official', ), - 'enum_labels' => array( + 'enum_labels' => array( 'icon-text' => esc_html__( 'Icon + text', 'jetpack' ), 'icon' => esc_html__( 'Icon only', 'jetpack' ), 'text' => esc_html__( 'Text only', 'jetpack' ), @@ -1973,7 +2472,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'validate_callback' => __CLASS__ . '::validate_list_item', 'jp_group' => 'sharedaddy', ), - 'sharing_label' => array( + 'sharing_label' => array( 'description' => esc_html__( 'Sharing Label', 'jetpack' ), 'type' => 'string', 'default' => '', @@ -1981,17 +2480,17 @@ class Jetpack_Core_Json_Api_Endpoints { 'sanitize_callback' => 'esc_html', 'jp_group' => 'sharedaddy', ), - 'show' => array( + 'show' => array( 'description' => esc_html__( 'Views where buttons are shown', 'jetpack' ), 'type' => 'array', 'items' => array( - 'type' => 'string' + 'type' => 'string', ), 'default' => array( 'post' ), 'validate_callback' => __CLASS__ . '::validate_sharing_show', 'jp_group' => 'sharedaddy', ), - 'jetpack-twitter-cards-site-tag' => array( + 'jetpack-twitter-cards-site-tag' => array( 'description' => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ), 'type' => 'string', 'default' => '', @@ -1999,14 +2498,14 @@ class Jetpack_Core_Json_Api_Endpoints { 'sanitize_callback' => 'esc_html', 'jp_group' => 'sharedaddy', ), - 'sharedaddy_disable_resources' => array( + 'sharedaddy_disable_resources' => array( 'description' => esc_html__( 'Disable CSS and JS', 'jetpack' ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'sharedaddy', ), - 'custom' => array( + 'custom' => array( 'description' => esc_html__( 'Custom sharing services added by user.', 'jetpack' ), 'type' => 'object', 'default' => array( @@ -2018,7 +2517,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'sharedaddy', ), // Not an option, but an action that can be perfomed on the list of custom services passing the service ID. - 'sharing_delete_service' => array( + 'sharing_delete_service' => array( 'description' => esc_html__( 'Delete custom sharing service.', 'jetpack' ), 'type' => 'string', 'default' => '', @@ -2027,14 +2526,14 @@ class Jetpack_Core_Json_Api_Endpoints { ), // SSO - 'jetpack_sso_require_two_step' => array( + 'jetpack_sso_require_two_step' => array( 'description' => esc_html__( 'Require Two-Step Authentication', 'jetpack' ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'sso', ), - 'jetpack_sso_match_by_email' => array( + 'jetpack_sso_match_by_email' => array( 'description' => esc_html__( 'Match by Email', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -2043,21 +2542,21 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Subscriptions - 'stb_enabled' => array( + 'stb_enabled' => array( 'description' => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'subscriptions', ), - 'stc_enabled' => array( + 'stc_enabled' => array( 'description' => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'subscriptions', ), - 'social_notifications_subscribe' => array( + 'social_notifications_subscribe' => array( 'description' => esc_html__( 'Send email notification when someone follows my blog', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -2066,14 +2565,14 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Related Posts - 'show_headline' => array( + 'show_headline' => array( 'description' => esc_html__( 'Highlight related content with a heading', 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'related-posts', ), - 'show_thumbnails' => array( + 'show_thumbnails' => array( 'description' => esc_html__( 'Show a thumbnail image where available', 'jetpack' ), 'type' => 'boolean', 'default' => 0, @@ -2081,162 +2580,218 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'related-posts', ), + // Search. + 'instant_search_enabled' => array( + 'description' => esc_html__( 'Enable Instant Search', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'search', + ), + + 'has_jetpack_search_product' => array( + 'description' => esc_html__( 'Has an active Jetpack Search product purchase', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'settings', + ), + + 'search_auto_config' => array( + 'description' => esc_html__( 'Trigger an auto config of instant search', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'search', + ), + // Verification Tools - 'google' => array( + 'google' => array( 'description' => esc_html__( 'Google Search Console', 'jetpack' ), 'type' => 'string', 'default' => '', 'validate_callback' => __CLASS__ . '::validate_verification_service', 'jp_group' => 'verification-tools', ), - 'bing' => array( + 'bing' => array( 'description' => esc_html__( 'Bing Webmaster Center', 'jetpack' ), 'type' => 'string', 'default' => '', 'validate_callback' => __CLASS__ . '::validate_verification_service', 'jp_group' => 'verification-tools', ), - 'pinterest' => array( + 'pinterest' => array( 'description' => esc_html__( 'Pinterest Site Verification', 'jetpack' ), 'type' => 'string', 'default' => '', 'validate_callback' => __CLASS__ . '::validate_verification_service', 'jp_group' => 'verification-tools', ), - 'yandex' => array( + 'yandex' => array( 'description' => esc_html__( 'Yandex Site Verification', 'jetpack' ), 'type' => 'string', 'default' => '', 'validate_callback' => __CLASS__ . '::validate_verification_service', 'jp_group' => 'verification-tools', ), - 'enable_header_ad' => array( - 'description' => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_approved' => array( - 'description' => esc_html__( 'Is site approved for WordAds?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_second_belowpost' => array( - 'description' => esc_html__( 'Display second ad below post?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_front_page' => array( - 'description' => esc_html__( 'Display ads on the front page?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_post' => array( - 'description' => esc_html__( 'Display ads on posts?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_page' => array( - 'description' => esc_html__( 'Display ads on pages?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_archive' => array( - 'description' => esc_html__( 'Display ads on archive pages?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_custom_adstxt' => array( - 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_string', - 'sanitize_callback' => 'sanitize_textarea_field', - 'jp_group' => 'wordads', + 'facebook' => array( + 'description' => esc_html__( 'Facebook Domain Verification', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'validate_callback' => __CLASS__ . '::validate_verification_service', + 'jp_group' => 'verification-tools', + ), + + // WordAds. + 'enable_header_ad' => array( + 'description' => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_approved' => array( + 'description' => esc_html__( 'Is site approved for WordAds?', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_second_belowpost' => array( + 'description' => esc_html__( 'Display second ad below post?', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_display_front_page' => array( + 'description' => esc_html__( 'Display ads on the front page?', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_display_post' => array( + 'description' => esc_html__( 'Display ads on posts?', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_display_page' => array( + 'description' => esc_html__( 'Display ads on pages?', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_display_archive' => array( + 'description' => esc_html__( 'Display ads on archive pages?', 'jetpack' ), + 'type' => 'boolean', + 'default' => 1, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_custom_adstxt_enabled' => array( + 'description' => esc_html__( 'Custom ads.txt', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_custom_adstxt' => array( + 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'validate_callback' => __CLASS__ . '::validate_string', + 'sanitize_callback' => 'sanitize_textarea_field', + 'jp_group' => 'wordads', + ), + 'wordads_ccpa_enabled' => array( + 'description' => esc_html__( 'Enable support for California Consumer Privacy Act', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'wordads', + ), + 'wordads_ccpa_privacy_policy_url' => array( + 'description' => esc_html__( 'Privacy Policy URL', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'validate_callback' => __CLASS__ . '::validate_string', + 'sanitize_callback' => 'sanitize_text_field', + 'jp_group' => 'wordads', ), // Google Analytics - 'google_analytics_tracking_id' => array( - 'description' => esc_html__( 'Google Analytics', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_alphanum', - 'jp_group' => 'google-analytics', + 'google_analytics_tracking_id' => array( + 'description' => esc_html__( 'Google Analytics', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'validate_callback' => __CLASS__ . '::validate_alphanum', + 'jp_group' => 'google-analytics', ), // Stats - 'admin_bar' => array( + 'admin_bar' => array( 'description' => esc_html__( 'Include a small chart in your admin bar with a 48-hour traffic snapshot.', 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'stats', ), - 'roles' => array( + 'roles' => array( 'description' => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ), 'type' => 'array', 'items' => array( - 'type' => 'string' + 'type' => 'string', ), 'default' => array( 'administrator' ), 'validate_callback' => __CLASS__ . '::validate_stats_roles', 'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles', 'jp_group' => 'stats', ), - 'count_roles' => array( + 'count_roles' => array( 'description' => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ), 'type' => 'array', 'items' => array( - 'type' => 'string' + 'type' => 'string', ), 'default' => array( 'administrator' ), 'validate_callback' => __CLASS__ . '::validate_stats_roles', 'jp_group' => 'stats', ), - 'blog_id' => array( + 'blog_id' => array( 'description' => esc_html__( 'Blog ID.', 'jetpack' ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'stats', ), - 'do_not_track' => array( + 'do_not_track' => array( 'description' => esc_html__( 'Do not track.', 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'stats', ), - 'hide_smile' => array( - 'description' => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'stats', - ), - 'version' => array( + 'version' => array( 'description' => esc_html__( 'Version.', 'jetpack' ), 'type' => 'integer', 'default' => 9, 'validate_callback' => __CLASS__ . '::validate_posint', 'jp_group' => 'stats', ), + 'collapse_nudges' => array( + 'description' => esc_html__( 'Collapse upgrade nudges', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'stats', + ), // Akismet - Not a module, but a plugin. The options can be passed and handled differently. - 'akismet_show_user_comments_approved' => array( + 'akismet_show_user_comments_approved' => array( 'description' => '', 'type' => 'boolean', 'default' => 0, @@ -2244,7 +2799,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'settings', ), - 'wordpress_api_key' => array( + 'wordpress_api_key' => array( 'description' => '', 'type' => 'string', 'default' => '', @@ -2253,7 +2808,7 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Apps card on dashboard - 'dismiss_dash_app_card' => array( + 'dismiss_dash_app_card' => array( 'description' => '', 'type' => 'boolean', 'default' => 0, @@ -2262,7 +2817,7 @@ class Jetpack_Core_Json_Api_Endpoints { ), // Empty stats card dismiss - 'dismiss_empty_stats_card' => array( + 'dismiss_empty_stats_card' => array( 'description' => '', 'type' => 'boolean', 'default' => 0, @@ -2270,14 +2825,14 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'settings', ), - 'lang_id' => array( + 'lang_id' => array( 'description' => esc_html__( 'Primary language for the site.', 'jetpack' ), - 'type' => 'string', - 'default' => 'en_US', - 'jp_group' => 'settings', + 'type' => 'string', + 'default' => 'en_US', + 'jp_group' => 'settings', ), - 'onboarding' => array( + 'onboarding' => array( 'description' => '', 'type' => 'object', 'default' => array( @@ -2299,6 +2854,30 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'settings', ), + // SEO Tools. + 'advanced_seo_front_page_description' => array( + 'description' => esc_html__( 'Front page meta description.', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'sanitize_callback' => 'Jetpack_SEO_Utils::sanitize_front_page_meta_description', + 'jp_group' => 'seo-tools', + ), + + 'advanced_seo_title_formats' => array( + 'description' => esc_html__( 'SEO page title structures.', 'jetpack' ), + 'type' => 'object', + 'default' => array( + 'archives' => array(), + 'front_page' => array(), + 'groups' => array(), + 'pages' => array(), + 'posts' => array(), + ), + 'jp_group' => 'seo-tools', + 'validate_callback' => 'Jetpack_SEO_Titles::are_valid_title_formats', + 'sanitize_callback' => 'Jetpack_SEO_Titles::sanitize_title_formats', + ), + ); // Add modules to list so they can be toggled @@ -2311,7 +2890,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'validate_callback' => __CLASS__ . '::validate_boolean', 'jp_group' => 'modules', ); - foreach( $modules as $module ) { + foreach ( $modules as $module ) { $options[ $module ] = $module_args; } } @@ -2378,14 +2957,15 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string|bool $value Value to check. + * @param string|bool $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ public static function validate_boolean( $value, $request, $param ) { - if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) { + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- Other code depends on loose comparison here. + if ( ! is_bool( $value ) && ! ( ctype_digit( (string) $value ) && in_array( $value, array( 0, 1 ) ) ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) ); } return true; @@ -2396,13 +2976,13 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param int $value Value to check. + * @param int $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_posint( $value = 0, $request, $param ) { + public static function validate_posint( $value, $request, $param ) { if ( ! is_numeric( $value ) || $value <= 0 ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a positive integer.', 'jetpack' ), $param ) ); } @@ -2410,17 +2990,39 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Validates that the parameter is a non-negative integer (includes 0). + * + * @since 10.4.0 + * + * @param int $value Value to check. + * @param WP_REST_Request $request The request sent to the WP REST API. + * @param string $param Name of the parameter passed to endpoint holding $value. + * + * @return bool|WP_Error + */ + public static function validate_non_neg_int( $value, $request, $param ) { + if ( ! is_numeric( $value ) || $value < 0 ) { + return new WP_Error( + 'invalid_param', + /* translators: %s: The literal parameter name. Should not be translated. */ + sprintf( esc_html__( '%s must be a non-negative integer.', 'jetpack' ), $param ) + ); + } + return true; + } + + /** * Validates that the parameter belongs to a list of admitted values. * * @since 4.3.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_list_item( $value = '', $request, $param ) { + public static function validate_list_item( $value, $request, $param ) { $attributes = $request->get_attributes(); if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s not recognized', 'jetpack' ), $param ) ); @@ -2431,10 +3033,15 @@ class Jetpack_Core_Json_Api_Endpoints { // If it's an associative array, use the keys to check that the value is among those admitted. $enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) ? array_keys( $args['enum'] ) : $args['enum']; if ( ! in_array( $value, $enum ) ) { - return new WP_Error( 'invalid_param_value', sprintf( + return new WP_Error( + 'invalid_param_value', + sprintf( /* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */ - esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum ) - ) ); + esc_html__( '%1$s must be one of %2$s', 'jetpack' ), + $param, + implode( ', ', $enum ) + ) + ); } } return true; @@ -2445,13 +3052,13 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_module_list( $value = '', $request, $param ) { + public static function validate_module_list( $value, $request, $param ) { if ( ! is_array( $value ) ) { return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be an array', 'jetpack' ), $param ) ); } @@ -2470,13 +3077,13 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_alphanum( $value = '', $request, $param ) { + public static function validate_alphanum( $value, $request, $param ) { if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^[a-z0-9]+$/i', $value ) ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) ); } @@ -2488,13 +3095,13 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.6.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_verification_service( $value = '', $request, $param ) { + public static function validate_verification_service( $value, $request, $param ) { if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) ); } @@ -2506,18 +3113,23 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string|bool $value Value to check. + * @param string|bool $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ public static function validate_stats_roles( $value, $request, $param ) { if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) { - return new WP_Error( 'invalid_param', sprintf( + return new WP_Error( + 'invalid_param', + sprintf( /* 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. */ - esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', self::$stats_roles ) - ) ); + esc_html__( '%1$s must be %2$s.', 'jetpack' ), + $param, + join( ', ', self::$stats_roles ) + ) + ); } return true; } @@ -2527,9 +3139,9 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string|bool $value Value to check. + * @param string|bool $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ @@ -2539,10 +3151,15 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) ); } if ( ! array_intersect( $views, $value ) ) { - return new WP_Error( 'invalid_param', sprintf( + return new WP_Error( + 'invalid_param', + sprintf( /* 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 */ - esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views ) - ) ); + esc_html__( '%1$s must be %2$s.', 'jetpack' ), + $param, + join( ', ', $views ) + ) + ); } return true; } @@ -2552,14 +3169,14 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string|bool $value { - * Value to check received by request. + * @param string|bool $value { + * Value to check received by request. * * @type array $visible List of slug of services to share to that are displayed directly in the page. * @type array $hidden List of slug of services to share to that are concealed in a folding menu. * } * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ @@ -2573,21 +3190,25 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { + if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) { return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) ); } - $sharer = new Sharing_Service(); + $sharer = new Sharing_Service(); $services = array_keys( $sharer->get_all_services() ); if ( ( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) ) || - ( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) ) - { - return new WP_Error( 'invalid_param', sprintf( + ( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) ) { + return new WP_Error( + 'invalid_param', + sprintf( /* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */ - esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services ) - ) ); + esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), + $param, + join( ', ', $services ) + ) + ); } return true; } @@ -2597,9 +3218,9 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string|bool $value Value to check. + * @param string|bool $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ @@ -2613,7 +3234,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { + if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) { return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) ); } @@ -2630,21 +3251,21 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_custom_service_id( $value = '', $request, $param ) { + public static function validate_custom_service_id( $value, $request, $param ) { if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( "%s must be a string prefixed with 'custom-' and followed by a numeric ID.", 'jetpack' ), $param ) ); } - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { + if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) { return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) ); } - $sharer = new Sharing_Service(); + $sharer = new Sharing_Service(); $services = array_keys( $sharer->get_all_services() ); if ( ! empty( $value ) && ! in_array( $value, $services ) ) { @@ -2659,13 +3280,13 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_twitter_username( $value = '', $request, $param ) { + public static function validate_twitter_username( $value, $request, $param ) { if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/^@?\w{1,15}$/i', $value ) ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a Twitter username.', 'jetpack' ), $param ) ); } @@ -2677,13 +3298,13 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @param string $value Value to check. + * @param string $value Value to check. * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. + * @param string $param Name of the parameter passed to endpoint holding $value. * * @return bool|WP_Error */ - public static function validate_string( $value = '', $request, $param ) { + public static function validate_string( $value, $request, $param ) { if ( ! is_string( $value ) ) { return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be a string.', 'jetpack' ), $param ) ); } @@ -2691,6 +3312,26 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Validates that the parameter is an array of strings. + * + * @param array $value Value to check. + * @param WP_REST_Request $request The request sent to the WP REST API. + * @param string $param Name of the parameter passed to the endpoint holding $value. + * + * @return bool|WP_Error + */ + public static function validate_array_of_strings( $value, $request, $param ) { + foreach ( $value as $array_item ) { + $validate = self::validate_string( $array_item, $request, $param ); + if ( is_wp_error( $validate ) ) { + return $validate; + } + } + + return true; + } + + /** * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes), * return an array with only 'administrator' as the allowed role and save it for 'roles' option. * @@ -2748,23 +3389,23 @@ class Jetpack_Core_Json_Api_Endpoints { $location = apply_filters( 'jetpack_sitemap_location', '' ); if ( $wp_rewrite->using_index_permalinks() ) { - $sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' ); + $sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' ); $news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' ); - } else if ( $wp_rewrite->using_permalinks() ) { - $sitemap_url = home_url( $location . '/sitemap.xml' ); + } elseif ( $wp_rewrite->using_permalinks() ) { + $sitemap_url = home_url( $location . '/sitemap.xml' ); $news_sitemap_url = home_url( $location . '/news-sitemap.xml' ); } else { - $sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' ); + $sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' ); $news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' ); } if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) { // Is a list of modules - $modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url; + $modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url; $modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url; } elseif ( 'sitemaps' == $slug ) { // It's a single module - $modules['extra']['sitemap_url'] = $sitemap_url; + $modules['extra']['sitemap_url'] = $sitemap_url; $modules['extra']['news_sitemap_url'] = $news_sitemap_url; } return $modules; @@ -2804,7 +3445,7 @@ class Jetpack_Core_Json_Api_Endpoints { // Protect $options['jetpack_protect_key']['current_value'] = get_site_option( 'jetpack_protect_key', false ); if ( ! function_exists( 'jetpack_protect_format_whitelist' ) ) { - include_once( JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php' ); + include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php'; } $options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist(); break; @@ -2820,26 +3461,26 @@ class Jetpack_Core_Json_Api_Endpoints { break; case 'google-analytics': - $wga = get_option( 'jetpack_wga' ); + $wga = get_option( 'jetpack_wga' ); $code = ''; if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) { $code = $wga[ 'code' ]; } - $options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code; + $options['google_analytics_tracking_id']['current_value'] = $code; break; case 'sharedaddy': // It's local, but it must be broken apart since it's saved as an array. - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { + if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) { break; } - $sharer = new Sharing_Service(); - $options = self::split_options( $options, $sharer->get_global_options() ); + $sharer = new Sharing_Service(); + $options = self::split_options( $options, $sharer->get_global_options() ); $options['sharing_services']['current_value'] = $sharer->get_blog_services(); - $other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' ); + $other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' ); foreach ( $other_sharedaddy_options as $key ) { - $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; - $current_value = get_option( $key, $default_value ); + $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; + $current_value = get_option( $key, $default_value ); $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] ); } break; @@ -2847,15 +3488,15 @@ class Jetpack_Core_Json_Api_Endpoints { case 'stats': // It's local, but it must be broken apart since it's saved as an array. if ( ! function_exists( 'stats_get_options' ) ) { - include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' ); + include_once JETPACK__PLUGIN_DIR . 'modules/stats.php'; } $options = self::split_options( $options, stats_get_options() ); break; default: // These option are just stored as plain WordPress options. foreach ( $options as $key => $value ) { - $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; - $current_value = get_option( $key, $default_value ); + $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; + $current_value = get_option( $key, $default_value ); $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] ); } } @@ -2964,7 +3605,7 @@ class Jetpack_Core_Json_Api_Endpoints { } // Only check a remote option if Jetpack is connected. - if ( ! Jetpack::is_active() ) { + if ( ! Jetpack::is_connection_ready() ) { return false; } @@ -2972,7 +3613,7 @@ class Jetpack_Core_Json_Api_Endpoints { switch ( $module ) { case 'monitor': // Load the class to use the method. If class can't be found, do nothing. - if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) { + if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once Jetpack::get_module_path( $module ) ) { return false; } $value = Jetpack_Monitor::user_receives_notifications( false ); @@ -2980,11 +3621,10 @@ class Jetpack_Core_Json_Api_Endpoints { case 'post-by-email': // Load the class to use the method. If class can't be found, do nothing. - if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) { + if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once Jetpack::get_module_path( $module ) ) { return false; } - $post_by_email = new Jetpack_Post_By_Email(); - $value = $post_by_email->get_post_by_email_address(); + $value = Jetpack_Post_By_Email::init()->get_post_by_email_address(); if ( $value === null ) { $value = 'NULL'; // sentinel value so it actually gets set } @@ -3032,181 +3672,221 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) ); } - /** - * Returns a list of all plugins in the site. + * Get plugins data in site. * * @since 4.2.0 - * @uses get_plugins() * - * @return array + * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error. */ - private static function core_get_plugins() { - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ - $plugins = apply_filters( 'all_plugins', get_plugins() ); + public static function get_plugins() { + jetpack_require_lib( 'plugins' ); + $plugins = Jetpack_Plugins::get_plugins(); - if ( is_array( $plugins ) && ! empty( $plugins ) ) { - foreach ( $plugins as $plugin_slug => $plugin_data ) { - $plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug ); - } - return $plugins; + if ( ! empty( $plugins ) ) { + return rest_ensure_response( $plugins ); } - return array(); + return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) ); } /** - * Deprecated - Get third party plugin API keys. - * @deprecated + * Install a specific plugin and optionally activates it. * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function get_service_api_key( $request ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key( $request ); - } - - /** - * Deprecated - Update third party plugin API keys. - * @deprecated + * @since 8.9.0 * * @param WP_REST_Request $request { * Array of parameters received by request. * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. + * @type string $slug Plugin slug. + * @type string $status Plugin status. + * @type string $source Where did the plugin installation request originate. * } + * + * @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. */ - public static function update_service_api_key( $request ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key( $request ) ; + public static function install_plugin( $request ) { + $plugin = stripslashes( $request['slug'] ); + + jetpack_require_lib( 'plugins' ); + + // Let's make sure the plugin isn't already installed. + $plugin_id = Jetpack_Plugins::get_plugin_id_by_slug( $plugin ); + + // If not installed, let's install now. + if ( ! $plugin_id ) { + $result = Jetpack_Plugins::install_plugin( $plugin ); + + if ( is_wp_error( $result ) ) { + return new WP_Error( + 'install_plugin_failed', + sprintf( + /* translators: %1$s: plugin name. -- %2$s: error message. */ + __( 'Unable to install %1$s: %2$s ', 'jetpack' ), + $plugin, + $result->get_error_message() + ), + array( 'status' => 500 ) + ); + } + } + + /* + * We may want to activate the plugin as well. + * Let's check for the status parameter in the request to find out. + * If none was passed (or something other than active), let's return now. + */ + if ( empty( $request['status'] ) || 'active' !== $request['status'] ) { + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html( + sprintf( + /* translators: placeholder is a plugin name. */ + __( 'Installed %s', 'jetpack' ), + $plugin + ) + ), + ) + ); + } + + /* + * Proceed with plugin activation. + * Let's check again for the plugin's ID if we don't already have it. + */ + if ( ! $plugin_id ) { + $plugin_id = Jetpack_Plugins::get_plugin_id_by_slug( $plugin ); + if ( ! $plugin_id ) { + return new WP_Error( + 'unable_to_determine_installed_plugin', + __( 'Unable to determine what plugin was installed.', 'jetpack' ), + array( 'status' => 500 ) + ); + } + } + + $source = ! empty( $request['source'] ) ? stripslashes( $request['source'] ) : 'rest_api'; + $plugin_args = array( + 'plugin' => substr( $plugin_id, 0, - 4 ), + 'status' => 'active', + 'source' => $source, + ); + return self::activate_plugin( $plugin_args ); } /** - * Deprecated - Delete a third party plugin API key. - * @deprecated + * Activate a specific plugin. + * + * @since 8.9.0 * * @param WP_REST_Request $request { * Array of parameters received by request. * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. + * @type string $plugin Plugin long slug (slug/index-file) + * @type string $status Plugin status. We only support active in Jetpack. + * @type string $source Where did the plugin installation request originate. * } - */ - public static function delete_service_api_key( $request ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key( $request ); - } - - /** - * Deprecated - Validate the service provided in /service-api-keys/ endpoints. - * To add a service to these endpoints, add the service name to $valid_services - * and add '{service name}_api_key' to the non-compact return array in get_option_names(), - * in class-jetpack-options.php - * @deprecated * - * @param string $service The service the API key is for. - * @return string Returns the service name if valid, null if invalid. + * @return WP_REST_Response|WP_Error A response object if the activation was successful, or a WP_Error object if the activation failed. */ - public static function validate_service_api_service( $service = null ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service( $service ); - } + public static function activate_plugin( $request ) { + /* + * We need an "active" status parameter to be passed to the request + * just like the core plugins endpoind we'll eventually switch to. + */ + if ( empty( $request['status'] ) || 'active' !== $request['status'] ) { + return new WP_Error( + 'missing_status_parameter', + esc_html__( 'Status parameter missing.', 'jetpack' ), + array( 'status' => 403 ) + ); + } - /** - * Error response for invalid service API key requests with an invalid service. - */ - public static function service_api_invalid_service_response() { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response(); - } + jetpack_require_lib( 'plugins' ); + $plugins = Jetpack_Plugins::get_plugins(); - /** - * Deprecated - Validate API Key - * @deprecated - * - * @param string $key The API key to be validated. - * @param string $service The service the API key is for. - * - */ - public static function validate_service_api_key( $key = null, $service = null ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key( $key , $service ); - } + if ( empty( $plugins ) ) { + return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) ); + } - /** - * Deprecated - Validate Mapbox API key - * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php - * @deprecated - * - * @param string $key The API key to be validated. - */ - public static function validate_service_api_key_mapbox( $key ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key_mapbox( $key ); + if ( empty( $request['plugin'] ) ) { + return new WP_Error( 'no_plugin_specified', esc_html__( 'You did not specify a plugin.', 'jetpack' ), array( 'status' => 404 ) ); + } - } + $plugin = $request['plugin'] . '.php'; - /** - * Checks if the queried plugin is active. - * - * @since 4.2.0 - * @uses is_plugin_active() - * - * @return bool - */ - private static function core_is_plugin_active( $plugin ) { - if ( ! function_exists( 'is_plugin_active' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; + // Is the plugin installed? + if ( ! in_array( $plugin, array_keys( $plugins ), true ) ) { + return new WP_Error( + 'plugin_not_found', + esc_html( + sprintf( + /* translators: placeholder is a plugin slug. */ + __( 'Plugin %s is not installed.', 'jetpack' ), + $plugin + ) + ), + array( 'status' => 404 ) + ); } - return is_plugin_active( $plugin ); - } + // Is the plugin active already? + $status = Jetpack_Plugins::get_plugin_status( $plugin ); + if ( in_array( $status, array( 'active', 'network-active' ), true ) ) { + return new WP_Error( + 'plugin_already_active', + esc_html( + sprintf( + /* translators: placeholder is a plugin slug. */ + __( 'Plugin %s is already active.', 'jetpack' ), + $plugin + ) + ), + array( 'status' => 404 ) + ); + } - /** - * Get plugins data in site. - * - * @since 4.2.0 - * - * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function get_plugins() { - $plugins = self::core_get_plugins(); + // Now try to activate the plugin. + $activated = activate_plugin( $plugin ); - if ( ! empty( $plugins ) ) { - return rest_ensure_response( $plugins ); + if ( is_wp_error( $activated ) ) { + return $activated; + } else { + $source = ! empty( $request['source'] ) ? stripslashes( $request['source'] ) : 'rest_api'; + /** + * Fires when Jetpack installs a plugin for you. + * + * @since 8.9.0 + * + * @param string $plugin_file Plugin file. + * @param string $source Where did the plugin installation originate. + */ + do_action( 'jetpack_activated_plugin', $plugin, $source ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => sprintf( + /* translators: placeholder is a plugin name. */ + esc_html__( 'Activated %s', 'jetpack' ), + $plugin + ), + ) + ); } - - return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) ); } /** - * Ensures that Akismet is installed and activated. + * Check if a plugin can be activated. * - * @since 7.7 + * @since 8.9.0 * - * @return WP_REST_Response A response indicating whether or not the installation was successful. + * @param string|bool $value Value to check. + * @param WP_REST_Request $request The request sent to the WP REST API. + * @param string $param Name of the parameter passed to endpoint holding $value. */ - public static function activate_akismet() { - jetpack_require_lib( 'plugins' ); - $result = Jetpack_Plugins::install_and_activate_plugin('akismet'); - - if ( is_wp_error( $result ) ) { - return rest_ensure_response( array( - 'code' => 'failure', - 'message' => esc_html__( 'Unable to activate Akismet', 'jetpack' ) - ) ); - } else { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Activated Akismet', 'jetpack' ) - ) ); - } + public static function validate_activate_plugin( $value, $request, $param ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return 'active' === $value; } /** @@ -3223,8 +3903,8 @@ class Jetpack_Core_Json_Api_Endpoints { * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error. */ public static function get_plugin( $request ) { - - $plugins = self::core_get_plugins(); + jetpack_require_lib( 'plugins' ); + $plugins = Jetpack_Plugins::get_plugins(); if ( empty( $plugins ) ) { return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) ); @@ -3238,13 +3918,15 @@ class Jetpack_Core_Json_Api_Endpoints { $plugin_data = $plugins[ $plugin ]; - $plugin_data['active'] = self::core_is_plugin_active( $plugin ); + $plugin_data['active'] = in_array( Jetpack_Plugins::get_plugin_status( $plugin ), array( 'active', 'network-active' ), true ); - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Plugin found.', 'jetpack' ), - 'data' => $plugin_data - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'Plugin found.', 'jetpack' ), + 'data' => $plugin_data, + ) + ); } /** @@ -3281,4 +3963,166 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); } + + /** + * Get the last licensing error message, if any. + * + * @since 9.0.0 + * + * @return string Licensing error message or empty string. + */ + public static function get_licensing_error() { + return Licensing::instance()->last_error(); + } + + /** + * Update the last licensing error message. + * + * @since 9.0.0 + * + * @param WP_REST_Request $request The request. + * + * @return bool true. + */ + public static function update_licensing_error( $request ) { + Licensing::instance()->log_error( $request['error'] ); + + return true; + } + + /** + * Set a Jetpack license + * + * @since 9.6.0 + * + * @param WP_REST_Request $request The request. + * + * @return WP_REST_Response|WP_Error A response object if the option was successfully updated, or a WP_Error if it failed. + */ + public static function set_jetpack_license( $request ) { + $license = trim( sanitize_text_field( $request['license'] ) ); + + if ( Licensing::instance()->append_license( $license ) ) { + return rest_ensure_response( array( 'code' => 'success' ) ); + } + + return new WP_Error( + 'setting_license_key_failed', + esc_html__( 'Could not set this license key. Please try again.', 'jetpack' ), + array( 'status' => 500 ) + ); + } + + /** + * Attach Jetpack licenses + * + * @since 10.4.0 + * + * @param WP_REST_Request $request The request. + * + * @return WP_REST_Response|WP_Error A response object + */ + public static function attach_jetpack_licenses( $request ) { + $licenses = array_map( + function ( $license ) { + return trim( sanitize_text_field( $license ) ); + }, + $request['licenses'] + ); + return rest_ensure_response( Licensing::instance()->attach_licenses( $licenses ) ); + } + + /** + * Returns the Jetpack CRM data. + * + * @return WP_REST_Response A response object containing the Jetpack CRM data. + */ + public static function get_jetpack_crm_data() { + $jetpack_crm_data = ( new Jetpack_CRM_Data() )->get_crm_data(); + return rest_ensure_response( $jetpack_crm_data ); + } + + /** + * Activates Jetpack CRM's Jetpack Forms extension. + * + * @param WP_REST_Request $request The request sent to the WP REST API. + * @return WP_REST_Response|WP_Error A response object if the extension activation was successful, or a WP_Error object if it failed. + */ + public static function activate_crm_jetpack_forms_extension( $request ) { + if ( ! isset( $request['extension'] ) || 'jetpackforms' !== $request['extension'] ) { + return new WP_Error( 'invalid_param', esc_html__( 'Missing or invalid extension parameter.', 'jetpack' ), array( 'status' => 404 ) ); + } + + $result = ( new Jetpack_CRM_Data() )->activate_crm_jetpackforms_extension(); + + if ( is_wp_error( $result ) ) { + return $result; + } + + return rest_ensure_response( array( 'code' => 'success' ) ); + } + + /** + * Verifies that the current user has the required permission for accessing the CRM data. + * + * @return true|WP_Error Returns true if the user has the required capability, else a WP_Error object. + */ + public static function jetpack_crm_data_permission_check() { + if ( current_user_can( 'publish_posts' ) ) { + return true; + } + + return new WP_Error( + 'invalid_user_permission_jetpack_crm_data', + self::$user_permissions_error_msg, + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Verifies that the current user has the required capability for activating Jetpack CRM extensions. + * + * @return true|WP_Error Returns true if the user has the required capability, else a WP_Error object. + */ + public static function activate_crm_extensions_permission_check() { + if ( current_user_can( 'admin_zerobs_manage_options' ) ) { + return true; + } + + return new WP_Error( + 'invalid_user_permission_activate_jetpack_crm_ext', + self::$user_permissions_error_msg, + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Verify that the user can set a Jetpack license key + * + * @since 9.5.0 + * + * @return bool|WP_Error True if user is able to set a Jetpack license key + */ + public static function set_jetpack_license_key_permission_check() { + if ( Licensing::instance()->is_licensing_input_enabled() ) { + return true; + } + + return new WP_Error( 'invalid_user_permission_set_jetpack_license_key', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + + } + + /** + * Set hasSeenWCConnectionModal to true when the site has displayed it + * + * @since 10.4.0 + * + * @return bool + */ + public static function set_has_seen_wc_connection_modal() { + $updated_option = Jetpack_Options::update_option( 'has_seen_wc_connection_modal', true ); + + return rest_ensure_response( array( 'success' => $updated_option ) ); + } + } // class end |