diff options
author | Yury German <blueknight@gentoo.org> | 2022-06-15 12:08:35 -0400 |
---|---|---|
committer | Yury German <blueknight@gentoo.org> | 2022-06-15 12:08:35 -0400 |
commit | 36d7691c33cb64ece817246e47a779ec648d10b0 (patch) | |
tree | 08f2fb95303a1d8eeba2c8629a24b35a91fb1cac /plugins/jetpack/_inc/lib | |
parent | twentyfourteen upg 2.7 to 3.2 and twentysixteen from 2.0 to 2.5 (diff) | |
download | blogs-gentoo-36d7691c33cb64ece817246e47a779ec648d10b0.tar.gz blogs-gentoo-36d7691c33cb64ece817246e47a779ec648d10b0.tar.bz2 blogs-gentoo-36d7691c33cb64ece817246e47a779ec648d10b0.zip |
Openid-3.6.1 and jetpack-11.0 upgrade
Signed-off-by: Yury German <blueknight@gentoo.org>
Diffstat (limited to 'plugins/jetpack/_inc/lib')
55 files changed, 3587 insertions, 3596 deletions
diff --git a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php index 6cd1faf6..1dcf8edd 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php @@ -12,7 +12,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -require_once 'class.jetpack-admin-page.php'; +require_once __DIR__ . '/class.jetpack-admin-page.php'; /** * Builds the landing page and its menu. @@ -53,10 +53,12 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { /** * Add page action * - * @param string $hook Hook of current page, unused. + * @param string $hook Hook of current page. */ - public function add_page_actions( $hook ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $this->a8c_data = $this->fetch_a8c_data(); + public function add_page_actions( $hook ) { + if ( 'admin_page_jetpack_about' === $hook ) { + $this->a8c_data = $this->fetch_a8c_data(); + } } /** @@ -537,7 +539,7 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { true ); if ( ! empty( $data ) && is_array( $data ) ) { - set_transient( 'jetpack_a8c_data', $data, DAY_IN_SECONDS ); + set_transient( 'jetpack_a8c_data', $data, WEEK_IN_SECONDS ); } else { // Fallback if everything fails. $data = array( diff --git a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-redux-state-helper.php b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-redux-state-helper.php index ebad61af..dff497ea 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-redux-state-helper.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-redux-state-helper.php @@ -13,6 +13,8 @@ use Automattic\Jetpack\Constants; use Automattic\Jetpack\Device_Detection\User_Agent_Info; use Automattic\Jetpack\Identity_Crisis; use Automattic\Jetpack\Licensing; +use Automattic\Jetpack\Licensing\Endpoints as Licensing_Endpoints; +use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer; use Automattic\Jetpack\Partner; use Automattic\Jetpack\Partner_Coupon as Jetpack_Partner_Coupon; use Automattic\Jetpack\Status; @@ -23,6 +25,18 @@ use Automattic\Jetpack\Status\Host; */ class Jetpack_Redux_State_Helper { /** + * Generate minimal state for React to fetch its own data asynchronously after load + * This can improve user experience, reducing time spent on server requests before serving the page + * e.g. used by React Disconnect Dialog on plugins page where the full initial state is not needed + */ + public static function get_minimal_state() { + return array( + 'WP_API_root' => esc_url_raw( rest_url() ), + 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ), + ); + } + + /** * Generate the initial state array to be used by the Redux store. */ public static function get_initial_state() { @@ -45,6 +59,10 @@ class Jetpack_Redux_State_Helper { $modules[ $slug ]['long_description'] = html_entity_decode( $data['long_description'] ); } + // "mock" a block module in order to get it searchable in the settings. + $modules['blocks']['module'] = 'blocks'; + $modules['blocks']['additional_search_queries'] = esc_html_x( 'blocks, block, gutenberg', 'Search terms', 'jetpack' ); + // Collecting roles that can view site stats. $stats_roles = array(); $enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' ); @@ -100,10 +118,6 @@ class Jetpack_Redux_State_Helper { $host = new Host(); - // Get Jetpack benefits for this site. - $jetpack_benefits_response = Jetpack_Core_API_Site_Endpoint::get_benefits(); - $jetpack_benefits = 200 === $jetpack_benefits_response->status ? json_decode( $jetpack_benefits_response->data['data'] ) : array(); - return array( 'WP_API_root' => esc_url_raw( rest_url() ), 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ), @@ -113,7 +127,7 @@ class Jetpack_Redux_State_Helper { 'pluginBaseUrl' => plugins_url( '', JETPACK__PLUGIN_FILE ), 'connectionStatus' => $connection_status, 'connectedPlugins' => Connection_Plugin_Storage::get_all(), - 'connectUrl' => false == $current_user_data['isConnected'] // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + 'connectUrl' => false == $current_user_data['isConnected'] // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual ? Jetpack::init()->build_connect_url( true, false, false ) : '', 'dismissedNotices' => self::get_dismissed_jetpack_notices(), @@ -145,7 +159,7 @@ class Jetpack_Redux_State_Helper { 'icon' => has_site_icon() ? apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) ) : '', - 'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + 'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual /** * Whether promotions are visible or not. * @@ -160,6 +174,8 @@ class Jetpack_Redux_State_Helper { 'plan' => Jetpack_Plan::get(), 'showBackups' => Jetpack::show_backups_ui(), 'showRecommendations' => Jetpack_Recommendations::is_enabled(), + /** This filter is documented in my-jetpack/src/class-initializer.php */ + 'showMyJetpack' => My_Jetpack_Initializer::should_initialize(), 'isMultisite' => is_multisite(), 'dateFormat' => get_option( 'date_format' ), ), @@ -168,9 +184,10 @@ class Jetpack_Redux_State_Helper { 'hasUpdate' => (bool) get_theme_update_available( $current_theme ), 'support' => array( 'infinite-scroll' => current_theme_supports( 'infinite-scroll' ) || in_array( $current_theme->get_stylesheet(), $inf_scr_support_themes, true ), + 'widgets' => current_theme_supports( 'widgets' ), + 'webfonts' => WP_Theme_JSON_Resolver::theme_has_support() && function_exists( 'wp_register_webfont_provider' ) && function_exists( 'wp_register_webfonts' ), ), ), - 'jetpackBenefits' => $jetpack_benefits, 'jetpackStateNotices' => array( 'messageCode' => Jetpack::state( 'message' ), 'errorCode' => Jetpack::state( 'error' ), @@ -189,12 +206,14 @@ class Jetpack_Redux_State_Helper { 'licensing' => array( 'error' => Licensing::instance()->last_error(), 'showLicensingUi' => Licensing::instance()->is_licensing_input_enabled(), - 'userCounts' => Jetpack_Core_Json_Api_Endpoints::get_user_license_counts(), + 'userCounts' => Licensing_Endpoints::get_user_license_counts(), 'activationNoticeDismiss' => Licensing::instance()->get_license_activation_notice_dismiss(), ), 'hasSeenWCConnectionModal' => Jetpack_Options::get_option( 'has_seen_wc_connection_modal', false ), + 'newRecommendations' => Jetpack_Recommendations::get_new_conditional_recommendations(), // Check if WooCommerce plugin is active (based on https://docs.woocommerce.com/document/create-a-plugin/). 'isWooCommerceActive' => in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', Jetpack::get_active_plugins() ), true ), + 'useMyJetpackLicensingUI' => My_Jetpack_Initializer::is_licensing_ui_enabled(), ); } @@ -342,7 +361,7 @@ class Jetpack_Redux_State_Helper { $connect_urls = array(); jetpack_require_lib( 'class.jetpack-keyring-service-helper' ); // phpcs:disable - foreach ( Jetpack_Keyring_Service_Helper::$SERVICES as $service_name => $service_info ) { + foreach ( Jetpack_Keyring_Service_Helper::SERVICES as $service_name => $service_info ) { // phpcs:enable $connect_urls[ $service_name ] = Jetpack_Keyring_Service_Helper::connect_url( $service_name, $service_info['for'] ); } @@ -380,7 +399,6 @@ class Jetpack_Redux_State_Helper { public static function generate_purchase_token() { return wp_generate_password( 12, false ); } - } /** diff --git a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-search-dashboard-page.php b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-search-dashboard-page.php deleted file mode 100644 index 5eef2dfe..00000000 --- a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-search-dashboard-page.php +++ /dev/null @@ -1,137 +0,0 @@ -<?php -/** - * A class that adds a search dashboard to wp-admin. - * - * @package automattic/jetpack - */ - -use Automattic\Jetpack\Assets; -use Automattic\Jetpack\Status; - -/** - * Requires files needed. - */ -require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-admin-page.php'; -require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class-jetpack-redux-state-helper.php'; - -/** - * Responsible for adding a search dashboard to wp-admin. - * - * @package Automattic\Jetpack\Search - */ -class Jetpack_Search_Dashboard_Page extends Jetpack_Admin_Page { - /** - * Show the settings page only when Jetpack is connected or in dev mode. - * - * @var bool If the page should be shown. - */ - protected $dont_show_if_not_active = true; - - /** - * Add page specific actions given the page hook. - * - * @param {object} $hook The page hook. - */ - public function add_page_actions( $hook ) {}// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - - /** - * Create a menu item for the page and returns the hook. - */ - public function get_page_hook() { - - if ( ! $this->should_add_sub_menu() ) { - return; - } - return add_submenu_page( - 'jetpack', - __( 'Search Settings', 'jetpack' ), - _x( 'Search', 'product name shown in menu', 'jetpack' ), - 'manage_options', - 'jetpack-search', - array( $this, 'render' ), - $this->get_link_offset() - ); - } - - /** - * Enqueue and localize page specific scripts - */ - public function page_admin_scripts() { - $this->load_admin_scripts(); - } - - /** - * Override render funtion - */ - public function render() { - $this->page_render(); - } - - /** - * Render Search setting elements - */ - public function page_render() { - ?> - <div id="jp-search-dashboard" class="jp-search-dashboard"> - <div class="hide-if-js"><?php esc_html_e( 'Your Search dashboard requires JavaScript to function properly.', 'jetpack' ); ?></div> - </div> - <?php - } - - /** - * Test whether we should show Search menu. - * - * @return {boolean} Show search sub menu or not. - */ - protected function should_add_sub_menu() { - return method_exists( 'Jetpack_Plan', 'supports' ) && Jetpack_Plan::supports( 'search' ); - } - - /** - * Place the Jetpack Search menu item at the bottom of the Jetpack submenu. - * - * @return int Menu offset. - */ - private function get_link_offset() { - global $submenu; - return count( $submenu['jetpack'] ); - } - - /** - * Enqueue admin styles. - */ - public function load_admin_styles() { - $this->load_admin_scripts(); - } - - /** - * Enqueue admin scripts. - */ - public function load_admin_scripts() { - \Jetpack_Admin_Page::load_wrapper_styles(); - - if ( ! ( new Status() )->is_offline_mode() && Jetpack::is_connection_ready() ) { - // Required for Analytics. - Automattic\Jetpack\Tracking::register_tracks_functions_scripts( true ); - } - - Assets::register_script( - 'jp-search-dashboard', - '_inc/build/search-dashboard.js', - JETPACK__PLUGIN_FILE, - array( - 'in_footer' => true, - 'textdomain' => 'jetpack', - ) - ); - Assets::enqueue_script( 'jp-search-dashboard' ); - - // Add objects to be passed to the initial state of the app. - // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280. - wp_add_inline_script( - 'jp-search-dashboard', - 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( \Jetpack_Redux_State_Helper::get_initial_state() ) ) . '"));', - 'before' - ); - } -} diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php index 86887667..d7b5675c 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php @@ -1,36 +1,55 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Main class file for Jetpack Admin pages. + * + * @package automattic/jetpack + */ use Automattic\Jetpack\Identity_Crisis; use Automattic\Jetpack\Redirect; use Automattic\Jetpack\Status; -// Shared logic between Jetpack admin pages +/** + * Shared logic between Jetpack admin pages. + */ abstract class Jetpack_Admin_Page { - // Add page specific actions given the page hook - abstract function add_page_actions( $hook ); + /** + * Add page specific actions given the page hook. + * + * @param string $hook Hook of current page. + */ + abstract public function add_page_actions( $hook ); - // Create a menu item for the page and returns the hook - abstract function get_page_hook(); + /** + * Create a menu item for the page and returns the hook. + * + * @return string|false Return value from WordPress's `add_menu_page()` or `add_submenu_page()`. + */ + abstract public function get_page_hook(); - // Enqueue and localize page specific scripts - abstract function page_admin_scripts(); + /** + * Enqueue and localize page specific scripts. + */ + abstract public function page_admin_scripts(); - // Render page specific HTML - abstract function page_render(); + /** + * Render page specific HTML + */ + abstract public function page_render(); /** * Should we block the page rendering because the site is in IDC? * * @var bool */ - static $block_page_rendering_for_idc; + public static $block_page_rendering_for_idc; /** * Function called after admin_styles to load any additional needed styles. * * @since 4.3.0 */ - function additional_styles() {} + public function additional_styles() {} /** * The constructor. @@ -52,7 +71,10 @@ abstract class Jetpack_Admin_Page { ); } - function add_actions() { + /** + * Add common page actions and attach page-specific actions. + */ + public function add_actions() { $is_offline_mode = ( new Status() )->is_offline_mode(); // If user is not an admin and site is in Offline Mode or not connected yet then don't do anything. @@ -70,7 +92,7 @@ abstract class Jetpack_Admin_Page { return; } - // Initialize menu item for the page in the admin + // Initialize menu item for the page in the admin. $hook = $this->get_page_hook(); // Attach hooks common to all Jetpack admin pages based on the created hook. @@ -116,38 +138,51 @@ abstract class Jetpack_Admin_Page { } - // Render the page with a common top and bottom part, and page specific content - function render() { + /** + * Render the page with a common top and bottom part, and page specific content. + */ + public function render() { // We're in an IDC: we need a decision made before we show the UI again. if ( self::$block_page_rendering_for_idc ) { return; } - // Check if we are looking at the main dashboard - if ( isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) { + // Check if we are looking at the main dashboard. + if ( isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- View logic. $this->page_render(); return; } self::wrap_ui( array( $this, 'page_render' ) ); } - function admin_help() { + /** + * Load Help tab. + * + * @todo This may no longer be used. + */ + public function admin_help() { $this->jetpack->admin_help(); } - function admin_page_load() { - // This is big. For the moment, just call the existing one. + /** + * Call the existing admin page events. + */ + public function admin_page_load() { $this->jetpack->admin_page_load(); } - // Add page specific scripts and jetpack stats for all menu pages - function admin_scripts() { - $this->page_admin_scripts(); // Delegate to inheriting class + /** + * Add page specific scripts and jetpack stats for all menu pages. + */ + public function admin_scripts() { + $this->page_admin_scripts(); // Delegate to inheriting class. add_action( 'admin_footer', array( $this->jetpack, 'do_stats' ) ); } - // Enqueue the Jetpack admin stylesheet - function admin_styles() { + /** + * Enqueue the Jetpack admin stylesheet. + */ + public function admin_styles() { $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; wp_enqueue_style( 'jetpack-admin', plugins_url( "css/jetpack-admin{$min}.css", JETPACK__PLUGIN_FILE ), array( 'genericons' ), JETPACK__VERSION . '-20121016' ); @@ -162,7 +197,7 @@ abstract class Jetpack_Admin_Page { * * @return bool */ - function is_rest_api_enabled() { + public function is_rest_api_enabled() { return /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ apply_filters( 'rest_enabled', true ) && /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ @@ -174,11 +209,11 @@ abstract class Jetpack_Admin_Page { * * @since 4.4.0 * - * @param $page + * @param WP_Screen $page Current WP_Screen object. * * @return array */ - function check_plan_deactivate_modules( $page ) { + public function check_plan_deactivate_modules( $page ) { if ( ( new Status() )->is_offline_mode() || ! in_array( @@ -189,7 +224,8 @@ abstract class Jetpack_Admin_Page { 'jetpack_page_vaultpress', 'jetpack_page_stats', 'jetpack_page_akismet-key-config', - ) + ), + true ) ) { return false; @@ -231,7 +267,10 @@ abstract class Jetpack_Admin_Page { ); } - static function load_wrapper_styles() { + /** + * Enqueue inline wrapper styles for the main container. + */ + public static function load_wrapper_styles() { $rtl = is_rtl() ? '.rtl' : ''; wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin{$rtl}.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION ); wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min{$rtl}.css", JETPACK__PLUGIN_FILE ), array( 'wp-components' ), JETPACK__VERSION ); @@ -278,6 +317,14 @@ abstract class Jetpack_Admin_Page { wp_add_inline_style( 'dops-css', $custom_css ); } + /** + * Build header, content, and footer for admin page. + * + * @param string $callback Callback to produce the content of the page. The callback is responsible for any needed escaping. + * @param array $args Options for the wrapping. Also passed to the `jetpack_admin_pages_wrap_ui_after_callback` action. + * - is-wide: (bool) Set the "is-wide" class on the wrapper div, which increases the max width. Default false. + * - show-nav: (bool) Whether to show the navigation bar at the top of the page. Default true. + */ public static function wrap_ui( $callback, $args = array() ) { $defaults = array( 'is-wide' => false, @@ -356,7 +403,7 @@ abstract class Jetpack_Admin_Page { call_user_func( $callback ); $callback_ui = ob_get_contents(); ob_end_clean(); - echo $callback_ui; + echo $callback_ui;// phpcs:ignore WordPress.Security.EscapeOutput -- Callback is responsible for any needed escaping. ?> <!-- END OF CALLBACK --> @@ -417,6 +464,5 @@ abstract class Jetpack_Admin_Page { </div> </div> <?php - return; } } diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-landing-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-landing-page.php index 5c06c284..2c61bd5e 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-landing-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-landing-page.php @@ -1,3 +1,6 @@ <?php -// This is intentionally left empty as a stub because some sites were caching the require() -// @see https://github.com/Automattic/jetpack/issues/5091 +/** This is intentionally left empty as a stub because some sites were caching the require() + * + * @see https://github.com/Automattic/jetpack/issues/5091 + * @package automattic/jetpack + */ diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php index 1c76ea28..d550b508 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php @@ -1,43 +1,64 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Status; -include_once( 'class.jetpack-admin-page.php' ); +require_once __DIR__ . '/class.jetpack-admin-page.php'; require_once __DIR__ . '/class-jetpack-redux-state-helper.php'; -// Builds the landing page and its menu +/** + * Builds the landing page and its menu. + */ class Jetpack_React_Page extends Jetpack_Admin_Page { - + /** + * Show the landing page only when Jetpack is connected. + * + * @var bool + */ protected $dont_show_if_not_active = false; + /** + * Used for fallback when REST API is disabled. + * + * @var bool + */ protected $is_redirecting = false; - function get_page_hook() { - // Add the main admin Jetpack menu + /** + * Add the main admin Jetpack menu. + * + * @return string|false Return value from WordPress's `add_menu_page()`. + */ + public function get_page_hook() { return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div', 3 ); } - function add_page_actions( $hook ) { - /** This action is documented in class.jetpack.php */ + /** + * Add page action. + * + * @param string $hook Hook of current page. + * @return void + */ + public function add_page_actions( $hook ) { + /** This action is documented in class.jetpack-admin.php */ do_action( 'jetpack_admin_menu', $hook ); - if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] ) { - return; // No need to handle the fallback redirection if we are not on the Jetpack page + if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + return; // No need to handle the fallback redirection if we are not on the Jetpack page. } - // Adding a redirect meta tag if the REST API is disabled + // Adding a redirect meta tag if the REST API is disabled. if ( ! $this->is_rest_api_enabled() ) { $this->is_redirecting = true; add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) ); } - // Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled + // Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled. add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) ); // If this is the first time the user is viewing the admin, don't show JITMs. // This filter is added just in time because this function is called on admin_menu - // and JITMs are initialized on admin_init + // and JITMs are initialized on admin_init. if ( Jetpack::is_connection_ready() && ! Jetpack_Options::get_option( 'first_admin_view', false ) ) { Jetpack_Options::update_option( 'first_admin_view', true ); add_filter( 'jetpack_just_in_time_msgs', '__return_false' ); @@ -51,7 +72,7 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { * * @since 4.3.0 */ - function jetpack_add_dashboard_sub_nav_item() { + public function jetpack_add_dashboard_sub_nav_item() { if ( ( new Status() )->is_offline_mode() || Jetpack::is_connection_ready() ) { add_submenu_page( 'jetpack', __( 'Dashboard', 'jetpack' ), __( 'Dashboard', 'jetpack' ), 'jetpack_admin_page', 'jetpack#/dashboard', '__return_null' ); remove_submenu_page( 'jetpack', 'jetpack' ); @@ -130,17 +151,27 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { * @since 4.3.0 * @since 9.7.0 If Connection does not have an owner, restrict it to admins */ - function jetpack_add_settings_sub_nav_item() { + public function jetpack_add_settings_sub_nav_item() { if ( $this->can_access_settings() ) { add_submenu_page( 'jetpack', __( 'Settings', 'jetpack' ), __( 'Settings', 'jetpack' ), 'jetpack_admin_page', 'jetpack#/settings', '__return_null' ); } } - function add_fallback_head_meta() { + /** + * Fallback redirect meta tag if the REST API is disabled. + * + * @return void + */ + public function add_fallback_head_meta() { echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">'; } - function add_noscript_head_meta() { + /** + * Fallback meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled. + * + * @return void + */ + public function add_noscript_head_meta() { echo '<noscript>'; $this->add_fallback_head_meta(); echo '</noscript>'; @@ -153,33 +184,36 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { * @param array $menu_order Menu order. * @return array */ - function jetpack_menu_order( $menu_order ) { + public function jetpack_menu_order( $menu_order ) { _deprecated_function( __METHOD__, 'jetpack-9.2' ); return $menu_order; } - function page_render() { - /** This action is already documented in views/admin/admin-page.php */ + /** + * Add action to render page specific HTML. + * + * @return void + */ + public function page_render() { + /** This action is already documented in class.jetpack-admin-page.php */ do_action( 'jetpack_notices' ); - // Try fetching by patch - $static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' ); + // Fetch static.html. + $static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' ); //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, Not fetching a remote file. if ( false === $static_html ) { - // If we still have nothing, display an error + // If we still have nothing, display an error. echo '<p>'; esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' ); - echo '<code>pnpm run distclean && pnpx jetpack build plugins/jetpack</code>'; + echo '<code>pnpm run distclean && pnpm jetpack build plugins/jetpack</code>'; echo '</p>'; } else { - - // We got the static.html so let's display it - echo $static_html; + // We got the static.html so let's display it. + echo $static_html; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } - /** * Allow robust deep links to React. * @@ -202,20 +236,26 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $target = sanitize_text_field( (string) $_GET['jp-react-redirect'] ); + $target = sanitize_text_field( wp_unslash( $_GET['jp-react-redirect'] ) ); if ( isset( $allowed_paths[ $target ] ) ) { wp_safe_redirect( $allowed_paths[ $target ] ); exit; } } - function additional_styles() { + /** + * Load styles for static page. + */ + public function additional_styles() { Jetpack_Admin_Page::load_wrapper_styles(); } - function page_admin_scripts() { + /** + * Load admin page scripts. + */ + public function page_admin_scripts() { if ( $this->is_redirecting ) { - return; // No need for scripts on a fallback page + return; // No need for scripts on a fallback page. } $status = new Status(); @@ -223,16 +263,18 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { $site_suffix = $status->get_site_suffix(); $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.asset.php'; $script_dependencies = array( 'wp-polyfill' ); + $version = JETPACK__VERSION; if ( file_exists( $script_deps_path ) ) { $asset_manifest = include $script_deps_path; $script_dependencies = $asset_manifest['dependencies']; + $version = $asset_manifest['version']; } wp_enqueue_script( 'react-plugin', plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ), $script_dependencies, - JETPACK__VERSION, + $version, true ); diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php index 551b9f71..b1f0dab6 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php @@ -1,21 +1,35 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -use Automattic\Jetpack\Tracking; use Automattic\Jetpack\Assets; +use Automattic\Jetpack\Tracking; -include_once( 'class.jetpack-admin-page.php' ); -include_once( JETPACK__PLUGIN_DIR . 'class.jetpack-modules-list-table.php' ); +require_once __DIR__ . '/class.jetpack-admin-page.php'; +require_once JETPACK__PLUGIN_DIR . 'class.jetpack-modules-list-table.php'; -// Builds the settings page and its menu +/** + * Builds the settings page and its menu + */ class Jetpack_Settings_Page extends Jetpack_Admin_Page { - // Show the settings page only when Jetpack is connected or in dev mode + /** + * Show the settings page only when Jetpack is connected or in dev mode. + * + * @var boolean + */ protected $dont_show_if_not_active = true; - function add_page_actions( $hook ) {} + /** + * Add page action. + * + * @param string $hook Hook of current page. + * @return void + */ + public function add_page_actions( $hook ) {} //phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Adds the Settings sub menu - function get_page_hook() { + /** + * Adds the Settings sub menu. + */ + public function get_page_hook() { return add_submenu_page( null, __( 'Jetpack Settings', 'jetpack' ), @@ -26,14 +40,16 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { ); } - // Renders the module list table where you can use bulk action or row - // actions to activate/deactivate and configure modules - function page_render() { - $list_table = new Jetpack_Modules_List_Table; + /** + * Renders the module list table where you can use bulk action or row + * actions to activate/deactivate and configure modules + */ + public function page_render() { + $list_table = new Jetpack_Modules_List_Table(); - // We have static.html so let's continue trying to fetch the others - $noscript_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-noscript-notice.html' ); - $rest_api_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-version-notice.html' ); + // We have static.html so let's continue trying to fetch the others. + $noscript_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-noscript-notice.html' ); //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, Not fetching a remote file. + $rest_api_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-version-notice.html' ); //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, Not fetching a remote file. $noscript_notice = str_replace( '#HEADER_TEXT#', @@ -58,9 +74,9 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { ); if ( ! $this->is_rest_api_enabled() ) { - echo $rest_api_notice; + echo $rest_api_notice; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - echo $noscript_notice; + echo $noscript_notice; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <div class="page-content configure"> @@ -92,15 +108,52 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { <?php $list_table->search_box( __( 'Search', 'jetpack' ), 'srch-term' ); ?> <p><?php esc_html_e( 'View:', 'jetpack' ); ?></p> <div class="button-group filter-active"> - <button type="button" class="button <?php if ( empty( $_GET['activated'] ) ) echo 'active'; ?>"><?php esc_html_e( 'All', 'jetpack' ); ?></button> - <button type="button" class="button <?php if ( ! empty( $_GET['activated'] ) && 'true' == $_GET['activated'] ) echo 'active'; ?>" data-filter-by="activated" data-filter-value="true"><?php esc_html_e( 'Active', 'jetpack' ); ?></button> - <button type="button" class="button <?php if ( ! empty( $_GET['activated'] ) && 'false' == $_GET['activated'] ) echo 'active'; ?>" data-filter-by="activated" data-filter-value="false"><?php esc_html_e( 'Inactive', 'jetpack' ); ?></button> + <button type="button" class="button + <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + if ( empty( $_GET['activated'] ) ) { + echo 'active'; + } + ?> + "> + <?php esc_html_e( 'All', 'jetpack' ); ?></button> + <button type="button" class="button + <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + if ( ! empty( $_GET['activated'] ) && 'true' === $_GET['activated'] ) { + echo 'active'; + } + ?> + " data-filter-by="activated" data-filter-value="true"><?php esc_html_e( 'Active', 'jetpack' ); ?></button> + <button type="button" class="button + <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + if ( ! empty( $_GET['activated'] ) && 'false' === $_GET['activated'] ) { + echo 'active'; + } + ?> + " data-filter-by="activated" data-filter-value="false"><?php esc_html_e( 'Inactive', 'jetpack' ); ?></button> </div> <p><?php esc_html_e( 'Sort by:', 'jetpack' ); ?></p> <div class="button-group sort"> - <button type="button" class="button <?php if ( empty( $_GET['sort_by'] ) ) echo 'active'; ?>" data-sort-by="name"><?php esc_html_e( 'Alphabetical', 'jetpack' ); ?></button> - <button type="button" class="button <?php if ( ! empty( $_GET['sort_by'] ) && 'introduced' == $_GET['sort_by'] ) echo 'active'; ?>" data-sort-by="introduced" data-sort-order="reverse"><?php esc_html_e( 'Newest', 'jetpack' ); ?></button> - <button type="button" class="button <?php if ( ! empty( $_GET['sort_by'] ) && 'sort' == $_GET['sort_by'] ) echo 'active'; ?>" data-sort-by="sort"><?php esc_html_e( 'Popular', 'jetpack' ); ?></button> + <button type="button" class="button + <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + if ( empty( $_GET['sort_by'] ) ) { + echo 'active'; + } + ?> + " data-sort-by="name"><?php esc_html_e( 'Alphabetical', 'jetpack' ); ?></button> + <button type="button" class="button + <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + if ( ! empty( $_GET['sort_by'] ) && 'introduced' === $_GET['sort_by'] ) { + echo 'active'; + } + ?> + " data-sort-by="introduced" data-sort-order="reverse"><?php esc_html_e( 'Newest', 'jetpack' ); ?></button> + <button type="button" class="button + <?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is view logic. + if ( ! empty( $_GET['sort_by'] ) && 'sort' === $_GET['sort_by'] ) { + echo 'active'; + } + ?> + " data-sort-by="sort"><?php esc_html_e( 'Popular', 'jetpack' ); ?></button> </div> <p><?php esc_html_e( 'Show:', 'jetpack' ); ?></p> <?php $list_table->views(); ?> @@ -109,7 +162,7 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { </div> <div class="manage-left" style="width: 100%;"> <form class="jetpack-modules-list-table-form" onsubmit="return false;"> - <table class="<?php echo implode( ' ', $list_table->get_table_classes() ); ?>"> + <table class="<?php echo esc_attr( implode( ' ', $list_table->get_table_classes() ) ); ?>"> <tbody id="the-list"> <?php $list_table->display_rows_or_placeholder(); ?> </tbody> @@ -130,17 +183,20 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { * * @since 4.3.0 */ - function additional_styles() { + public function additional_styles() { Jetpack_Admin_Page::load_wrapper_styles(); } - // Javascript logic specific to the list table - function page_admin_scripts() { + /** + * Javascript logic specific to the list table + */ + public function page_admin_scripts() { wp_enqueue_script( 'jetpack-admin-js', Assets::get_file_url_for_environment( '_inc/build/jetpack-admin.min.js', '_inc/jetpack-admin.js' ), array( 'jquery' ), - JETPACK__VERSION + JETPACK__VERSION, + true ); } } diff --git a/plugins/jetpack/_inc/lib/class-jetpack-recommendations.php b/plugins/jetpack/_inc/lib/class-jetpack-recommendations.php index ffbbddf3..77dd3a32 100644 --- a/plugins/jetpack/_inc/lib/class-jetpack-recommendations.php +++ b/plugins/jetpack/_inc/lib/class-jetpack-recommendations.php @@ -7,8 +7,10 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Plugins_Installer; use Automattic\Jetpack\Status; use Automattic\Jetpack\Status\Host; +use Automattic\Jetpack\Tracking; /** * Contains utilities related to the Jetpack Recommendations. @@ -20,6 +22,22 @@ use Automattic\Jetpack\Status\Host; * Jetpack_Recommendations class */ class Jetpack_Recommendations { + + const PUBLICIZE_RECOMMENDATION = 'publicize'; + const SECURITY_PLAN_RECOMMENDATION = 'security-plan'; + const ANTI_SPAM_RECOMMENDATION = 'anti-spam'; + const VIDEOPRESS_RECOMMENDATION = 'videopress'; + + const CONDITIONAL_RECOMMENDATIONS_OPTION = 'recommendations_conditional'; + const CONDITIONAL_RECOMMENDATIONS = array( + self::PUBLICIZE_RECOMMENDATION, + self::SECURITY_PLAN_RECOMMENDATION, + self::ANTI_SPAM_RECOMMENDATION, + self::VIDEOPRESS_RECOMMENDATION, + ); + + const VIDEOPRESS_TIMED_ACTION = 'jetpack_recommend_videopress'; + /** * Returns a boolean indicating if the Jetpack Recommendations are enabled. * @@ -98,6 +116,278 @@ class Jetpack_Recommendations { } /** + * Set up actions to monitor for things that trigger a recommendation. + * + * @return false|void + */ + public static function init_conditional_recommendation_actions() { + // Check to make sure that recommendations are enabled. + if ( ! self::is_enabled() ) { + return false; + } + + // Monitor for the publishing of a new post. + add_action( 'transition_post_status', array( get_called_class(), 'post_transition' ), 10, 3 ); + add_action( 'jetpack_activate_module', array( get_called_class(), 'jetpack_module_activated' ), 10, 2 ); + + // Monitor for activating a new plugin. + add_action( 'activated_plugin', array( get_called_class(), 'plugin_activated' ), 10 ); + + // Monitor for the addition of a new comment. + add_action( 'comment_post', array( get_called_class(), 'comment_added' ), 10, 3 ); + + // Monitor for Jetpack connection success. + add_action( 'jetpack_authorize_ending_authorized', array( get_called_class(), 'jetpack_connected' ) ); + add_action( self::VIDEOPRESS_TIMED_ACTION, array( get_called_class(), 'recommend_videopress' ) ); + } + + /** + * Check when Jetpack modules are activated if some recommendations should be skipped. + * + * @param string $module Name of the module activated. + * @param bool $success Whether the module activation was successful. + */ + public static function jetpack_module_activated( $module, $success ) { + if ( 'publicize' === $module && $success ) { + self::disable_conditional_recommendation( self::PUBLICIZE_RECOMMENDATION ); + } elseif ( 'videopress' === $module && $success ) { + // If VideoPress is enabled and a recommendation for it is scheduled, cancel that recommendation. + $recommendation_timestamp = wp_next_scheduled( self::VIDEOPRESS_TIMED_ACTION ); + if ( false !== $recommendation_timestamp ) { + wp_unschedule_event( $recommendation_timestamp, self::VIDEOPRESS_TIMED_ACTION ); + } + } + } + + /** + * Hook for transition_post_status that checks for the publishing of a new post. + * Used to enable the publicize recommendation. + * + * @param string $new_status new status of post. + * @param string $old_status old status of post. + * @param WP_Post $post the post object being updated. + */ + public static function post_transition( $new_status, $old_status, $post ) { + // Check for condition when post has been published. + if ( 'post' === $post->post_type && 'publish' === $new_status && 'publish' !== $old_status && ! Jetpack::is_module_active( 'publicize' ) ) { + // Set the publicize recommendation to have met criteria to be shown. + self::enable_conditional_recommendation( self::PUBLICIZE_RECOMMENDATION ); + } + } + + /** + * Runs when a plugin gets activated + * + * @param string $plugin Path to the plugins file relative to the plugins directory. + */ + public static function plugin_activated( $plugin ) { + // If the plugin is in this list, don't enable the recommendation. + $plugin_whitelist = array( + 'jetpack.php', + 'akismet.php', + 'creative-mail.php', + 'jetpack-backup.php', + 'jetpack-boost.php', + 'crowdsignal.php', + 'vaultpress.php', + 'woocommerce.php', + ); + + $path_parts = explode( '/', $plugin ); + $plugin_file = $path_parts ? array_pop( $path_parts ) : $plugin; + + if ( ! in_array( $plugin_file, $plugin_whitelist, true ) ) { + $products = array_column( Jetpack_Plan::get_products(), 'product_slug' ); + $has_anti_spam_product = count( array_intersect( array( 'jetpack_anti_spam', 'jetpack_anti_spam_monthly' ), $products ) ) > 0; + $has_anti_spam = is_plugin_active( 'akismet/akismet.php' ) || Jetpack_Plan::supports( 'antispam' ) || $has_anti_spam_product; + + // Check the backup state. + $rewind_state = get_transient( 'jetpack_rewind_state' ); + $has_backup = $rewind_state && in_array( $rewind_state->state, array( 'awaiting_credentials', 'provisioning', 'active' ), true ); + + // Check for a plan or product that enables scan. + $plan_supports_scan = Jetpack_Plan::supports( 'scan' ); + $has_scan_product = count( array_intersect( array( 'jetpack_scan', 'jetpack_scan_monthly' ), $products ) ) > 0; + $has_scan = $plan_supports_scan || $has_scan_product; + + if ( ! $has_scan || ! $has_backup || ! $has_anti_spam ) { + self::enable_conditional_recommendation( self::SECURITY_PLAN_RECOMMENDATION ); + } + } + } + + /** + * Runs when a new comment is added. + * + * @param integer $comment_id The ID of the comment that was added. + * @param bool $comment_approved Whether or not the comment is approved. + * @param array $commentdata Comment data. + */ + public static function comment_added( $comment_id, $comment_approved, $commentdata ) { + if ( self::is_conditional_recommendation_enabled( self::ANTI_SPAM_RECOMMENDATION ) ) { + return; + } + + if ( Plugins_Installer::is_plugin_active( 'akismet/akismet.php' ) ) { + return; + } + + // The site has anti-spam features already. + $site_products = array_column( Jetpack_Plan::get_products(), 'product_slug' ); + $has_anti_spam_product = count( array_intersect( array( 'jetpack_anti_spam', 'jetpack_anti_spam_monthly' ), $site_products ) ) > 0; + + if ( Jetpack_Plan::supports( 'antispam' ) || $has_anti_spam_product ) { + return; + } + + if ( isset( $commentdata['comment_post_ID'] ) ) { + $post_id = $commentdata['comment_post_ID']; + } else { + $comment = get_comment( $comment_id ); + $post_id = $comment->comment_post_ID; + } + $comment_count = get_comments_number( $post_id ); + + if ( intval( $comment_count ) >= 5 ) { + self::enable_conditional_recommendation( self::ANTI_SPAM_RECOMMENDATION ); + } + } + + /** + * Runs after a successful connection is made. + */ + public static function jetpack_connected() { + // Schedule a recommendation for VideoPress in 2 weeks. + if ( false === wp_next_scheduled( self::VIDEOPRESS_TIMED_ACTION ) ) { + $date = new DateTime(); + $date->add( new DateInterval( 'P14D' ) ); + wp_schedule_single_event( $date->getTimestamp(), self::VIDEOPRESS_TIMED_ACTION ); + } + } + + /** + * Enable a recommendation for VideoPress. + */ + public static function recommend_videopress() { + // Check to see if the VideoPress recommendation is already enabled. + if ( self::is_conditional_recommendation_enabled( self::VIDEOPRESS_RECOMMENDATION ) ) { + return; + } + + $site_plan = Jetpack_Plan::get(); + $site_products = array_column( Jetpack_Plan::get_products(), 'product_slug' ); + + if ( self::should_recommend_videopress( $site_plan, $site_products ) ) { + self::enable_conditional_recommendation( self::VIDEOPRESS_RECOMMENDATION ); + } + } + + /** + * Should we provide a recommendation for videopress? + * This method exists to facilitate unit testing + * + * @param array $site_plan A representation of the site's plan. + * @param array $site_products An array of product slugs. + * @return boolean + */ + public static function should_recommend_videopress( $site_plan, $site_products ) { + // Does the site have the VideoPress module enabled? + if ( Jetpack::is_module_active( 'videopress' ) ) { + return false; + } + + // Does the site plan have upgraded videopress features? + // For now, this just checks to see if the site has a free plan. + // Jetpack_Plan::supports('videopress') returns true for all plans, since there is a free tier. + $is_free_plan = 'free' === $site_plan['class']; + if ( ! $is_free_plan ) { + return false; + } + + // Does this site already have a VideoPress product? + $has_videopress_product = count( array_intersect( array( 'jetpack_videopress', 'jetpack_videopress_monthly' ), $site_products ) ) > 0; + if ( $has_videopress_product ) { + return false; + } + + return true; + } + + /** + * Enable a recommendation. + * + * @param string $recommendation_name The name of the recommendation to enable. + * @return false|void + */ + public static function enable_conditional_recommendation( $recommendation_name ) { + if ( ! in_array( $recommendation_name, self::CONDITIONAL_RECOMMENDATIONS, true ) ) { + return false; + } + + $conditional_recommendations = Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() ); + if ( ! in_array( $recommendation_name, $conditional_recommendations, true ) ) { + array_push( $conditional_recommendations, $recommendation_name ); + Jetpack_Options::update_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, $conditional_recommendations ); + $tracking = new Tracking(); + $tracking->record_user_event( 'recommendations_conditional_recommendation_enabled', array( 'feature' => $recommendation_name ) ); + } + } + + /** + * Disable a recommendation. + * + * @param string $recommendation_name The name of the recommendation to disable. + * @return false|void + */ + public static function disable_conditional_recommendation( $recommendation_name ) { + if ( ! in_array( $recommendation_name, self::CONDITIONAL_RECOMMENDATIONS, true ) ) { + return false; + } + + $conditional_recommendations = Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() ); + $recommendation_index = array_search( $recommendation_name, $conditional_recommendations, true ); + + if ( false !== $recommendation_index ) { + array_splice( $conditional_recommendations, $recommendation_index, 1 ); + Jetpack_Options::update_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, $conditional_recommendations ); + } + } + + /** + * Check to see if a recommendation is enabled or not. + * + * @param string $recommendation_name The name of the recommendation to check for. + * @return bool + */ + public static function is_conditional_recommendation_enabled( $recommendation_name ) { + $conditional_recommendations = Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() ); + return in_array( $recommendation_name, $conditional_recommendations, true ); + } + + /** + * Gets data for all conditional recommendations. + * + * @return mixed + */ + public static function get_conditional_recommendations() { + return Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() ); + } + + /** + * Get an array of new conditional recommendations that have not been viewed. + * + * @return array + */ + public static function get_new_conditional_recommendations() { + $conditional_recommendations = self::get_conditional_recommendations(); + $recommendations_data = Jetpack_Options::get_option( 'recommendations_data', array() ); + $viewed_recommendations = isset( $recommendations_data['viewedRecommendations'] ) ? $recommendations_data['viewedRecommendations'] : array(); + + // array_diff returns a keyed array - reduce to unique values. + return array_unique( array_values( array_diff( $conditional_recommendations, $viewed_recommendations ) ) ); + } + + /** * Initializes the Recommendations step according to the Setup Wizard state. */ private static function initialize_jetpack_recommendations() { diff --git a/plugins/jetpack/_inc/lib/class.color.php b/plugins/jetpack/_inc/lib/class.color.php index 70930659..5a919af7 100644 --- a/plugins/jetpack/_inc/lib/class.color.php +++ b/plugins/jetpack/_inc/lib/class.color.php @@ -1,4 +1,4 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * Color utility and conversion * @@ -10,20 +10,29 @@ * @author Harold Asbridge <hasbridge@gmail.com> * @author Matt Wiebe <wiebe@automattic.com> * @license https://www.opensource.org/licenses/MIT + * + * @package automattic/jetpack */ +// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + +/** + * Color utilities + */ class Jetpack_Color { /** - * @var int + * Color code (later array or string, depending on type) + * + * @var int|array|string */ protected $color = 0; /** * Initialize object * - * @param string|array $color A color of the type $type - * @param string $type The type of color we will construct from. - * One of hex (default), rgb, hsl, int + * @param string|array $color A color of the type $type. + * @param string $type The type of color we will construct from. + * One of hex (default), rgb, hsl, int. */ public function __construct( $color = null, $type = 'hex' ) { if ( $color ) { @@ -32,13 +41,13 @@ class Jetpack_Color { $this->fromHex( $color ); break; case 'rgb': - if ( is_array( $color ) && count( $color ) == 3 ) { + if ( is_array( $color ) && count( $color ) === 3 ) { list( $r, $g, $b ) = array_values( $color ); $this->fromRgbInt( $r, $g, $b ); } break; case 'hsl': - if ( is_array( $color ) && count( $color ) == 3 ) { + if ( is_array( $color ) && count( $color ) === 3 ) { list( $h, $s, $l ) = array_values( $color ); $this->fromHsl( $h, $s, $l ); } @@ -56,55 +65,44 @@ class Jetpack_Color { /** * Init color from hex value * - * @param string $hexValue - * - * @return Jetpack_Color - */ - public function fromHex($hexValue) { - $hexValue = str_replace( '#', '', $hexValue ); - // handle short hex codes like #fff - if ( 3 === strlen( $hexValue ) ) { - $short = $hexValue; - $i = 0; - $hexValue = ''; - while ( $i < 3 ) { - $chunk = substr($short, $i, 1 ); - $hexValue .= $chunk . $chunk; - $i++; - } - } - $intValue = hexdec( $hexValue ); - - if ( $intValue < 0 || $intValue > 16777215 ) { - throw new RangeException( $hexValue . " out of valid color code range" ); + * @param string $hex_value Color hex value. + * + * @return $this + * @throws RangeException Invalid color code range error. + */ + public function fromHex( $hex_value ) { + $hex_value = str_replace( '#', '', $hex_value ); + // handle short hex codes like #fff. + if ( 3 === strlen( $hex_value ) ) { + $hex_value = $hex_value[0] . $hex_value[0] . $hex_value[1] . $hex_value[1] . $hex_value[2] . $hex_value[2]; } - - $this->color = $intValue; - - return $this; + return $this->fromInt( hexdec( $hex_value ) ); } /** * Init color from integer RGB values * - * @param int $red - * @param int $green - * @param int $blue + * @param int $red Red color code. + * @param int $green Green color code. + * @param int $blue Blue color code. * - * @return Jetpack_Color + * @return $this + * @throws RangeException Invalid color code range error. */ - public function fromRgbInt($red, $green, $blue) - { - if ( $red < 0 || $red > 255 ) - throw new RangeException( "Red value " . $red . " out of valid color code range" ); + public function fromRgbInt( $red, $green, $blue ) { + if ( $red < 0 || $red > 255 ) { + throw new RangeException( 'Red value ' . $red . ' out of valid color code range' ); + } - if ( $green < 0 || $green > 255 ) - throw new RangeException( "Green value " . $green . " out of valid color code range" ); + if ( $green < 0 || $green > 255 ) { + throw new RangeException( 'Green value ' . $green . ' out of valid color code range' ); + } - if ( $blue < 0 || $blue > 255 ) - throw new RangeException( "Blue value " . $blue . " out of valid color code range" ); + if ( $blue < 0 || $blue > 255 ) { + throw new RangeException( 'Blue value ' . $blue . ' out of valid color code range' ); + } - $this->color = (int)(($red << 16) + ($green << 8) + $blue); + $this->color = (int) ( ( $red << 16 ) + ( $green << 8 ) + $blue ); return $this; } @@ -112,36 +110,40 @@ class Jetpack_Color { /** * Init color from hex RGB values * - * @param string $red - * @param string $green - * @param string $blue + * @param string $red Red color code. + * @param string $green Green color code. + * @param string $blue Blue color code. * - * @return Jetpack_Color + * @return $this */ - public function fromRgbHex($red, $green, $blue) - { - return $this->fromRgbInt(hexdec($red), hexdec($green), hexdec($blue)); + public function fromRgbHex( $red, $green, $blue ) { + return $this->fromRgbInt( hexdec( $red ), hexdec( $green ), hexdec( $blue ) ); } /** * Converts an HSL color value to RGB. Conversion formula * adapted from https://en.wikipedia.org/wiki/HSL_color_space. - * @param int $h Hue. [0-360] - * @param in $s Saturation [0, 100] - * @param int $l Lightness [0, 100] + * + * @param int $h Hue. [0-360]. + * @param int $s Saturation [0, 100]. + * @param int $l Lightness [0, 100]. */ public function fromHsl( $h, $s, $l ) { - $h /= 360; $s /= 100; $l /= 100; - - if ( $s == 0 ) { - $r = $g = $b = $l; // achromatic - } - else { + $h /= 360; + $s /= 100; + $l /= 100; + + if ( 0 === $s ) { + // achromatic. + $r = $l; + $g = $l; + $b = $l; + } else { $q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s; $p = 2 * $l - $q; - $r = $this->hue2rgb( $p, $q, $h + 1/3 ); + $r = $this->hue2rgb( $p, $q, $h + 1 / 3 ); $g = $this->hue2rgb( $p, $q, $h ); - $b = $this->hue2rgb( $p, $q, $h - 1/3 ); + $b = $this->hue2rgb( $p, $q, $h - 1 / 3 ); } return $this->fromRgbInt( $r * 255, $g * 255, $b * 255 ); @@ -149,29 +151,44 @@ class Jetpack_Color { /** * Helper function for Jetpack_Color::fromHsl() + * + * @param float $p Minimum of R/G/B [0, 1]. + * @param float $q Maximum of R/G/B [0, 1]. + * @param float $t Adjusted hue [0, 1]. */ private function hue2rgb( $p, $q, $t ) { - if ( $t < 0 ) $t += 1; - if ( $t > 1 ) $t -= 1; - if ( $t < 1/6 ) return $p + ( $q - $p ) * 6 * $t; - if ( $t < 1/2 ) return $q; - if ( $t < 2/3 ) return $p + ( $q - $p ) * ( 2/3 - $t ) * 6; + if ( $t < 0 ) { + ++$t; + } + if ( $t > 1 ) { + --$t; + } + if ( $t < 1 / 6 ) { + return $p + ( $q - $p ) * 6 * $t; + } + if ( $t < 1 / 2 ) { + return $q; + } + if ( $t < 2 / 3 ) { + return $p + ( $q - $p ) * ( 2 / 3 - $t ) * 6; + } return $p; } /** * Init color from integer value * - * @param int $intValue + * @param int $int_value Color code. * - * @return Jetpack_Color + * @return $this + * @throws RangeException Invalid color code range error. */ - public function fromInt($intValue) - { - if ( $intValue < 0 || $intValue > 16777215 ) - throw new RangeException( $intValue . " out of valid color code range" ); + public function fromInt( $int_value ) { + if ( $int_value < 0 || $int_value > 16777215 ) { + throw new RangeException( $int_value . ' out of valid color code range' ); + } - $this->color = $intValue; + $this->color = $int_value; return $this; } @@ -181,9 +198,8 @@ class Jetpack_Color { * * @return string */ - public function toHex() - { - return str_pad(dechex($this->color), 6, '0', STR_PAD_LEFT); + public function toHex() { + return sprintf( '%06x', $this->color ); } /** @@ -191,12 +207,11 @@ class Jetpack_Color { * * @return array */ - public function toRgbInt() - { + public function toRgbInt() { return array( - 'red' => (int)(255 & ($this->color >> 16)), - 'green' => (int)(255 & ($this->color >> 8)), - 'blue' => (int)(255 & ($this->color)) + 'red' => (int) ( 255 & ( $this->color >> 16 ) ), + 'green' => (int) ( 255 & ( $this->color >> 8 ) ), + 'blue' => (int) ( 255 & ( $this->color ) ), ); } @@ -205,11 +220,10 @@ class Jetpack_Color { * * @return array */ - public function toRgbHex() - { + public function toRgbHex() { $r = array(); - foreach ($this->toRgbInt() as $item) { - $r[] = dechex($item); + foreach ( $this->toRgbInt() as $item ) { + $r[] = dechex( $item ); } return $r; } @@ -220,55 +234,54 @@ class Jetpack_Color { * * @return array */ - public function toHsvFloat() - { + public function toHsvFloat() { $rgb = $this->toRgbInt(); - $rgbMin = min($rgb); - $rgbMax = max($rgb); + $rgb_min = min( $rgb ); + $rgb_max = max( $rgb ); $hsv = array( - 'hue' => 0, - 'sat' => 0, - 'val' => $rgbMax + 'hue' => 0, + 'sat' => 0, + 'val' => $rgb_max, ); - // If v is 0, color is black - if ($hsv['val'] == 0) { + // If v is 0, color is black. + if ( 0 === $hsv['val'] ) { return $hsv; } - // Normalize RGB values to 1 - $rgb['red'] /= $hsv['val']; + // Normalize RGB values to 1. + $rgb['red'] /= $hsv['val']; $rgb['green'] /= $hsv['val']; - $rgb['blue'] /= $hsv['val']; - $rgbMin = min($rgb); - $rgbMax = max($rgb); + $rgb['blue'] /= $hsv['val']; + $rgb_min = min( $rgb ); + $rgb_max = max( $rgb ); - // Calculate saturation - $hsv['sat'] = $rgbMax - $rgbMin; - if ($hsv['sat'] == 0) { + // Calculate saturation. + $hsv['sat'] = $rgb_max - $rgb_min; + if ( 0 === $hsv['sat'] ) { $hsv['hue'] = 0; return $hsv; } - // Normalize saturation to 1 - $rgb['red'] = ($rgb['red'] - $rgbMin) / ($rgbMax - $rgbMin); - $rgb['green'] = ($rgb['green'] - $rgbMin) / ($rgbMax - $rgbMin); - $rgb['blue'] = ($rgb['blue'] - $rgbMin) / ($rgbMax - $rgbMin); - $rgbMin = min($rgb); - $rgbMax = max($rgb); - - // Calculate hue - if ($rgbMax == $rgb['red']) { - $hsv['hue'] = 0.0 + 60 * ($rgb['green'] - $rgb['blue']); - if ($hsv['hue'] < 0) { + // Normalize saturation to 1. + $rgb['red'] = ( $rgb['red'] - $rgb_min ) / ( $rgb_max - $rgb_min ); + $rgb['green'] = ( $rgb['green'] - $rgb_min ) / ( $rgb_max - $rgb_min ); + $rgb['blue'] = ( $rgb['blue'] - $rgb_min ) / ( $rgb_max - $rgb_min ); + $rgb_min = min( $rgb ); + $rgb_max = max( $rgb ); + + // Calculate hue. + if ( $rgb_max === $rgb['red'] ) { + $hsv['hue'] = 0.0 + 60 * ( $rgb['green'] - $rgb['blue'] ); + if ( $hsv['hue'] < 0 ) { $hsv['hue'] += 360; } - } else if ($rgbMax == $rgb['green']) { - $hsv['hue'] = 120 + (60 * ($rgb['blue'] - $rgb['red'])); + } elseif ( $rgb_max === $rgb['green'] ) { + $hsv['hue'] = 120 + ( 60 * ( $rgb['blue'] - $rgb['red'] ) ); } else { - $hsv['hue'] = 240 + (60 * ($rgb['red'] - $rgb['green'])); + $hsv['hue'] = 240 + ( 60 * ( $rgb['red'] - $rgb['green'] ) ); } return $hsv; @@ -278,42 +291,41 @@ class Jetpack_Color { * Get HSV values for color * (integer values from 0-255, fast but less accurate) * - * @return int + * @return array */ - public function toHsvInt() - { + public function toHsvInt() { $rgb = $this->toRgbInt(); - $rgbMin = min($rgb); - $rgbMax = max($rgb); + $rgb_min = min( $rgb ); + $rgb_max = max( $rgb ); $hsv = array( - 'hue' => 0, - 'sat' => 0, - 'val' => $rgbMax + 'hue' => 0, + 'sat' => 0, + 'val' => $rgb_max, ); - // If value is 0, color is black - if ($hsv['val'] == 0) { + // If value is 0, color is black. + if ( 0 === $hsv['val'] ) { return $hsv; } - // Calculate saturation - $hsv['sat'] = round(255 * ($rgbMax - $rgbMin) / $hsv['val']); - if ($hsv['sat'] == 0) { + // Calculate saturation. + $hsv['sat'] = round( 255 * ( $rgb_max - $rgb_min ) / $hsv['val'] ); + if ( 0 === $hsv['sat'] ) { $hsv['hue'] = 0; return $hsv; } - // Calculate hue - if ($rgbMax == $rgb['red']) { - $hsv['hue'] = round(0 + 43 * ($rgb['green'] - $rgb['blue']) / ($rgbMax - $rgbMin)); - } else if ($rgbMax == $rgb['green']) { - $hsv['hue'] = round(85 + 43 * ($rgb['blue'] - $rgb['red']) / ($rgbMax - $rgbMin)); + // Calculate hue. + if ( $rgb_max === $rgb['red'] ) { + $hsv['hue'] = round( 0 + 43 * ( $rgb['green'] - $rgb['blue'] ) / ( $rgb_max - $rgb_min ) ); + } elseif ( $rgb_max === $rgb['green'] ) { + $hsv['hue'] = round( 85 + 43 * ( $rgb['blue'] - $rgb['red'] ) / ( $rgb_max - $rgb_min ) ); } else { - $hsv['hue'] = round(171 + 43 * ($rgb['red'] - $rgb['green']) / ($rgbMax - $rgbMin)); + $hsv['hue'] = round( 171 + 43 * ( $rgb['red'] - $rgb['green'] ) / ( $rgb_max - $rgb_min ) ); } - if ($hsv['hue'] < 0) { + if ( $hsv['hue'] < 0 ) { $hsv['hue'] += 255; } @@ -326,19 +338,22 @@ class Jetpack_Color { * Assumes r, g, and b are contained in the set [0, 255] and * returns h in [0, 360], s in [0, 100], l in [0, 100] * - * @return Array The HSL representation + * @return Array The HSL representation */ public function toHsl() { list( $r, $g, $b ) = array_values( $this->toRgbInt() ); - $r /= 255; $g /= 255; $b /= 255; - $max = max( $r, $g, $b ); - $min = min( $r, $g, $b ); - $h = $s = $l = ( $max + $min ) / 2; - #var_dump( array( compact('max', 'min', 'r', 'g', 'b')) ); - if ( $max == $min ) { - $h = $s = 0; // achromatic - } - else { + $r /= 255; + $g /= 255; + $b /= 255; + $max = max( $r, $g, $b ); + $min = min( $r, $g, $b ); + $l = ( $max + $min ) / 2; + + if ( $max === $min ) { + // achromatic. + $s = 0; + $h = 0; + } else { $d = $max - $min; $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min ); switch ( $max ) { @@ -360,34 +375,36 @@ class Jetpack_Color { return compact( 'h', 's', 'l' ); } + /** + * From a color code to a string to be used in CSS declaration. + * + * @param string $type Color code type. + * @param int $alpha Transparency. + * + * @return string + */ public function toCSS( $type = 'hex', $alpha = 1 ) { switch ( $type ) { case 'hex': return $this->toString(); - break; case 'rgb': case 'rgba': list( $r, $g, $b ) = array_values( $this->toRgbInt() ); if ( is_numeric( $alpha ) && $alpha < 1 ) { return "rgba( {$r}, {$g}, {$b}, $alpha )"; - } - else { + } else { return "rgb( {$r}, {$g}, {$b} )"; } - break; case 'hsl': case 'hsla': list( $h, $s, $l ) = array_values( $this->toHsl() ); if ( is_numeric( $alpha ) && $alpha < 1 ) { return "hsla( {$h}, {$s}, {$l}, $alpha )"; - } - else { + } else { return "hsl( {$h}, {$s}, {$l} )"; } - break; default: return $this->toString(); - break; } } @@ -396,22 +413,20 @@ class Jetpack_Color { * * @return array */ - public function toXyz() - { + public function toXyz() { $rgb = $this->toRgbInt(); - // Normalize RGB values to 1 - + // Normalize RGB values to 1. $rgb_new = array(); - foreach ($rgb as $item) { + foreach ( $rgb as $item ) { $rgb_new[] = $item / 255; } $rgb = $rgb_new; $rgb_new = array(); - foreach ($rgb as $item) { - if ($item > 0.04045) { - $item = pow((($item + 0.055) / 1.055), 2.4); + foreach ( $rgb as $item ) { + if ( $item > 0.04045 ) { + $item = pow( ( ( $item + 0.055 ) / 1.055 ), 2.4 ); } else { $item = $item / 12.92; } @@ -419,11 +434,11 @@ class Jetpack_Color { } $rgb = $rgb_new; - // Observer. = 2°, Illuminant = D65 + // Observer. = 2°, Illuminant = D65. $xyz = array( - 'x' => ($rgb['red'] * 0.4124) + ($rgb['green'] * 0.3576) + ($rgb['blue'] * 0.1805), - 'y' => ($rgb['red'] * 0.2126) + ($rgb['green'] * 0.7152) + ($rgb['blue'] * 0.0722), - 'z' => ($rgb['red'] * 0.0193) + ($rgb['green'] * 0.1192) + ($rgb['blue'] * 0.9505) + 'x' => ( $rgb['red'] * 0.4124 ) + ( $rgb['green'] * 0.3576 ) + ( $rgb['blue'] * 0.1805 ), + 'y' => ( $rgb['red'] * 0.2126 ) + ( $rgb['green'] * 0.7152 ) + ( $rgb['blue'] * 0.0722 ), + 'z' => ( $rgb['red'] * 0.0193 ) + ( $rgb['green'] * 0.1192 ) + ( $rgb['blue'] * 0.9505 ), ); return $xyz; @@ -434,29 +449,28 @@ class Jetpack_Color { * * @return array */ - public function toLabCie() - { + public function toLabCie() { $xyz = $this->toXyz(); - //Ovserver = 2*, Iluminant=D65 + // Ovserver = 2*, Iluminant=D65. $xyz['x'] /= 95.047; $xyz['y'] /= 100; $xyz['z'] /= 108.883; $xyz_new = array(); - foreach ($xyz as $item) { - if ($item > 0.008856) { - $xyz_new[] = pow($item, 1/3); + foreach ( $xyz as $item ) { + if ( $item > 0.008856 ) { + $xyz_new[] = pow( $item, 1 / 3 ); } else { - $xyz_new[] = (7.787 * $item) + (16 / 116); + $xyz_new[] = ( 7.787 * $item ) + ( 16 / 116 ); } } $xyz = $xyz_new; $lab = array( - 'l' => (116 * $xyz['y']) - 16, - 'a' => 500 * ($xyz['x'] - $xyz['y']), - 'b' => 200 * ($xyz['y'] - $xyz['z']) + 'l' => ( 116 * $xyz['y'] ) - 16, + 'a' => 500 * ( $xyz['x'] - $xyz['y'] ), + 'b' => 200 * ( $xyz['y'] - $xyz['z'] ), ); return $lab; @@ -467,8 +481,7 @@ class Jetpack_Color { * * @return int */ - public function toInt() - { + public function toInt() { return $this->color; } @@ -477,8 +490,7 @@ class Jetpack_Color { * * @return string */ - public function __toString() - { + public function __toString() { return $this->toString(); } @@ -487,58 +499,60 @@ class Jetpack_Color { * * @return string */ - public function toString() - { + public function toString() { $str = $this->toHex(); - return strtoupper("#{$str}"); + return strtoupper( "#{$str}" ); } /** * Get the distance between this color and the given color * - * @param Jetpack_Color $color + * @param Jetpack_Color $color Color code. * * @return int */ - public function getDistanceRgbFrom(Jetpack_Color $color) - { + public function getDistanceRgbFrom( Jetpack_Color $color ) { $rgb1 = $this->toRgbInt(); $rgb2 = $color->toRgbInt(); - $rDiff = abs($rgb1['red'] - $rgb2['red']); - $gDiff = abs($rgb1['green'] - $rgb2['green']); - $bDiff = abs($rgb1['blue'] - $rgb2['blue']); + $r_diff = abs( $rgb1['red'] - $rgb2['red'] ); + $g_diff = abs( $rgb1['green'] - $rgb2['green'] ); + $b_diff = abs( $rgb1['blue'] - $rgb2['blue'] ); - // Sum of RGB differences - $diff = $rDiff + $gDiff + $bDiff; + // Sum of RGB differences. + $diff = $r_diff + $g_diff + $b_diff; return $diff; } /** * Get distance from the given color using the Delta E method * - * @param Jetpack_Color $color + * @param Jetpack_Color $color Color code. * * @return float */ - public function getDistanceLabFrom(Jetpack_Color $color) - { + public function getDistanceLabFrom( Jetpack_Color $color ) { $lab1 = $this->toLabCie(); $lab2 = $color->toLabCie(); - $lDiff = abs($lab2['l'] - $lab1['l']); - $aDiff = abs($lab2['a'] - $lab1['a']); - $bDiff = abs($lab2['b'] - $lab1['b']); + $l_diff = abs( $lab2['l'] - $lab1['l'] ); + $a_diff = abs( $lab2['a'] - $lab1['a'] ); + $b_diff = abs( $lab2['b'] - $lab1['b'] ); - $delta = sqrt($lDiff + $aDiff + $bDiff); + $delta = sqrt( $l_diff + $a_diff + $b_diff ); return $delta; } + /** + * Calculate luminosity. + * + * @return float + */ public function toLuminosity() { $lum = array(); - foreach( $this->toRgbInt() as $slot => $value ) { - $chan = $value / 255; + foreach ( $this->toRgbInt() as $slot => $value ) { + $chan = $value / 255; $lum[ $slot ] = ( $chan <= 0.03928 ) ? $chan / 12.92 : pow( ( ( $chan + 0.055 ) / 1.055 ), 2.4 ); } return 0.2126 * $lum['red'] + 0.7152 * $lum['green'] + 0.0722 * $lum['blue']; @@ -548,36 +562,47 @@ class Jetpack_Color { * Get distance between colors using luminance. * Should be more than 5 for readable contrast * - * @param Jetpack_Color $color Another color + * @param Jetpack_Color $color Another color. * @return float */ public function getDistanceLuminosityFrom( Jetpack_Color $color ) { - $L1 = $this->toLuminosity(); - $L2 = $color->toLuminosity(); - if ( $L1 > $L2 ) { - return ( $L1 + 0.05 ) / ( $L2 + 0.05 ); - } - else{ - return ( $L2 + 0.05 ) / ( $L1 + 0.05 ); + $l1 = $this->toLuminosity(); + $l2 = $color->toLuminosity(); + if ( $l1 > $l2 ) { + return ( $l1 + 0.05 ) / ( $l2 + 0.05 ); + } else { + return ( $l2 + 0.05 ) / ( $l1 + 0.05 ); } } + /** + * Get maximum contrast color. + * + * @return $this + */ public function getMaxContrastColor() { - $withBlack = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#000') ); - $withWhite = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#fff') ); - $color = new Jetpack_Color; - $hex = ( $withBlack >= $withWhite ) ? '#000000' : '#ffffff'; + $with_black = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#000' ) ); + $with_white = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#fff' ) ); + $color = new Jetpack_Color(); + $hex = ( $with_black >= $with_white ) ? '#000000' : '#ffffff'; return $color->fromHex( $hex ); } + /** + * Get grayscale contrasting color. + * + * @param bool|int $contrast Contrast. + * + * @return $this + */ public function getGrayscaleContrastingColor( $contrast = false ) { if ( ! $contrast ) { return $this->getMaxContrastColor(); } - // don't allow less than 5 + // don't allow less than 5. $target_contrast = ( $contrast < 5 ) ? 5 : $contrast; - $color = $this->getMaxContrastColor(); - $contrast = $color->getDistanceLuminosityFrom( $this ); + $color = $this->getMaxContrastColor(); + $contrast = $color->getDistanceLuminosityFrom( $this ); // if current max contrast is less than the target contrast, we had wishful thinking. if ( $contrast <= $target_contrast ) { @@ -586,7 +611,7 @@ class Jetpack_Color { $incr = ( '#000000' === $color->toString() ) ? 1 : -1; while ( $contrast > $target_contrast ) { - $color = $color->incrementLightness( $incr ); + $color = $color->incrementLightness( $incr ); $contrast = $color->getDistanceLuminosityFrom( $this ); } @@ -595,7 +620,8 @@ class Jetpack_Color { /** * Gets a readable contrasting color. $this is assumed to be the text and $color the background color. - * @param object $bg_color A Color object that will be compared against $this + * + * @param object $bg_color A Color object that will be compared against $this. * @param integer $min_contrast The minimum contrast to achieve, if possible. * @return object A Color object, an increased contrast $this compared against $bg_color */ @@ -605,17 +631,17 @@ class Jetpack_Color { } // you shouldn't use less than 5, but you might want to. $target_contrast = $min_contrast; - // working things - $contrast = $bg_color->getDistanceLuminosityFrom( $this ); + // working things. + $contrast = $bg_color->getDistanceLuminosityFrom( $this ); $max_contrast_color = $bg_color->getMaxContrastColor(); - $max_contrast = $max_contrast_color->getDistanceLuminosityFrom( $bg_color ); + $max_contrast = $max_contrast_color->getDistanceLuminosityFrom( $bg_color ); // if current max contrast is less than the target contrast, we had wishful thinking. - // still, go max + // still, go max. if ( $max_contrast <= $target_contrast ) { return $max_contrast_color; } - // or, we might already have sufficient contrast + // or, we might already have sufficient contrast. if ( $contrast >= $target_contrast ) { return $this; } @@ -625,7 +651,7 @@ class Jetpack_Color { $this->incrementLightness( $incr ); $contrast = $bg_color->getDistanceLuminosityFrom( $this ); // infininite loop prevention: you never know. - if ( $this->color === 0 || $this->color === 16777215 ) { + if ( 0 === $this->color || 16777215 === $this->color ) { break; } } @@ -636,18 +662,17 @@ class Jetpack_Color { /** * Detect if color is grayscale * - * @param int @threshold + * @param int $threshold Max difference between colors. * * @return bool */ - public function isGrayscale($threshold = 16) - { + public function isGrayscale( $threshold = 16 ) { $rgb = $this->toRgbInt(); - // Get min and max rgb values, then difference between them - $rgbMin = min($rgb); - $rgbMax = max($rgb); - $diff = $rgbMax - $rgbMin; + // Get min and max rgb values, then difference between them. + $rgb_min = min( $rgb ); + $rgb_max = max( $rgb ); + $diff = $rgb_max - $rgb_min; return $diff < $threshold; } @@ -655,38 +680,58 @@ class Jetpack_Color { /** * Get the closest matching color from the given array of colors * - * @param array $colors array of integers or Jetpack_Color objects + * @param array $colors array of integers or Jetpack_Color objects. * * @return mixed the array key of the matched color */ - public function getClosestMatch(array $colors) - { - $matchDist = 10000; - $matchKey = null; - foreach($colors as $key => $color) { - if (false === ($color instanceof Jetpack_Color)) { - $c = new Jetpack_Color($color); + public function getClosestMatch( array $colors ) { + $match_dist = 10000; + $match_key = null; + foreach ( $colors as $key => $color ) { + if ( false === ( $color instanceof Jetpack_Color ) ) { + $c = new Jetpack_Color( $color ); } - $dist = $this->getDistanceLabFrom($c); - if ($dist < $matchDist) { - $matchDist = $dist; - $matchKey = $key; + $dist = $this->getDistanceLabFrom( $c ); + if ( $dist < $match_dist ) { + $match_dist = $dist; + $match_key = $key; } } - return $matchKey; + return $match_key; } /* TRANSFORMS */ + /** + * Transform -- Darken color. + * + * @param int $amount Amount. Default to 5. + * + * @return $this + */ public function darken( $amount = 5 ) { return $this->incrementLightness( - $amount ); } + /** + * Transform -- Lighten color. + * + * @param int $amount Amount. Default to 5. + * + * @return $this + */ public function lighten( $amount = 5 ) { return $this->incrementLightness( $amount ); } + /** + * Transform -- Increment lightness. + * + * @param int $amount Amount. + * + * @return $this + */ public function incrementLightness( $amount ) { $hsl = $this->toHsl(); @@ -695,19 +740,44 @@ class Jetpack_Color { $l = isset( $hsl['l'] ) ? $hsl['l'] : 0; $l += $amount; - if ( $l < 0 ) $l = 0; - if ( $l > 100 ) $l = 100; + if ( $l < 0 ) { + $l = 0; + } + if ( $l > 100 ) { + $l = 100; + } return $this->fromHsl( $h, $s, $l ); } + /** + * Transform -- Saturate color. + * + * @param int $amount Amount. Default to 15. + * + * @return $this + */ public function saturate( $amount = 15 ) { return $this->incrementSaturation( $amount ); } + /** + * Transform -- Desaturate color. + * + * @param int $amount Amount. Default to 15. + * + * @return $this + */ public function desaturate( $amount = 15 ) { return $this->incrementSaturation( - $amount ); } + /** + * Transform -- Increment saturation. + * + * @param int $amount Amount. + * + * @return $this + */ public function incrementSaturation( $amount ) { $hsl = $this->toHsl(); @@ -716,11 +786,20 @@ class Jetpack_Color { $l = isset( $hsl['l'] ) ? $hsl['l'] : 0; $s += $amount; - if ( $s < 0 ) $s = 0; - if ( $s > 100 ) $s = 100; + if ( $s < 0 ) { + $s = 0; + } + if ( $s > 100 ) { + $s = 100; + } return $this->fromHsl( $h, $s, $l ); } + /** + * Transform -- To grayscale. + * + * @return $this + */ public function toGrayscale() { $hsl = $this->toHsl(); @@ -731,30 +810,80 @@ class Jetpack_Color { return $this->fromHsl( $h, $s, $l ); } + /** + * Transform -- To the complementary color. + * + * The complement is the color on the opposite side of the color wheel, 180° away. + * + * @return $this + */ public function getComplement() { return $this->incrementHue( 180 ); } + /** + * Transform -- To an analogous color of the complement. + * + * @param int $step Pass `1` or `-1` to choose which direction around the color wheel. + * + * @return $this + */ public function getSplitComplement( $step = 1 ) { $incr = 180 + ( $step * 30 ); return $this->incrementHue( $incr ); } + /** + * Transform -- To an analogous color. + * + * Analogous colors are those adjacent on the color wheel, separated by 30°. + * + * @param int $step Pass `1` or `-1` to choose which direction around the color wheel. + * + * @return $this + */ public function getAnalog( $step = 1 ) { $incr = $step * 30; return $this->incrementHue( $incr ); } + /** + * Transform -- To a tetradic (rectangular) color. + * + * A rectangular color scheme uses a color, its complement, and the colors 60° from each. + * This transforms the color to its 60° "tetrad". + * + * @param int $step Pass `1` or `-1` to choose which direction around the color wheel. + * + * @return $this + */ public function getTetrad( $step = 1 ) { $incr = $step * 60; return $this->incrementHue( $incr ); } + /** + * Transform -- To a triadic color. + * + * A triadic color scheme uses three colors evenly spaced (120°) around the color wheel. + * This transforms the color to one of its triadic colors. + * + * @param int $step Pass `1` or `-1` to choose which direction around the color wheel. + * + * @return $this + */ public function getTriad( $step = 1 ) { $incr = $step * 120; return $this->incrementHue( $incr ); } + /** + * Transform -- Increment hue. + * + * @param int $amount Amount. + * + * @return $this + */ public function incrementHue( $amount ) { $hsl = $this->toHsl(); @@ -763,8 +892,10 @@ class Jetpack_Color { $l = isset( $hsl['l'] ) ? $hsl['l'] : 0; $h = ( $h + $amount ) % 360; - if ( $h < 0 ) $h = 360 - $h; + if ( $h < 0 ) { + $h += 360; + } return $this->fromHsl( $h, $s, $l ); } -} // class Jetpack_Color +} 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 342c85b2..35d91c3b 100644 --- a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php +++ b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php @@ -1,19 +1,18 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Register WP REST API endpoints for Jetpack. + * + * @package automattic/jetpack + */ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; 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\Plugins_Installer; use Automattic\Jetpack\Status\Host; - -/** - * Register WP REST API endpoints for Jetpack. - * - * @author Automattic - */ +use Automattic\Jetpack\Status\Visitor; /** * Disable direct access. @@ -31,25 +30,16 @@ 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 * * @since 4.3.0 */ class Jetpack_Core_Json_Api_Endpoints { - /** - * @var string Generic error message when user is not allowed to perform an action. + * Roles that can access Stats once they're granted access. * - * @deprecated 8.8.0 Use `REST_Connector::get_user_permissions_error_msg()` instead. - */ - public static $user_permissions_error_msg; - - /** - * @var array Roles that can access Stats once they're granted access. + * @var array */ public static $stats_roles; @@ -60,16 +50,14 @@ class Jetpack_Core_Json_Api_Endpoints { */ public static function register_endpoints() { - // Load API endpoint base classes + // Load API endpoint base classes. require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php'; - // Load API endpoints + // Load API endpoints. require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php'; 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 = 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() ) ); @@ -110,7 +98,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Test current connection status of Jetpack + // Test current connection status of Jetpack. register_rest_route( 'jetpack/v4', '/connection/test', @@ -152,7 +140,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Fetches a fresh connect URL + // Fetches a fresh connect URL. register_rest_route( 'jetpack/v4', '/connection/url', @@ -188,7 +176,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Disconnect/unlink user from WordPress.com servers + // Disconnect/unlink user from WordPress.com servers. register_rest_route( 'jetpack/v4', '/connection/user', @@ -199,7 +187,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Get current site data + // Get current site data. register_rest_route( 'jetpack/v4', '/site', @@ -210,7 +198,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Get current site data + // Get current site data. register_rest_route( 'jetpack/v4', '/site/features', @@ -242,7 +230,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Get current site benefits + // Get current site benefits. register_rest_route( 'jetpack/v4', '/site/benefits', @@ -264,7 +252,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Return all modules + // Return all modules. register_rest_route( 'jetpack/v4', '/module/all', @@ -275,7 +263,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Activate many modules + // Activate many modules. register_rest_route( 'jetpack/v4', '/module/all/active', @@ -303,7 +291,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Return a single module and update it when needed + // Return a single module and update it when needed. register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', @@ -314,7 +302,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Activate and deactivate a module + // Activate and deactivate a module. register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', @@ -333,7 +321,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Update a module + // Update a module. register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', @@ -365,7 +353,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Check if the API key for a specific service is valid or not + // 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', @@ -395,7 +383,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Update any Jetpack module option or setting + // Update any Jetpack module option or setting. register_rest_route( 'jetpack/v4', '/settings', @@ -407,7 +395,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Update a module + // Update a module. register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', @@ -419,7 +407,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Return all module settings + // Return all module settings. register_rest_route( 'jetpack/v4', '/settings/', @@ -430,7 +418,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Reset all Jetpack options + // Reset all Jetpack options. register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', @@ -441,7 +429,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Updates: get number of plugin updates available + // Updates: get number of plugin updates available. register_rest_route( 'jetpack/v4', '/updates/plugins', @@ -452,7 +440,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Dismiss Jetpack Notices + // Dismiss Jetpack Notices. register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', @@ -559,7 +547,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Site Verify: check if the site is verified, and a get verification token if not + // 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\-_]+)', @@ -580,7 +568,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - // Site Verify: tell a service to verify the site + // Site Verify: tell a service to verify the site. register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', @@ -680,105 +668,26 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - /* - * Get and update the last licensing error message. - */ register_rest_route( 'jetpack/v4', - '/licensing/error', + '/recommendations/conditional', 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_licensing_error', + 'callback' => __CLASS__ . '::get_conditional_recommendations', 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - 'args' => array( - 'error' => array( - 'required' => true, - 'type' => 'string', - 'validate_callback' => __CLASS__ . '::validate_string', - 'sanitize_callback' => 'sanitize_text_field', - ), - ), ), ) ); - // Return all module settings. + // Get site discount. 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', + '/site/discount', 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', - ), - ), - ), + 'callback' => __CLASS__ . '::get_site_discount', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', ) ); @@ -837,6 +746,17 @@ class Jetpack_Core_Json_Api_Endpoints { 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', ) ); + + // Get Jetpack introduction offers + register_rest_route( + 'jetpack/v4', + '/intro-offers', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_intro_offers', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); } /** @@ -908,7 +828,7 @@ class Jetpack_Core_Json_Api_Endpoints { array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ) ); @@ -952,7 +872,7 @@ class Jetpack_Core_Json_Api_Endpoints { array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ) ); @@ -970,6 +890,15 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Get conditional recommendations data. + * + * @return array Conditional recommendations data. + */ + public static function get_conditional_recommendations() { + return Jetpack_Recommendations::get_conditional_recommendations(); + } + + /** * Validate the recommendations data * * @param array $value Value to check received by request. @@ -1029,6 +958,11 @@ class Jetpack_Core_Json_Api_Endpoints { return Jetpack_Options::delete_option( 'purchase_token' ); } + /** + * Get list of Jetpack Plans. + * + * @param WP_REST_Request $request The request. + */ public static function get_plans( $request ) { $request = Client::wpcom_json_api_request_as_user( '/plans?_locale=' . get_user_locale(), @@ -1036,7 +970,7 @@ class Jetpack_Core_Json_Api_Endpoints { array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ) ); @@ -1045,7 +979,7 @@ class Jetpack_Core_Json_Api_Endpoints { if ( 200 === wp_remote_retrieve_response_code( $request ) ) { $data = $body; } else { - // something went wrong so we'll just return the response without caching + // something went wrong so we'll just return the response without caching. return $body; } @@ -1060,14 +994,14 @@ class Jetpack_Core_Json_Api_Endpoints { * * @return string|WP_Error A JSON object of wpcom products if the request was successful, or a WP_Error otherwise. */ - public static function get_products( $request ) { + public static function get_products( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $wpcom_request = Client::wpcom_json_api_request_as_user( '/products?_locale=' . get_user_locale() . '&type=jetpack', '2', array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ) ); @@ -1086,76 +1020,11 @@ class Jetpack_Core_Json_Api_Endpoints { } /** - * 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( - '/jetpack-licensing/user/licenses/counts', - '2', - array( - 'method' => 'GET', - 'headers' => array( - 'Content-Type' => 'application/json', - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), - ), - ) - ); - - $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 { - return new WP_Error( - 'failed_to_fetch_data', - esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), - array( 'status' => $response_code ) - ); - } - } - - /** - * Update the user-licenses activation notice dismissal data. - * - * @since 10.4.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. + * Send Survey details to WordPress.com. * - * @return array|WP_Error + * @param WP_REST_Request $request The request. */ - public static function update_licensing_activation_notice_dismiss( $request ) { - - if ( ! isset( $request['last_detached_count'] ) ) { - return new WP_Error( 'invalid_param', esc_html__( 'Missing parameter "last_detached_count".', 'jetpack' ), array( 'status' => 404 ) ); - } - - $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 ); - } - public static function submit_survey( $request ) { - $wpcom_request = Client::wpcom_json_api_request_as_user( '/marketing/survey', 'v2', @@ -1163,7 +1032,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json', - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ), $request->get_json_params() @@ -1173,7 +1042,7 @@ class Jetpack_Core_Json_Api_Endpoints { 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 + // something went wrong so we'll just return the response without caching. return $wpcom_request_body; } @@ -1214,10 +1083,10 @@ class Jetpack_Core_Json_Api_Endpoints { $csp4 = get_option( 'seed_csp4_settings_content', array() ); if ( ( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon ) - || Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok. - || ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok. - || ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok. - || ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) && isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok. + || Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual + || ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual + || ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual + || ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) && isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual /** * Allow plugins to mark a site as "under construction". * @@ -1254,6 +1123,11 @@ class Jetpack_Core_Json_Api_Endpoints { } } + /** + * Verify site with external service. + * + * @param WP_REST_Request $request The request. + */ public static function verify_site( $request ) { $xml = new Jetpack_IXR_Client( array( @@ -1299,7 +1173,7 @@ class Jetpack_Core_Json_Api_Endpoints { public static function dismiss_notice( $request ) { $notice = $request['notice']; - if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) { + if ( ! isset( $request['dismissed'] ) || true !== $request['dismissed'] ) { return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) ); } @@ -1332,7 +1206,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_jetpack_disconnect', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } @@ -1348,7 +1226,11 @@ class Jetpack_Core_Json_Api_Endpoints { 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_jetpack_connect', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } @@ -1366,7 +1248,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_unlink_user', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1381,7 +1267,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_manage_modules', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1396,7 +1286,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_configure_modules', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1411,7 +1305,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_view_admin', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1426,7 +1324,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_manage_settings', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1441,7 +1343,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_activate_plugins', REST_Connector::get_user_permissions_error_msg(), array( 'status' => 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() ) + ); } /** @@ -1454,7 +1360,11 @@ 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' => rest_authorization_required_code() ) ); + return new WP_Error( + 'invalid_user_permission_edit_others_posts', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1471,22 +1381,11 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_permission_manage_purchase_token', self::$user_permissions_error_msg, array( 'status' => rest_authorization_required_code() ) ); - } - - /** - * Verify that user can view and update user-licensing data. - * - * @return bool Whether the user is currently connected and they are the connection owner. - */ - 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() ) ); + return new WP_Error( + 'invalid_permission_manage_purchase_token', + REST_Connector::get_user_permissions_error_msg(), + array( 'status' => rest_authorization_required_code() ) + ); } /** @@ -1520,16 +1419,17 @@ class Jetpack_Core_Json_Api_Endpoints { * @return bool */ public static function view_jetpack_connection_test_check() { + // phpcs:disable WordPress.Security.NonceVerification.Recommended -- This is verifying the trusted caller via a shared private key and timestamp. if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'] ) ) { return false; } - $signature = base64_decode( $_GET['signature'] ); + $signature = base64_decode( wp_unslash( $_GET['signature'] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $signature_data = wp_json_encode( array( - 'rest_route' => $_GET['rest_route'], - 'timestamp' => (int) $_GET['timestamp'], - 'url' => wp_unslash( $_GET['url'] ), + 'rest_route' => isset( $_GET['rest_route'] ) ? filter_var( wp_unslash( $_GET['rest_route'] ) ) : null, + 'timestamp' => (int) $_GET['timestamp'], + 'url' => esc_url_raw( wp_unslash( $_GET['url'] ) ), ) ); @@ -1544,11 +1444,13 @@ class Jetpack_Core_Json_Api_Endpoints { return false; } - // signature timestamp must be within 5min of current time + // signature timestamp must be within 5min of current time. if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) { return false; } + // phpcs:enable WordPress.Security.NonceVerification.Recommended + return true; } @@ -1617,6 +1519,9 @@ class Jetpack_Core_Json_Api_Endpoints { ); } + /** + * Fetch information about the Rewind status of the site. + */ public static function rewind_data() { $site_id = Jetpack_Options::get_option( 'id' ); @@ -1778,7 +1683,7 @@ class Jetpack_Core_Json_Api_Endpoints { 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 ) { + if ( ! isset( $request['isActive'] ) || false !== $request['isActive'] ) { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } @@ -1818,7 +1723,7 @@ class Jetpack_Core_Json_Api_Endpoints { return rest_ensure_response( array( - 'authorizeUrl' => Jetpack::build_authorize_url( false, true ), + 'authorizeUrl' => Jetpack::build_authorize_url( false ), ) ); } @@ -1884,7 +1789,7 @@ class Jetpack_Core_Json_Api_Endpoints { */ public static function unlink_user( $request ) { - if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) { + if ( ! isset( $request['linked'] ) || false !== $request['linked'] ) { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } @@ -1908,7 +1813,7 @@ class Jetpack_Core_Json_Api_Endpoints { * * @return WP_REST_Response|WP_Error Response, else error. */ - public static function get_user_tracking_settings( $request ) { + public static function get_user_tracking_settings( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable if ( ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) { $response = array( 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. @@ -1920,7 +1825,7 @@ class Jetpack_Core_Json_Api_Endpoints { array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ) ); @@ -1954,7 +1859,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'method' => 'PUT', 'headers' => array( 'Content-Type' => 'application/json', - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ), wp_json_encode( $request->get_params() ) @@ -1985,7 +1890,7 @@ class Jetpack_Core_Json_Api_Endpoints { // Allow use a store sandbox. Internal ref: PCYsg-IA-p2. if ( isset( $_COOKIE ) && isset( $_COOKIE['store_sandbox'] ) ) { - $secret = $_COOKIE['store_sandbox']; + $secret = filter_var( wp_unslash( $_COOKIE['store_sandbox'] ) ); $args['headers']['Cookie'] = "store_sandbox=$secret;"; } @@ -2032,7 +1937,7 @@ class Jetpack_Core_Json_Api_Endpoints { array( 'code' => 'success', 'message' => esc_html__( 'Site data correctly received.', 'jetpack' ), - 'data' => json_encode( $site_data ), + 'data' => wp_json_encode( $site_data ), ) ); } @@ -2081,7 +1986,7 @@ class Jetpack_Core_Json_Api_Endpoints { array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), ), ), null, @@ -2103,7 +2008,7 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'activity_not_found', esc_html__( 'No activity found', 'jetpack' ), - array( 'status' => 204 ) // no content + array( 'status' => 204 ) // no content. ); } @@ -2116,6 +2021,62 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Fetch the discount for this site and return it. + * + * @since 10.8 + * + * @return array|WP_Error + */ + public static function get_site_discount() { + $site_id = Jetpack_Options::get_option( 'id' ); + + if ( ! $site_id ) { + return new WP_Error( + 'site_id_missing', + esc_html__( 'Site ID is missing.', 'jetpack' ), + array( 'status' => 400 ) + ); + } + + $response = Client::wpcom_json_api_request_as_user( + "/sites/$site_id/discount", + '2', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), + ), + ) + ); + + $response_code = wp_remote_retrieve_response_code( $response ); + $data = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( 200 !== $response_code ) { + return new WP_Error( + 'discount_fetch_failed', + is_object( $data ) && property_exists( $data, 'error' ) ? $data->error : esc_html__( 'Could not retrieve site discount.', 'jetpack' ), + array( 'status' => $response_code ) + ); + } + + if ( ! isset( $data ) ) { + return new WP_Error( + 'discount_parse_error', + esc_html__( 'Could not parse discount', 'jetpack' ), + array( 'status' => 204 ) // no content. + ); + } + + return rest_ensure_response( + array( + 'code' => 'success', + 'data' => $data, + ) + ); + } + + /** * Reset Jetpack options * * @since 4.3.0 @@ -2130,18 +2091,19 @@ class Jetpack_Core_Json_Api_Endpoints { */ public static function reset_jetpack_options( $request ) { - if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) { + if ( ! isset( $request['reset'] ) || true !== $request['reset'] ) { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } if ( isset( $request['options'] ) ) { - $data = $request['options']; + $data = $request['options']; + $message = ''; switch ( $data ) { case ( 'options' ): $options_to_reset = Jetpack::get_jetpack_options_for_reset(); - // Reset the Jetpack options + // Reset the Jetpack options. foreach ( $options_to_reset['jp_options'] as $option_to_reset ) { Jetpack_Options::delete_option( $option_to_reset ); } @@ -2150,32 +2112,28 @@ class Jetpack_Core_Json_Api_Endpoints { delete_option( $option_to_reset ); } - // Reset to default modules + // Reset to default modules. $default_modules = Jetpack::get_default_modules(); Jetpack::update_active_modules( $default_modules ); + $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' ), - ) - ); - break; + $message = esc_html__( 'Modules reset to default.', 'jetpack' ); + break; default: return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } + + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => $message, + ) + ); } return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) ); @@ -2217,6 +2175,14 @@ class Jetpack_Core_Json_Api_Endpoints { public static function get_updateable_data_list( $selector = '' ) { $options = array( + // Blocks. + 'jetpack_blocks_disabled' => array( + 'description' => esc_html__( 'Jetpack Blocks disabled.', 'jetpack' ), + 'type' => 'boolean', + 'default' => false, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'settings', + ), // Carousel 'carousel_background_color' => array( @@ -2257,7 +2223,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'carousel', ), - // Comments + // Comments. 'highlander_comment_form_prompt' => array( 'description' => esc_html__( 'Greeting Text', 'jetpack' ), 'type' => 'string', @@ -2283,7 +2249,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'comments', ), - // Custom Content Types + // Custom Content Types. 'jetpack_portfolio' => array( 'description' => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ), 'type' => 'boolean', @@ -2313,7 +2279,38 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'custom-content-types', ), - // Galleries + // WAF. + 'jetpack_waf_ip_list' => array( + 'description' => esc_html__( 'Allow / Block list - Block or allow a specific request IP.', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'waf', + ), + 'jetpack_waf_ip_block_list' => array( + 'description' => esc_html__( 'Blocked IP addresses', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'validate_callback' => __CLASS__ . '::validate_string', + 'sanitize_callback' => 'esc_textarea', + 'jp_group' => 'waf', + ), + 'jetpack_waf_ip_allow_list' => array( + 'description' => esc_html__( 'Always allowed IP addresses', 'jetpack' ), + 'type' => 'string', + 'default' => '', + 'validate_callback' => __CLASS__ . '::validate_string', + 'sanitize_callback' => 'esc_textarea', + 'jp_group' => 'waf', + ), + 'jetpack_waf_share_data' => array( + 'description' => esc_html__( 'Share data with Jetpack.', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'waf', + ), + // Galleries. 'tiled_galleries' => array( 'description' => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ), 'type' => 'boolean', @@ -2339,7 +2336,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'gravatar-hovercards', ), - // Infinite Scroll + // Infinite Scroll. 'infinite_scroll' => array( 'description' => esc_html__( 'To infinity and beyond', 'jetpack' ), 'type' => 'boolean', @@ -2355,7 +2352,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'infinite-scroll', ), - // Likes + // Likes. 'wpl_default' => array( 'description' => esc_html__( 'WordPress.com Likes are', 'jetpack' ), 'type' => 'string', @@ -2379,7 +2376,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'likes', ), - // Markdown + // Markdown. 'wpcom_publish_comments_with_markdown' => array( 'description' => esc_html__( 'Use Markdown for comments.', 'jetpack' ), 'type' => 'boolean', @@ -2395,7 +2392,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'markdown', ), - // Monitor + // Monitor. 'monitor_receive_notifications' => array( 'description' => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ), 'type' => 'boolean', @@ -2404,7 +2401,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'monitor', ), - // Post by Email + // Post by Email. 'post_by_email_address' => array( 'description' => esc_html__( 'Email Address', 'jetpack' ), 'type' => 'string', @@ -2425,7 +2422,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'post-by-email', ), - // Protect + // Protect. 'jetpack_protect_key' => array( 'description' => esc_html__( 'Protect API key', 'jetpack' ), 'type' => 'string', @@ -2442,7 +2439,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'protect', ), - // Sharing + // Sharing. 'sharing_services' => array( 'description' => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ), 'type' => 'object', @@ -2525,7 +2522,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'sharedaddy', ), - // SSO + // SSO. 'jetpack_sso_require_two_step' => array( 'description' => esc_html__( 'Require Two-Step Authentication', 'jetpack' ), 'type' => 'boolean', @@ -2541,7 +2538,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'sso', ), - // Subscriptions + // Subscriptions. 'stb_enabled' => array( 'description' => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ), 'type' => 'boolean', @@ -2564,7 +2561,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'subscriptions', ), - // Related Posts + // Related Posts. 'show_headline' => array( 'description' => esc_html__( 'Highlight related content with a heading', 'jetpack' ), 'type' => 'boolean', @@ -2605,7 +2602,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'search', ), - // Verification Tools + // Verification Tools. 'google' => array( 'description' => esc_html__( 'Google Search Console', 'jetpack' ), 'type' => 'string', @@ -2723,7 +2720,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'wordads', ), - // Google Analytics + // Google Analytics. 'google_analytics_tracking_id' => array( 'description' => esc_html__( 'Google Analytics', 'jetpack' ), 'type' => 'string', @@ -2732,7 +2729,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'google-analytics', ), - // Stats + // Stats. 'admin_bar' => array( 'description' => esc_html__( 'Include a small chart in your admin bar with a 48-hour traffic snapshot.', 'jetpack' ), 'type' => 'boolean', @@ -2807,7 +2804,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'settings', ), - // Apps card on dashboard + // Apps card on dashboard. 'dismiss_dash_app_card' => array( 'description' => '', 'type' => 'boolean', @@ -2816,7 +2813,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'jp_group' => 'settings', ), - // Empty stats card dismiss + // Empty stats card dismiss. 'dismiss_empty_stats_card' => array( 'description' => '', 'type' => 'boolean', @@ -2878,9 +2875,18 @@ class Jetpack_Core_Json_Api_Endpoints { 'sanitize_callback' => 'Jetpack_SEO_Titles::sanitize_title_formats', ), + // VideoPress. + 'videopress_private_enabled_for_site' => array( + 'description' => esc_html__( 'Video Privacy: Restrict views to members of this site', 'jetpack' ), + 'type' => 'boolean', + 'default' => 0, + 'validate_callback' => __CLASS__ . '::validate_boolean', + 'jp_group' => 'videopress', + ), + ); - // Add modules to list so they can be toggled + // Add modules to list so they can be toggled. $modules = Jetpack::get_available_modules(); if ( is_array( $modules ) && ! empty( $modules ) ) { $module_args = array( @@ -2897,13 +2903,13 @@ class Jetpack_Core_Json_Api_Endpoints { if ( is_array( $selector ) ) { - // Return only those options whose keys match $selector keys + // Return only those options whose keys match $selector keys. return array_intersect_key( $options, $selector ); } if ( 'any' === $selector ) { - // Toggle module or update any module option or any general setting + // Toggle module or update any module option or any general setting. return $options; } @@ -2966,7 +2972,14 @@ class Jetpack_Core_Json_Api_Endpoints { public static function validate_boolean( $value, $request, $param ) { // 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 new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), + $param + ) + ); } return true; } @@ -2984,7 +2997,14 @@ class Jetpack_Core_Json_Api_Endpoints { */ 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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be a positive integer.', 'jetpack' ), + $param + ) + ); } return true; } @@ -3025,14 +3045,23 @@ class Jetpack_Core_Json_Api_Endpoints { 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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s not recognized', 'jetpack' ), + $param + ) + ); } $args = $attributes['args'][ $param ]; if ( ! empty( $args['enum'] ) ) { - // 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 ) ) { + $enum = ( count( array_filter( array_keys( $args['enum'] ), 'is_string' ) ) > 0 ) + ? array_keys( $args['enum'] ) + : $args['enum']; + $enum = array_map( 'strval', $enum ); + if ( ! in_array( $value, $enum, true ) ) { return new WP_Error( 'invalid_param_value', sprintf( @@ -3060,13 +3089,27 @@ class Jetpack_Core_Json_Api_Endpoints { */ 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 ) ); + return new WP_Error( + 'invalid_param_value', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be an array', 'jetpack' ), + $param + ) + ); } $modules = Jetpack::get_available_modules(); - if ( count( array_intersect( $value, $modules ) ) != count( $value ) ) { - return new WP_Error( 'invalid_param_value', sprintf( esc_html__( '%s must be a list of valid modules', 'jetpack' ), $param ) ); + if ( count( array_intersect( $value, $modules ) ) !== count( $value ) ) { + return new WP_Error( + 'invalid_param_value', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be a list of valid modules', 'jetpack' ), + $param + ) + ); } return true; @@ -3085,7 +3128,14 @@ class Jetpack_Core_Json_Api_Endpoints { */ 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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), + $param + ) + ); } return true; } @@ -3095,15 +3145,22 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.6.0 * - * @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 $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_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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a verification string used to verify a service like Google Webmaster Console. */ + esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), + $param + ) + ); } return true; } @@ -3124,7 +3181,7 @@ class Jetpack_Core_Json_Api_Endpoints { 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. */ + /* 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 ) @@ -3148,13 +3205,20 @@ class Jetpack_Core_Json_Api_Endpoints { public static function validate_sharing_show( $value, $request, $param ) { $views = array( 'index', 'post', 'page', 'attachment', 'jetpack-portfolio' ); if ( ! is_array( $value ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array of post types.', 'jetpack' ), $param ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be an array of post types.', 'jetpack' ), + $param + ) + ); } if ( ! array_intersect( $views, $value ) ) { 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 */ + /* 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 ) @@ -3182,7 +3246,14 @@ class Jetpack_Core_Json_Api_Endpoints { */ public static function validate_services( $value, $request, $param ) { if ( ! is_array( $value ) || ! isset( $value['visible'] ) || ! isset( $value['hidden'] ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), $param ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be an array with visible and hidden items.', 'jetpack' ), + $param + ) + ); } // Allow to clear everything. @@ -3203,7 +3274,7 @@ class Jetpack_Core_Json_Api_Endpoints { 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 */ + /* 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 ) @@ -3226,7 +3297,14 @@ class Jetpack_Core_Json_Api_Endpoints { */ public static function validate_custom_service( $value, $request, $param ) { if ( ! is_array( $value ) || ! isset( $value['sharing_name'] ) || ! isset( $value['sharing_url'] ) || ! isset( $value['sharing_icon'] ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), $param ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be an array with sharing name, url and icon.', 'jetpack' ), + $param + ) + ); } // Allow to clear everything. @@ -3241,7 +3319,14 @@ class Jetpack_Core_Json_Api_Endpoints { if ( ( ! empty( $value['sharing_name'] ) && ! is_string( $value['sharing_name'] ) ) || ( ! empty( $value['sharing_url'] ) && ! is_string( $value['sharing_url'] ) ) || ( ! empty( $value['sharing_icon'] ) && ! is_string( $value['sharing_icon'] ) ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), $param ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s needs sharing name, url and icon.', 'jetpack' ), + $param + ) + ); } return true; } @@ -3259,17 +3344,31 @@ class Jetpack_Core_Json_Api_Endpoints { */ 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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + 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' ) { return new WP_Error( 'invalid_param', esc_html__( 'Failed loading required dependency Sharing_Service.', 'jetpack' ) ); } $sharer = new Sharing_Service(); - $services = array_keys( $sharer->get_all_services() ); + $services = $sharer->get_all_services(); - if ( ! empty( $value ) && ! in_array( $value, $services ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), $param ) ); + if ( ! empty( $value ) && ! isset( $services[ $value ] ) ) { + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s is not a registered custom sharing service.', 'jetpack' ), + $param + ) + ); } return true; @@ -3280,15 +3379,22 @@ class Jetpack_Core_Json_Api_Endpoints { * * @since 4.3.0 * - * @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 $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_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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a twitter name. */ + esc_html__( '%s must be a Twitter username.', 'jetpack' ), + $param + ) + ); } return true; } @@ -3306,7 +3412,14 @@ class Jetpack_Core_Json_Api_Endpoints { */ 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 ) ); + return new WP_Error( + 'invalid_param', + sprintf( + /* Translators: Placeholder is a parameter name. */ + esc_html__( '%s must be a string.', 'jetpack' ), + $param + ) + ); } return true; } @@ -3399,12 +3512,12 @@ class Jetpack_Core_Json_Api_Endpoints { $news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' ); } - if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) { - // Is a list of modules + if ( $slug === null && isset( $modules['sitemaps'] ) ) { + // Is a list of modules. $modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url; $modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url; - } elseif ( 'sitemaps' == $slug ) { - // It's a single module + } elseif ( 'sitemaps' === $slug ) { + // It's a single module. $modules['extra']['sitemap_url'] = $sitemap_url; $modules['extra']['news_sitemap_url'] = $news_sitemap_url; } @@ -3432,17 +3545,17 @@ class Jetpack_Core_Json_Api_Endpoints { switch ( $module ) { case 'monitor': - // Status of user notifications + // Status of user notifications. $options['monitor_receive_notifications']['current_value'] = self::cast_value( self::get_remote_value( 'monitor', 'monitor_receive_notifications' ), $options['monitor_receive_notifications'] ); break; case 'post-by-email': - // Email address + // Email address. $options['post_by_email_address']['current_value'] = self::cast_value( self::get_remote_value( 'post-by-email', 'post_by_email_address' ), $options['post_by_email_address'] ); break; case 'protect': - // Protect + // 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'; @@ -3464,7 +3577,7 @@ class Jetpack_Core_Json_Api_Endpoints { $wga = get_option( 'jetpack_wga' ); $code = ''; if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) { - $code = $wga[ 'code' ]; + $code = $wga['code']; } $options['google_analytics_tracking_id']['current_value'] = $code; break; @@ -3503,7 +3616,7 @@ class Jetpack_Core_Json_Api_Endpoints { // At this point some options have current_value not set because they're options // that only get written on update, so we set current_value to the default one. foreach ( $options as $key => $value ) { - // We don't need validate_callback in the response + // We don't need validate_callback in the response. if ( isset( $options[ $key ]['validate_callback'] ) ) { unset( $options[ $key ]['validate_callback'] ); } @@ -3549,7 +3662,7 @@ class Jetpack_Core_Json_Api_Endpoints { * @return bool|float|int|string */ public static function cast_value( $value, $definition ) { - if ( $value === 'NULL' ) { + if ( 'NULL' === $value ) { return null; } @@ -3561,19 +3674,19 @@ class Jetpack_Core_Json_Api_Endpoints { } elseif ( 'false' === $value || 'off' === $value ) { return false; } - return (bool) $value; + $value = (bool) $value; break; case 'integer': - return (int) $value; + $value = (int) $value; break; case 'float': - return (float) $value; + $value = (float) $value; break; case 'string': - return (string) $value; + $value = (string) $value; break; } } @@ -3625,14 +3738,14 @@ class Jetpack_Core_Json_Api_Endpoints { return false; } $value = Jetpack_Post_By_Email::init()->get_post_by_email_address(); - if ( $value === null ) { - $value = 'NULL'; // sentinel value so it actually gets set + if ( null === $value ) { + $value = 'NULL'; // sentinel value so it actually gets set. } break; } // Normalize value to boolean. - if ( is_wp_error( $value ) || is_null( $value ) ) { + if ( is_wp_error( $value ) || $value === null ) { $value = false; } @@ -3653,7 +3766,7 @@ class Jetpack_Core_Json_Api_Endpoints { $updates = wp_get_update_data(); if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) { $count = $updates['counts']['plugins']; - if ( 0 == $count ) { + if ( 0 === $count ) { $response = array( 'code' => 'success', 'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ), @@ -3662,7 +3775,13 @@ class Jetpack_Core_Json_Api_Endpoints { } else { $response = array( 'code' => 'updates-available', - 'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ), + 'message' => esc_html( + sprintf( + /* Translators: placeholders are numbers. */ + _n( '%s plugin needs updating.', '%s plugins need updating.', $count, 'jetpack' ), + $count + ) + ), 'count' => $count, ); } @@ -3680,8 +3799,7 @@ class Jetpack_Core_Json_Api_Endpoints { * @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() { - jetpack_require_lib( 'plugins' ); - $plugins = Jetpack_Plugins::get_plugins(); + $plugins = Plugins_Installer::get_plugins(); if ( ! empty( $plugins ) ) { return rest_ensure_response( $plugins ); @@ -3708,14 +3826,12 @@ class Jetpack_Core_Json_Api_Endpoints { 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 ); + $plugin_id = Plugins_Installer::get_plugin_id_by_slug( $plugin ); // If not installed, let's install now. if ( ! $plugin_id ) { - $result = Jetpack_Plugins::install_plugin( $plugin ); + $result = Plugins_Installer::install_plugin( $plugin ); if ( is_wp_error( $result ) ) { return new WP_Error( @@ -3756,7 +3872,7 @@ class Jetpack_Core_Json_Api_Endpoints { * 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 ); + $plugin_id = Plugins_Installer::get_plugin_id_by_slug( $plugin ); if ( ! $plugin_id ) { return new WP_Error( 'unable_to_determine_installed_plugin', @@ -3803,8 +3919,7 @@ class Jetpack_Core_Json_Api_Endpoints { ); } - jetpack_require_lib( 'plugins' ); - $plugins = Jetpack_Plugins::get_plugins(); + $plugins = Plugins_Installer::get_plugins(); if ( empty( $plugins ) ) { return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) ); @@ -3817,7 +3932,7 @@ class Jetpack_Core_Json_Api_Endpoints { $plugin = $request['plugin'] . '.php'; // Is the plugin installed? - if ( ! in_array( $plugin, array_keys( $plugins ), true ) ) { + if ( ! array_key_exists( $plugin, $plugins ) ) { return new WP_Error( 'plugin_not_found', esc_html( @@ -3832,7 +3947,7 @@ class Jetpack_Core_Json_Api_Endpoints { } // Is the plugin active already? - $status = Jetpack_Plugins::get_plugin_status( $plugin ); + $status = Plugins_Installer::get_plugin_status( $plugin ); if ( in_array( $status, array( 'active', 'network-active' ), true ) ) { return new WP_Error( 'plugin_already_active', @@ -3903,8 +4018,7 @@ 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 ) { - jetpack_require_lib( 'plugins' ); - $plugins = Jetpack_Plugins::get_plugins(); + $plugins = Plugins_Installer::get_plugins(); if ( empty( $plugins ) ) { return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) ); @@ -3912,13 +4026,23 @@ class Jetpack_Core_Json_Api_Endpoints { $plugin = stripslashes( $request['plugin'] ); - if ( ! in_array( $plugin, array_keys( $plugins ) ) ) { - return new WP_Error( 'plugin_not_found', esc_html( sprintf( __( 'Plugin %s is not installed.', 'jetpack' ), $plugin ) ), array( 'status' => 404 ) ); + if ( ! array_key_exists( $plugin, $plugins ) ) { + return new WP_Error( + 'plugin_not_found', + esc_html( + sprintf( + /* Translators: placeholder is a plugin name. */ + __( 'Plugin %s is not installed.', 'jetpack' ), + $plugin + ) + ), + array( 'status' => 404 ) + ); } $plugin_data = $plugins[ $plugin ]; - $plugin_data['active'] = in_array( Jetpack_Plugins::get_plugin_status( $plugin ), array( 'active', 'network-active' ), true ); + $plugin_data['active'] = in_array( Plugins_Installer::get_plugin_status( $plugin ), array( 'active', 'network-active' ), true ); return rest_ensure_response( array( @@ -3936,7 +4060,7 @@ class Jetpack_Core_Json_Api_Endpoints { * @param WP_REST_REQUEST $request The request parameters. * @return bool|WP_Error */ - public static function send_mobile_magic_link( $request ) { + public static function send_mobile_magic_link( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $xml = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id(), @@ -3955,8 +4079,6 @@ class Jetpack_Core_Json_Api_Endpoints { ); } - $response = $xml->getResponse(); - return rest_ensure_response( array( 'code' => 'success', @@ -3965,74 +4087,6 @@ 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. @@ -4074,7 +4128,7 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'invalid_user_permission_jetpack_crm_data', - self::$user_permissions_error_msg, + REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } @@ -4091,38 +4145,79 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'invalid_user_permission_activate_jetpack_crm_ext', - self::$user_permissions_error_msg, + REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) ); } /** - * Verify that the user can set a Jetpack license key + * Set hasSeenWCConnectionModal to true when the site has displayed it * - * @since 9.5.0 + * @since 10.4.0 * - * @return bool|WP_Error True if user is able to set a Jetpack license key + * @return bool */ - 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() ) ); + 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 ) ); } /** - * Set hasSeenWCConnectionModal to true when the site has displayed it + * Fetch introdution offers. * - * @since 10.4.0 + * @since 10.9 * - * @return bool + * @return array|WP_Error */ - public static function set_has_seen_wc_connection_modal() { - $updated_option = Jetpack_Options::update_option( 'has_seen_wc_connection_modal', true ); + public static function get_intro_offers() { + $site_id = Jetpack_Options::get_option( 'id' ); - return rest_ensure_response( array( 'success' => $updated_option ) ); + if ( ! $site_id ) { + return new WP_Error( + 'site_id_missing', + esc_html__( 'Site ID is missing.', 'jetpack' ), + array( 'status' => 400 ) + ); + } + + $response = Client::wpcom_json_api_request_as_user( + '/introductory-offers', + '2', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), + ), + ) + ); + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( 200 !== $response_code ) { + return new WP_Error( + 'intro_offers_fetch_failed', + esc_html__( 'Could not retrieve intro offers.', 'jetpack' ), + array( 'status' => $response_code ) + ); + } + + $data = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( ! isset( $data ) ) { + return new WP_Error( + 'intro_offers_error', + esc_html__( 'Could not parse intro offers.', 'jetpack' ), + array( 'status' => 204 ) // no content. + ); + } + + return rest_ensure_response( + array( + 'code' => 'success', + 'data' => $data, + ) + ); } } // class end diff --git a/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php b/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php index 00afeb01..de46b144 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php @@ -1,112 +1,10 @@ <?php - -include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; -include_once ABSPATH . 'wp-admin/includes/file.php'; - /** - * Allows us to capture that the site doesn't have proper file system access. - * In order to update the plugin. + * This file has been moved to the jetpack-plugins-installer package + * + * @deprecated 10.7 + * + * @package jetpack */ -class Jetpack_Automatic_Install_Skin extends Automatic_Upgrader_Skin { - /** - * Stores the last error key; - **/ - protected $main_error_code = 'install_error'; - - /** - * Stores the last error message. - **/ - protected $main_error_message = 'An unknown error occurred during installation'; - - /** - * Overwrites the set_upgrader to be able to tell if we e ven have the ability to write to the files. - * - * @param WP_Upgrader $upgrader - * - */ - public function set_upgrader( &$upgrader ) { - parent::set_upgrader( $upgrader ); - - // Check if we even have permission to. - $result = $upgrader->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) ); - if ( ! $result ) { - // set the string here since they are not available just yet - $upgrader->generic_strings(); - $this->feedback( 'fs_unavailable' ); - } - } - - /** - * Overwrites the error function - */ - public function error( $error ) { - if ( is_wp_error( $error ) ) { - $this->feedback( $error ); - } - } - - private function set_main_error_code( $code ) { - // Don't set the process_failed as code since it is not that helpful unless we don't have one already set. - $this->main_error_code = ( $code === 'process_failed' && $this->main_error_code ? $this->main_error_code : $code ); - } - - private function set_main_error_message( $message, $code ) { - // Don't set the process_failed as message since it is not that helpful unless we don't have one already set. - $this->main_error_message = ( $code === 'process_failed' && $this->main_error_code ? $this->main_error_code : $message ); - } - - public function get_main_error_code() { - return $this->main_error_code; - } - - public function get_main_error_message() { - return $this->main_error_message; - } - - /** - * Overwrites the feedback function - * - * @param string|array|WP_Error $data Data. - * @param mixed ...$args Optional text replacements. - */ - public function feedback( $data, ...$args ) { - - $current_error = null; - if ( is_wp_error( $data ) ) { - $this->set_main_error_code( $data->get_error_code() ); - $string = $data->get_error_message(); - } elseif ( is_array( $data ) ) { - return; - } else { - $string = $data; - } - - if ( ! empty( $this->upgrader->strings[$string] ) ) { - $this->set_main_error_code( $string ); - - $current_error = $string; - $string = $this->upgrader->strings[$string]; - } - - if ( strpos( $string, '%' ) !== false ) { - if ( ! empty( $args ) ) { - $string = vsprintf( $string, $args ); - } - } - - $string = trim( $string ); - $string = wp_kses( - $string, array( - 'a' => array( - 'href' => true - ), - 'br' => true, - 'em' => true, - 'strong' => true, - ) - ); - $this->set_main_error_message( $string, $current_error ); - $this->messages[] = $string; - } -} +class_alias( Automattic\Jetpack\Automatic_Install_Skin::class, 'Jetpack_Automatic_Install_Skin' ); diff --git a/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php b/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php index 4445cb65..a4a5ed3c 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php @@ -1,20 +1,32 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** - * Tweak the preview when rendered in an iframe + * Tweak a preview when rendered in an iframe. + * This is used when rendering iFrames in the Calypso app. + * + * This file is shared between WordPress.com and Jetpack. + * The canonical source is Jetpack and no WordPress.com-specific code should exist in this file. + * + * @package automattic/jetpack */ +/** + * Tweak a preview when rendered in an iframe. + */ class Jetpack_Iframe_Embed { - static function init() { + /** + * Initialize class. + */ + public static function init() { if ( ! self::is_embedding_in_iframe() ) { return; } - // Disable the admin bar + // Disable the admin bar. if ( ! defined( 'IFRAME_REQUEST' ) ) { define( 'IFRAME_REQUEST', true ); } - // Prevent canonical redirects + // Prevent canonical redirects. remove_filter( 'template_redirect', 'redirect_canonical' ); add_action( 'wp_head', array( 'Jetpack_Iframe_Embed', 'noindex' ), 1 ); @@ -23,36 +35,44 @@ class Jetpack_Iframe_Embed { add_filter( 'shortcode_atts_video', array( 'Jetpack_Iframe_Embed', 'disable_autoplay' ) ); add_filter( 'shortcode_atts_audio', array( 'Jetpack_Iframe_Embed', 'disable_autoplay' ) ); + $ver = sprintf( '%s-%s', gmdate( 'oW' ), defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '' ); if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { - wp_enqueue_script( 'jetpack-iframe-embed', WPMU_PLUGIN_URL . '/jetpack-iframe-embed/jetpack-iframe-embed.js', array( 'jquery' ) ); + wp_enqueue_script( + 'jetpack-iframe-embed', + WPMU_PLUGIN_URL . '/jetpack-iframe-embed/jetpack-iframe-embed.js', + array( 'jquery' ), + $ver, + false + ); } else { - $ver = sprintf( '%s-%s', gmdate( 'oW' ), defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '' ); - wp_enqueue_script( 'jetpack-iframe-embed', '//s0.wp.com/wp-content/mu-plugins/jetpack-iframe-embed/jetpack-iframe-embed.js', array( 'jquery' ), $ver ); + wp_enqueue_script( + 'jetpack-iframe-embed', + '//s0.wp.com/wp-content/mu-plugins/jetpack-iframe-embed/jetpack-iframe-embed.js', + array( 'jquery' ), + $ver, + false + ); } wp_localize_script( 'jetpack-iframe-embed', '_previewSite', array( 'siteURL' => get_site_url() ) ); } - static function is_embedding_in_iframe() { + /** + * Check that we are in an iFrame. + * + * @return bool + */ + private static function is_embedding_in_iframe() { return ( - self::has_iframe_get_param() && ( - self::has_preview_get_param() || - self::has_preview_theme_preview_param() + // phpcs:disable WordPress.Security.NonceVerification.Recommended -- No nonce needed, we're only checking for a specific screen view. + isset( $_GET['iframe'] ) && 'true' === $_GET['iframe'] + && ( + isset( $_GET['preview'] ) && 'true' === $_GET['preview'] + || isset( $_GET['theme_preview'] ) && 'true' === $_GET['theme_preview'] ) + // phpcs:enable WordPress.Security.NonceVerification.Recommended ); } - private static function has_iframe_get_param() { - return isset( $_GET['iframe'] ) && $_GET['iframe'] === 'true'; - } - - private static function has_preview_get_param() { - return isset( $_GET['preview'] ) && $_GET['preview'] === 'true'; - } - - private static function has_preview_theme_preview_param() { - return isset( $_GET['theme_preview'] ) && $_GET['theme_preview'] === 'true'; - } - /** * Disable `autoplay` shortcode attribute in context of an iframe * Added via `shortcode_atts_video` & `shortcode_atts_audio` in `init` @@ -61,7 +81,7 @@ class Jetpack_Iframe_Embed { * * @return array The output array of shortcode attributes. */ - static function disable_autoplay( $atts ) { + public static function disable_autoplay( $atts ) { return array_merge( $atts, array( 'autoplay' => false ) ); } @@ -69,7 +89,7 @@ class Jetpack_Iframe_Embed { * We don't want search engines to index iframe previews * Added via `wp_head` action in `init` */ - static function noindex() { + public static function noindex() { echo '<meta name="robots" content="noindex,nofollow" />'; } @@ -78,7 +98,7 @@ class Jetpack_Iframe_Embed { * (unless overridden on client-side by JS) * Added via `wp_head` action in `init` */ - static function base_target_blank() { + public static function base_target_blank() { echo '<base target="_blank" />'; } } diff --git a/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php b/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php index d623a3eb..4c2a0112 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php @@ -1,11 +1,22 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Utilities to interact with a Keyring instance. + * Used for Publicize as well as the Site Verification tools. + * + * @package automattic/jetpack + */ use Automattic\Jetpack\Connection\Secrets; +/** + * A series of utilities to interact with a Keyring instance. + */ class Jetpack_Keyring_Service_Helper { /** + * Class instance + * * @var Jetpack_Keyring_Service_Helper - **/ + */ private static $instance = null; /** @@ -15,36 +26,39 @@ class Jetpack_Keyring_Service_Helper { */ private static $is_sharing_page_registered = false; - static function init() { - if ( is_null( self::$instance ) ) { - self::$instance = new Jetpack_Keyring_Service_Helper; + /** + * Initialize instance. + */ + public static function init() { + if ( self::$instance === null ) { + self::$instance = new Jetpack_Keyring_Service_Helper(); } return self::$instance; } - public static $SERVICES = array( - 'facebook' => array( - 'for' => 'publicize' + const SERVICES = array( + 'facebook' => array( + 'for' => 'publicize', ), - 'twitter' => array( - 'for' => 'publicize' + 'twitter' => array( + 'for' => 'publicize', ), - 'linkedin' => array( - 'for' => 'publicize' + 'linkedin' => array( + 'for' => 'publicize', ), - 'tumblr' => array( - 'for' => 'publicize' + 'tumblr' => array( + 'for' => 'publicize', ), - 'path' => array( - 'for' => 'publicize' + 'path' => array( + 'for' => 'publicize', ), - 'google_plus' => array( - 'for' => 'publicize' + 'google_plus' => array( + 'for' => 'publicize', ), 'google_site_verification' => array( - 'for' => 'other' - ) + 'for' => 'other', + ), ); /** @@ -79,7 +93,12 @@ class Jetpack_Keyring_Service_Helper { $_registered_pages[ $hookname ] = true; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } - function get_services( $filter = 'all' ) { + /** + * Return a list of services. + * + * @param string $filter Choose 'all' to get all connected services, vs. just the connected ones. + */ + public function get_services( $filter = 'all' ) { $services = array(); if ( 'all' === $filter ) { @@ -97,14 +116,15 @@ class Jetpack_Keyring_Service_Helper { } /** - * Gets a URL to the public-api actions. Works like WP's admin_url + * Gets a URL to the public-api actions. Works like WP's admin_url. + * On WordPress.com this is/calls Keyring::admin_url. * * @param string $service Shortname of a specific service. + * @param array $params Parameters to append to an API connection URL. * * @return URL to specific public-api process */ - // on WordPress.com this is/calls Keyring::admin_url - static function api_url( $service = false, $params = array() ) { + private static function api_url( $service = false, $params = array() ) { /** * Filters the API URL used to interact with WordPress.com. * @@ -125,7 +145,13 @@ class Jetpack_Keyring_Service_Helper { return $url; } - static function connect_url( $service_name, $for ) { + /** + * Build a connection URL (sharing settings page with unique query args to create a connection). + * + * @param string $service_name Service name. + * @param string $for Feature name. + */ + public static function connect_url( $service_name, $for ) { return add_query_arg( array( 'action' => 'request', @@ -138,7 +164,14 @@ class Jetpack_Keyring_Service_Helper { ); } - static function refresh_url( $service_name, $for ) { + /** + * Build a URL to refresh a connection (sharing settings page with unique query args to refresh a connection). + * Similar to connect_url, but with a refresh parameter. + * + * @param string $service_name Service name. + * @param string $for Feature name. + */ + public static function refresh_url( $service_name, $for ) { return add_query_arg( array( 'action' => 'request', @@ -152,7 +185,13 @@ class Jetpack_Keyring_Service_Helper { ); } - static function disconnect_url( $service_name, $id ) { + /** + * Build a URL to delete a connection (sharing settings page with unique query args to delete a connection). + * + * @param string $service_name Service name. + * @param string $id Connection ID. + */ + public static function disconnect_url( $service_name, $id ) { return add_query_arg( array( 'action' => 'delete', @@ -165,10 +204,13 @@ class Jetpack_Keyring_Service_Helper { ); } - static function admin_page_load() { + /** + * Build contents handling Keyring connection management into Sharing settings screen. + */ + public static function admin_page_load() { if ( isset( $_GET['action'] ) ) { if ( isset( $_GET['service'] ) ) { - $service_name = $_GET['service']; + $service_name = sanitize_text_field( wp_unslash( $_GET['service'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We verify below. } switch ( $_GET['action'] ) { @@ -180,7 +222,20 @@ class Jetpack_Keyring_Service_Helper { $verification = ( new Secrets() )->generate( 'publicize' ); if ( ! $verification ) { $url = Jetpack::admin_url( 'jetpack#/settings' ); - wp_die( sprintf( __( "Jetpack is not connected. Please connect Jetpack by visiting <a href='%s'>Settings</a>.", 'jetpack' ), $url ) ); + wp_die( + sprintf( + wp_kses( + /* Translators: placeholder is a URL to a Settings page. */ + __( "Jetpack is not connected. Please connect Jetpack by visiting <a href='%s'>Settings</a>.", 'jetpack' ), + array( + 'a' => array( + 'href' => array(), + ), + ) + ), + esc_url( $url ) + ) + ); } $stats_options = get_option( 'stats_options' ); @@ -188,23 +243,32 @@ class Jetpack_Keyring_Service_Helper { $wpcom_blog_id = ! empty( $wpcom_blog_id ) ? $wpcom_blog_id : $stats_options['blog_id']; $user = wp_get_current_user(); - $redirect = Jetpack_Keyring_Service_Helper::api_url( $service_name, urlencode_deep( array( - 'action' => 'request', - 'redirect_uri' => add_query_arg( array( 'action' => 'done' ), menu_page_url( 'sharing', false ) ), - 'for' => 'publicize', - // required flag that says this connection is intended for publicize - 'siteurl' => site_url(), - 'state' => $user->ID, - 'blog_id' => $wpcom_blog_id, - 'secret_1' => $verification['secret_1'], - 'secret_2' => $verification['secret_2'], - 'eol' => $verification['exp'], - ) ) ); - wp_redirect( $redirect ); + $redirect = self::api_url( + $service_name, + urlencode_deep( + array( + 'action' => 'request', + 'redirect_uri' => add_query_arg( array( 'action' => 'done' ), menu_page_url( 'sharing', false ) ), + 'for' => 'publicize', + // required flag that says this connection is intended for publicize. + 'siteurl' => site_url(), + 'state' => $user->ID, + 'blog_id' => $wpcom_blog_id, + 'secret_1' => $verification['secret_1'], + 'secret_2' => $verification['secret_2'], + 'eol' => $verification['exp'], + ) + ) + ); + wp_redirect( $redirect ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- The API URL is an external URL and is filterable. exit; - break; case 'completed': + /* + * We do not use a nonce here, + * since we're populating a local cache of + * the Publicize connections that were created and stored on WordPress.com. + */ $xml = new Jetpack_IXR_Client(); $xml->query( 'jetpack.fetchPublicizeConnections' ); @@ -216,12 +280,12 @@ class Jetpack_Keyring_Service_Helper { break; case 'delete': - $id = $_GET['id']; + $id = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : null; check_admin_referer( 'keyring-request', 'kr_nonce' ); check_admin_referer( "keyring-request-$service_name", 'nonce' ); - Jetpack_Keyring_Service_Helper::disconnect( $service_name, $id ); + self::disconnect( $service_name, $id ); do_action( 'connection_disconnected', $service_name ); break; @@ -231,8 +295,14 @@ class Jetpack_Keyring_Service_Helper { /** * Remove a Publicize connection + * + * @param string $service_name Service name. + * @param string $connection_id Connection ID. + * @param int|bool $_blog_id Blog ID. + * @param int|bool $_user_id User ID. + * @param bool $force_delete Force delete the connection. */ - static function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) { + public static function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $xml = new Jetpack_IXR_Client(); $xml->query( 'jetpack.deletePublicizeConnection', $connection_id ); @@ -242,5 +312,4 @@ class Jetpack_Keyring_Service_Helper { return false; } } - } diff --git a/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php b/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php index 75b6bbfd..4887e588 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php @@ -101,7 +101,7 @@ class Jetpack_Password_Checker { */ $this->common_passwords = apply_filters( 'jetpack_password_checker_restricted_strings', array() ); - if ( is_null( $user ) ) { + if ( $user === null ) { $this->user_id = get_current_user_id(); } elseif ( is_object( $user ) && isset( $user->ID ) ) { diff --git a/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php b/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php index 1bf5bf6c..f533e4c3 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php @@ -1,4 +1,4 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * The Image Sizes library. * @@ -15,17 +15,23 @@ jetpack_require_lib( 'class.jetpack-photon-image' ); class Jetpack_Photon_ImageSizes { /** - * @var array $data Attachment metadata. + * Attachment metadata. + * + * @var array */ public $data; /** - * @var Image Image to be resized. + * Image to be resized. + * + * @var Image */ public $image; /** - * @var null|array $sizes Intermediate sizes. + * Intermediate sizes. + * + * @var null|array */ public static $sizes = null; @@ -99,6 +105,8 @@ class Jetpack_Photon_ImageSizes { } /** + * Add filtered sizes. + * * @return array */ public function filtered_sizes() { @@ -170,7 +178,9 @@ class Jetpack_Photon_ImageSizes { } /** - * @param array $size_data + * Resize image. + * + * @param array $size_data Resize parameters. * * @return array|\WP_Error Array for usage in $metadata['sizes']; WP_Error on failure. */ diff --git a/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php b/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php index 81ef74b4..4e15f08d 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php @@ -1,4 +1,4 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * The Image Class. * @@ -11,37 +11,51 @@ class Jetpack_Photon_Image { /** - * @var string $filename Attachment's Filename. + * Attachment's Filename. + * + * @var string */ public $filename; /** - * @var string/WP_Erorr $mime_type Attachment's mime-type, WP_Error on failure when recalculating the dimensions. + * Attachment's mime-type, WP_Error on failure when recalculating the dimensions. + * + * @var string|WP_Error */ private $mime_type; /** - * @var int $original_width Image original width. + * Image original width. + * + * @var int */ private $original_width; /** - * @var int $original_width Image original height. + * Image original height. + * + * @var int */ private $original_height; /** - * @var int $width Current attachment's width. + * Current attachment's width. + * + * @var int */ private $width; /** - * @var int $height Current attachment's height. + * Current attachment's height. + * + * @var int */ private $height; /** - * @var bool $is_resized Whether the attachment has been resized yet, or not. + * Whether the attachment has been resized yet, or not. + * + * @var bool */ private $is_resized = false; @@ -53,14 +67,16 @@ class Jetpack_Photon_Image { * width : int Image width * height : int Image height * - * @param array $data Array of attachment metadata, typically value of _wp_attachment_metadata postmeta + * @param array $data Array of attachment metadata, typically value of _wp_attachment_metadata postmeta. * @param string|\WP_Error $mime_type Typically value returned from get_post_mime_type function. */ public function __construct( $data, $mime_type ) { - $this->filename = $data['file']; - $this->width = $this->original_width = $data['width']; - $this->height = $this->original_height = $data['height']; - $this->mime_type = $mime_type; + $this->filename = $data['file']; + $this->original_width = $data['width']; + $this->original_height = $data['height']; + $this->width = $this->original_width; + $this->height = $this->original_height; + $this->mime_type = $mime_type; } /** @@ -84,7 +100,9 @@ class Jetpack_Photon_Image { $this->set_width_height( $dimensions ); - return $this->is_resized = true; + $this->is_resized = true; + + return true; } /** @@ -205,16 +223,16 @@ class Jetpack_Photon_Image { * associative array for the sake of more readable code no relying on index * nor `list`. * - * @param int $max_width - * @param int $max_height - * @param bool|array $crop + * @param int $max_width Maximum width. + * @param int $max_height Maximum height. + * @param bool|array $crop Cropping parameters. * * @return array|\WP_Error Array of dimensions matching the parameters to imagecopyresampled. WP_Error on failure. */ protected function image_resize_dimensions( $max_width, $max_height, $crop ) { $dimensions = image_resize_dimensions( $this->original_width, $this->original_height, $max_width, $max_height, $crop ); if ( ! $dimensions ) { - return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ), $this->filename ); + return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions', 'jetpack' ), $this->filename ); } return array_combine( diff --git a/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php b/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php index c3df0778..fb9dbf85 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php @@ -1,23 +1,51 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Measure the performance of Jetpack Search queries. + */ class Jetpack_Search_Performance_Logger { /** - * @var Jetpack_Search_Performance_Logger - **/ + * Jetpack_Search_Performance_Logger instance. + * + * @var null|Jetpack_Search_Performance_Logger + */ private static $instance = null; + /** + * WP_Query instance. + * + * @var null|WP_Query + */ private $current_query = null; + + /** + * Time when the query was started. + * + * @var null|float + */ private $query_started = null; + + /** + * Performance results. + * + * @var null|array + */ private $stats = null; - static function init() { - if ( is_null( self::$instance ) ) { - self::$instance = new Jetpack_Search_Performance_Logger; + /** + * Initialize the class. + */ + public static function init() { + if ( self::$instance === null ) { + self::$instance = new Jetpack_Search_Performance_Logger(); } return self::$instance; } + /** + * The constructor. + */ private function __construct() { $this->stats = array(); add_action( 'pre_get_posts', array( $this, 'begin_log_query' ), 10, 1 ); @@ -26,17 +54,28 @@ class Jetpack_Search_Performance_Logger { add_action( 'wp_footer', array( $this, 'print_stats' ) ); } - public function begin_log_query( $query ) { + /** + * Log the time when the query was started. + * + * @param WP_Query $query The query. + */ + public function begin_log_query( $query ) { if ( $this->should_log_query( $query ) ) { $this->query_started = microtime( true ); $this->current_query = $query; } } + /** + * Record the time when an SQL query was completed. + * + * @param int $found_posts The number of posts found. + * @param WP_Query $query The WP_Query instance (passed by reference). + */ public function log_mysql_query( $found_posts, $query ) { if ( $this->current_query === $query ) { $duration = microtime( true ) - $this->query_started; - if ( $duration < 60 ) { // eliminate outliers, likely tracking errors + if ( $duration < 60 ) { // eliminate outliers, likely tracking errors. $this->record_query_time( $duration, false ); } $this->reset_query_state(); @@ -45,38 +84,58 @@ class Jetpack_Search_Performance_Logger { return $found_posts; } + /** + * Log Jetpack Search query. + */ public function log_jetpack_search_query() { $duration = microtime( true ) - $this->query_started; - if ( $duration < 60 ) { // eliminate outliers, likely tracking errors + if ( $duration < 60 ) { // eliminate outliers, likely tracking errors. $this->record_query_time( $duration, true ); } $this->reset_query_state(); } + /** + * Reset data after each log. + */ private function reset_query_state() { $this->query_started = null; $this->current_query = null; } + /** + * Check if a query should be logged (a main query, or a jetpack search query). + * + * @param WP_Query $query The WP_Query instance. + */ private function should_log_query( $query ) { return $query->is_main_query() && $query->is_search(); } + /** + * Record the time of a query. + * + * @param float $duration The duration of the query. + * @param bool $was_jetpack_search Was this a Jetpack Search query. + */ private function record_query_time( $duration, $was_jetpack_search ) { $this->stats[] = array( $was_jetpack_search, (int) ( $duration * 1000 ) ); } + /** + * Print performance stats in the footer. + */ public function print_stats() { $beacons = array(); if ( ! empty( $this->stats ) ) { - foreach( $this->stats as $stat ) { + foreach ( $this->stats as $stat ) { $search_type = $stat[0] ? 'es' : 'mysql'; - $beacons[] = "%22jetpack.search.{$search_type}.duration:{$stat[1]}|ms%22"; + $beacons[] = "%22jetpack.search.{$search_type}.duration:{$stat[1]}|ms%22"; } - $encoded_json = '{%22beacons%22:[' . implode(',', $beacons ) . ']}'; - $encoded_site_url = urlencode( site_url() ); - $url = "https://pixel.wp.com/boom.gif?v=0.9&u={$encoded_site_url}&json={$encoded_json}"; + $encoded_json = '{%22beacons%22:[' . implode( ',', $beacons ) . ']}'; + $encoded_site_url = rawurlencode( site_url() ); + $url = "https://pixel.wp.com/boom.gif?v=0.9&u={$encoded_site_url}&json={$encoded_json}"; echo '<img src="' . esc_url( $url ) . '" width="1" height="1" style="display:none;" alt=""/>'; } } diff --git a/plugins/jetpack/_inc/lib/class.media.php b/plugins/jetpack/_inc/lib/class.media.php index a9ae488c..826c1535 100644 --- a/plugins/jetpack/_inc/lib/class.media.php +++ b/plugins/jetpack/_inc/lib/class.media.php @@ -1,15 +1,35 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -require_once( JETPACK__PLUGIN_DIR . 'sal/class.json-api-date.php' ); +require_once JETPACK__PLUGIN_DIR . 'sal/class.json-api-date.php'; /** * Class to handle different actions related to media. */ class Jetpack_Media { - public static $WP_ORIGINAL_MEDIA = '_wp_original_post_media'; - public static $WP_REVISION_HISTORY = '_wp_revision_history'; - public static $REVISION_HISTORY_MAXIMUM_AMOUNT = 0; - public static $WP_ATTACHMENT_IMAGE_ALT = '_wp_attachment_image_alt'; + /** + * Original media meta data. Metadata key as stored by WP. + * + * @var string + */ + const WP_ORIGINAL_MEDIA = '_wp_original_post_media'; + /** + * Revision history. Metadata key as stored by WP. + * + * @var string + */ + const WP_REVISION_HISTORY = '_wp_revision_history'; + /** + * Maximum amount of revisions. + * + * @var int + */ + const REVISION_HISTORY_MAXIMUM_AMOUNT = 0; + /** + * Image Alt. Metadata key as stored by WP. + * + * @var string + */ + const WP_ATTACHMENT_IMAGE_ALT = '_wp_attachment_image_alt'; /** * Generate a filename in function of the original filename of the media. @@ -45,7 +65,7 @@ class Jetpack_Media { // Add unique seed based on the filename. $filename_base .= '-' . crc32( $filename_base ) . '-'; - $number_suffix = time() . rand( 100, 999 ); + $number_suffix = time() . wp_rand( 100, 999 ); do { $filename = $filename_base; @@ -70,13 +90,14 @@ class Jetpack_Media { * "http://test.files.wordpress.com/2016/10/test.png" the resulting string * would be: "2016/10" * - * @param number $media_id + * @param int $media_id Attachment ID. * @return string */ private static function get_time_string_from_guid( $media_id ) { - $time = date( "Y/m", strtotime( current_time( 'mysql' ) ) ); + $time = gmdate( 'Y/m', strtotime( current_time( 'mysql' ) ) ); - if ( $media = get_post( $media_id ) ) { + $media = get_post( $media_id ); + if ( $media ) { $pattern = '/\/(\d{4}\/\d{2})\//'; preg_match( $pattern, $media->guid, $matches ); if ( count( $matches ) > 1 ) { @@ -89,20 +110,27 @@ class Jetpack_Media { /** * Return an array of allowed mime_type items used to upload a media file. * + * @param array $default_mime_types Array of mime types. + * * @return array mime_type array */ - static function get_allowed_mime_types( $default_mime_types ) { - return array_unique( array_merge( $default_mime_types, array( - 'application/msword', // .doc - 'application/vnd.ms-powerpoint', // .ppt, .pps - 'application/vnd.ms-excel', // .xls - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', // .ppsx - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx - 'application/vnd.oasis.opendocument.text', // .odt - 'application/pdf', // .pdf - ) ) ); + public static function get_allowed_mime_types( $default_mime_types ) { + return array_unique( + array_merge( + $default_mime_types, + array( + 'application/msword', // .doc + 'application/vnd.ms-powerpoint', // .ppt, .pps + 'application/vnd.ms-excel', // .xls + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', // .ppsx + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx + 'application/vnd.oasis.opendocument.text', // .odt + 'application/pdf', // .pdf + ) + ) + ); } /** @@ -119,14 +147,15 @@ class Jetpack_Media { /** * Try to remove the temporal file from the given file array. * - * @param array $file_array Array with data about the temporal file + * @param array $file_array Array with data about the temporal file. + * * @return bool `true` if the file has been removed. `false` either the file doesn't exist or it couldn't be removed. */ private static function remove_tmp_file( $file_array ) { - if ( ! file_exists ( $file_array['tmp_name'] ) ) { + if ( ! file_exists( $file_array['tmp_name'] ) ) { return false; } - return @unlink( $file_array['tmp_name'] ); + return @unlink( $file_array['tmp_name'] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } /** @@ -135,8 +164,8 @@ class Jetpack_Media { * The file type control is done through of `jetpack_supported_media_sideload_types` filter, * which allows define to the users their own file types list. * - * @param array $file_array file to save - * @param number $media_id + * @param array $file_array file to save. + * @param int $media_id Attachment ID. * @return array|WP_Error an array with information about the new file saved or a WP_Error is something went wrong. */ public static function save_temporary_file( $file_array, $media_id ) { @@ -146,10 +175,10 @@ class Jetpack_Media { return new WP_Error( 'invalid_input', 'No media provided in input.' ); } - // add additional mime_types through of the `jetpack_supported_media_sideload_types` filter + // add additional mime_types through of the `jetpack_supported_media_sideload_types` filter. $mime_type_static_filter = array( 'Jetpack_Media', - 'get_allowed_mime_types' + 'get_allowed_mime_types', ); add_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter ); @@ -157,22 +186,22 @@ class Jetpack_Media { ! self::is_file_supported_for_sideloading( $tmp_filename ) && ! file_is_displayable_image( $tmp_filename ) ) { - @unlink( $tmp_filename ); + @unlink( $tmp_filename ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return new WP_Error( 'invalid_input', 'Invalid file type.', 403 ); } remove_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter ); - // generate a new file name - $tmp_new_filename = self::generate_new_filename( $media_id, $file_array[ 'name' ] ); + // generate a new file name. + $tmp_new_filename = self::generate_new_filename( $media_id, $file_array['name'] ); - // start to create the parameters to move the temporal file + // start to create the parameters to move the temporal file. $overrides = array( 'test_form' => false ); - // get time according to the original filaname + // get time according to the original filaname. $time = self::get_time_string_from_guid( $media_id ); $file_array['name'] = $tmp_new_filename; - $file = wp_handle_sideload( $file_array, $overrides, $time ); + $file = wp_handle_sideload( $file_array, $overrides, $time ); self::remove_tmp_file( $file_array ); @@ -186,20 +215,20 @@ class Jetpack_Media { /** * Return an object with an snapshot of a revision item. * - * @param object $media_item - media post object + * @param object $media_item - media post object. * @return object a revision item */ public static function get_snapshot( $media_item ) { $current_file = get_attached_file( $media_item->ID ); - $file_paths = pathinfo( $current_file ); + $file_paths = pathinfo( $current_file ); $snapshot = array( - 'date' => (string) WPCOM_JSON_API_Date::format_date( $media_item->post_modified_gmt, $media_item->post_modified ), - 'URL' => (string) wp_get_attachment_url( $media_item->ID ), - 'file' => (string) $file_paths['basename'], - 'extension' => (string) $file_paths['extension'], - 'mime_type' => (string) $media_item->post_mime_type, - 'size' => (int) filesize( $current_file ), + 'date' => (string) WPCOM_JSON_API_Date::format_date( $media_item->post_modified_gmt, $media_item->post_modified ), + 'URL' => (string) wp_get_attachment_url( $media_item->ID ), + 'file' => (string) $file_paths['basename'], + 'extension' => (string) $file_paths['extension'], + 'mime_type' => (string) $media_item->post_mime_type, + 'size' => (int) filesize( $current_file ), ); return (object) $snapshot; @@ -208,9 +237,9 @@ class Jetpack_Media { /** * Add a new item into revision_history array. * - * @param object $media_item - media post object - * @param file $file - file recently added - * @param bool $has_original_media - condition is the original media has been already added + * @param object $media_item - media post object. + * @param file $file - file recently added. + * @param bool $has_original_media - condition is the original media has been already added. * @return bool `true` if the item has been added. Otherwise `false`. */ public static function register_revision( $media_item, $file, $has_original_media ) { @@ -218,30 +247,37 @@ class Jetpack_Media { return false; } - add_post_meta( $media_item->ID, self::$WP_REVISION_HISTORY, self::get_snapshot( $media_item ) ); + add_post_meta( $media_item->ID, self::WP_REVISION_HISTORY, self::get_snapshot( $media_item ) ); } /** * Return the `revision_history` of the given media. * - * @param number $media_id - media post ID + * @param number $media_id - media post ID. * @return array `revision_history` array */ public static function get_revision_history( $media_id ) { - return array_reverse( get_post_meta( $media_id, self::$WP_REVISION_HISTORY ) ); + return array_reverse( get_post_meta( $media_id, self::WP_REVISION_HISTORY ) ); } /** - * Return the original media data + * Return the original media data. + * + * @param int $media_id Attachment ID. */ public static function get_original_media( $media_id ) { - $original = get_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA, true ); + $original = get_post_meta( $media_id, self::WP_ORIGINAL_MEDIA, true ); $original = $original ? $original : array(); return $original; } + /** + * Delete a file. + * + * @param string $pathname Path name. + */ public static function delete_file( $pathname ) { if ( ! file_exists( $pathname ) || ! is_file( $pathname ) ) { - // let's touch a fake file to try to `really` remove the media file + // let's touch a fake file to try to `really` remove the media file. touch( $pathname ); } @@ -252,27 +288,28 @@ class Jetpack_Media { * Try to delete a file according to the dirname of * the media attached file and the filename. * - * @param number $media_id - media post ID - * @param string $filename - basename of the file ( name-of-file.ext ) - * @return bool `true` is the file has been removed, `false` if not. + * @param int $media_id - media post ID. + * @param string $filename - basename of the file ( name-of-file.ext ). + * + * @return void */ private static function delete_media_history_file( $media_id, $filename ) { - $attached_path = get_attached_file( $media_id ); + $attached_path = get_attached_file( $media_id ); $attached_parts = pathinfo( $attached_path ); - $dirname = $attached_parts['dirname']; + $dirname = $attached_parts['dirname']; $pathname = $dirname . '/' . $filename; - // remove thumbnails + // remove thumbnails. $metadata = wp_generate_attachment_metadata( $media_id, $pathname ); if ( isset( $metadata ) && isset( $metadata['sizes'] ) ) { - foreach ( $metadata['sizes'] as $size => $properties ) { + foreach ( $metadata['sizes'] as $properties ) { self::delete_file( $dirname . '/' . $properties['file'] ); } } - // remove primary file + // remove primary file. self::delete_file( $pathname ); } @@ -285,18 +322,19 @@ class Jetpack_Media { * * Also, it removes the file defined in each item. * - * @param number $media_id - media post ID - * @param object $criteria - criteria to remove the items - * @param array [$revision_history] - revision history array + * @param int $media_id - media post ID. + * @param object $criteria - criteria to remove the items. + * @param array $revision_history - revision history array. + * * @return array `revision_history` array updated. */ public static function remove_items_from_revision_history( $media_id, $criteria, $revision_history ) { - if ( ! isset ( $revision_history ) ) { + if ( ! isset( $revision_history ) ) { $revision_history = self::get_revision_history( $media_id ); } $from = $criteria['from']; - $to = $criteria['to'] ? $criteria['to'] : ( $from + 1 ); + $to = $criteria['to'] ? $criteria['to'] : ( $from + 1 ); for ( $i = $from; $i < $to; $i++ ) { $removed_item = array_slice( $revision_history, $from, 1 ); @@ -308,11 +346,11 @@ class Jetpack_Media { self::delete_media_history_file( $media_id, $removed_item[0]->file ); } - // override all history items - delete_post_meta( $media_id, self::$WP_REVISION_HISTORY ); + // override all history items. + delete_post_meta( $media_id, self::WP_REVISION_HISTORY ); $revision_history = array_reverse( $revision_history ); foreach ( $revision_history as &$item ) { - add_post_meta( $media_id, self::$WP_REVISION_HISTORY, $item ); + add_post_meta( $media_id, self::WP_REVISION_HISTORY, $item ); } return $revision_history; @@ -322,13 +360,14 @@ class Jetpack_Media { * Limit the number of items of the `revision_history` array. * When the stack is overflowing the oldest item is remove from there (FIFO). * - * @param number $media_id - media post ID - * @param number [$limit] - maximun amount of items. 20 as default. + * @param int $media_id - media post ID. + * @param null|int $limit - maximun amount of items. 20 as default. + * * @return array items removed from `revision_history` */ - public static function limit_revision_history( $media_id, $limit = null) { - if ( is_null( $limit ) ) { - $limit = self::$REVISION_HISTORY_MAXIMUM_AMOUNT; + public static function limit_revision_history( $media_id, $limit = null ) { + if ( $limit === null ) { + $limit = self::REVISION_HISTORY_MAXIMUM_AMOUNT; } $revision_history = self::get_revision_history( $media_id ); @@ -341,7 +380,10 @@ class Jetpack_Media { self::remove_items_from_revision_history( $media_id, - array( 'from' => $limit, 'to' => $total ), + array( + 'from' => $limit, + 'to' => $total, + ), $revision_history ); @@ -351,7 +393,7 @@ class Jetpack_Media { /** * Remove the original file and clean the post metadata. * - * @param number $media_id - media post ID + * @param int $media_id - media post ID. */ public static function clean_original_media( $media_id ) { $original_file = self::get_original_media( $media_id ); @@ -361,7 +403,7 @@ class Jetpack_Media { } self::delete_media_history_file( $media_id, $original_file->file ); - return delete_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA ); + return delete_post_meta( $media_id, self::WP_ORIGINAL_MEDIA ); } /** @@ -370,15 +412,16 @@ class Jetpack_Media { * - clean `revision_history` meta data. * - remove and clean the `original_media` * - * @param number $media_id - media post ID + * @param int $media_id - media post ID. + * * @return array results of removing these files */ public static function clean_revision_history( $media_id ) { self::clean_original_media( $media_id ); $revision_history = self::get_revision_history( $media_id ); - $total = count( $revision_history ); - $updated_history = array(); + $total = count( $revision_history ); + $updated_history = array(); if ( $total < 1 ) { return $updated_history; @@ -386,7 +429,10 @@ class Jetpack_Media { $updated_history = self::remove_items_from_revision_history( $media_id, - array( 'from' => 0, 'to' => $total ), + array( + 'from' => 0, + 'to' => $total, + ), $revision_history ); @@ -413,8 +459,7 @@ class Jetpack_Media { // The first time that the media is updated // the original media is stored into the revision_history. $snapshot = self::get_snapshot( $media_item ); - //phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - add_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA, $snapshot, true ); + add_post_meta( $media_id, self::WP_ORIGINAL_MEDIA, $snapshot, true ); } // Save temporary file in the correct location. @@ -433,7 +478,7 @@ class Jetpack_Media { $was_updated = update_attached_file( $media_id, $uploaded_path ); if ( ! $was_updated ) { - return WP_Error( 'update_error', 'Media update error' ); + return new WP_Error( 'update_error', 'Media update error' ); } // Check maximum amount of revision_history before updating the attachment metadata. @@ -458,9 +503,12 @@ class Jetpack_Media { } } -// hook: clean revision history when the media item is deleted -function clean_revision_history( $media_id ) { +/** + * Clean revision history when the media item is deleted. + * + * @param int $media_id Attachment ID. + */ +function jetpack_clean_revision_history( $media_id ) { Jetpack_Media::clean_revision_history( $media_id ); -}; - -add_action( 'delete_attachment', 'clean_revision_history' ); +} +add_action( 'delete_attachment', 'jetpack_clean_revision_history' ); diff --git a/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php b/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php index 2a2245e4..4b4839ca 100644 --- a/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php +++ b/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php @@ -1,38 +1,64 @@ -<?php - -// @todo - nicer API for array values? - +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * `WP_REST_Controller` is basically a wrapper for `register_rest_route()` * `WPCOM_REST_API_V2_Field_Controller` is a mostly-analogous wrapper for `register_rest_field()` + * + * @todo - nicer API for array values? + * + * @package automattic/jetpack + */ + +/** + * Abstract WPCOM_REST_API_V2_Field_Controller class extended for different fields needed in the Jetpack plugin. */ abstract class WPCOM_REST_API_V2_Field_Controller { /** - * @var string|string[] $object_type The REST Object Type(s) to which the field should be added. + * The REST Object Type(s) to which the field should be added. + * + * @var string|string[] */ protected $object_type; /** - * @var string $field_name The name of the REST API field to add. + * The name of the REST API field to add. + * + * @var string */ protected $field_name; + /** + * Constructor + */ public function __construct() { if ( ! $this->object_type ) { - /* translators: %s: object_type */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::$object_type', sprintf( __( "Property '%s' must be overridden.", 'jetpack' ), 'object_type' ), 'Jetpack 6.8' ); + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::$object_type', + sprintf( + /* translators: %s: object_type */ + esc_html__( "Property '%s' must be overridden.", 'jetpack' ), + 'object_type' + ), + 'jetpack-6.8' + ); return; } if ( ! $this->field_name ) { - /* translators: %s: field_name */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::$field_name', sprintf( __( "Property '%s' must be overridden.", 'jetpack' ), 'field_name' ), 'Jetpack 6.8' ); + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::$field_name', + sprintf( + /* translators: %s: field_name */ + esc_html__( "Property '%s' must be overridden.", 'jetpack' ), + 'field_name' + ), + 'jetpack-6.8' + ); return; } add_action( 'rest_api_init', array( $this, 'register_fields' ) ); - // do this again later to collect any CPTs that get registered later + // do this again later to collect any CPTs that get registered later. add_action( 'restapi_theme_init', array( $this, 'register_fields' ), 20 ); } @@ -41,6 +67,9 @@ abstract class WPCOM_REST_API_V2_Field_Controller { */ public function register_fields() { foreach ( (array) $this->object_type as $object_type ) { + if ( $this->is_registered( $object_type ) ) { + continue; + } register_rest_field( $object_type, $this->field_name, @@ -54,10 +83,23 @@ abstract class WPCOM_REST_API_V2_Field_Controller { } /** + * Checks if the field is already registered for the object_type + * + * @param string $object_type The name of the object type. + * @return boolean Whether the field has been registered for the type. + */ + public function is_registered( $object_type ) { + global $wp_rest_additional_fields; + + return ! empty( $wp_rest_additional_fields[ $object_type ][ $this->field_name ] ); + } + + /** * Ensures the response matches the schema and request context. * - * @param mixed $value - * @param WP_REST_Request $request + * @param mixed $value Value passed in request. + * @param WP_REST_Request $request WP API request. + * * @return mixed */ private function prepare_for_response( $value, $request ) { @@ -77,7 +119,8 @@ abstract class WPCOM_REST_API_V2_Field_Controller { * * If there is no default, returns the type's falsey value. * - * @param array $schema + * @param array $schema Schema to validate against. + * * @return mixed */ final public function get_default_value( $schema ) { @@ -85,7 +128,7 @@ abstract class WPCOM_REST_API_V2_Field_Controller { return $schema['default']; } - // If you have something more complicated, use $schema['default']; + // If you have something more complicated, use $schema['default']. switch ( isset( $schema['type'] ) ? $schema['type'] : 'null' ) { case 'string': return ''; @@ -110,17 +153,25 @@ abstract class WPCOM_REST_API_V2_Field_Controller { * This cannot be extended: implement `->get()` instead. * * @param mixed $object_data Probably an array. Whatever the endpoint returns. - * @param string $field_name Should always match `->field_name` - * @param WP_REST_Request $request - * @param string $object_type Should always match `->object_type` + * @param string $field_name Should always match `->field_name`. + * @param WP_REST_Request $request WP API request. + * @param string $object_type Should always match `->object_type`. + * * @return mixed */ - final public function get_for_response( $object_data, $field_name, $request, $object_type ) { + final public function get_for_response( $object_data, $field_name, $request, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $permission_check = $this->get_permission_check( $object_data, $request ); if ( ! $permission_check ) { - /* translators: %s: get_permission_check() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get_permission_check', sprintf( __( "Method '%s' must return either true or WP_Error.", 'jetpack' ), 'get_permission_check' ), 'Jetpack 6.8' ); + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::get_permission_check', + sprintf( + /* translators: %s: get_permission_check() */ + esc_html__( "Method '%s' must return either true or WP_Error.", 'jetpack' ), + 'get_permission_check' + ), + 'jetpack-6.8' + ); return $this->get_default_value( $this->get_schema() ); } @@ -138,21 +189,35 @@ abstract class WPCOM_REST_API_V2_Field_Controller { * * This cannot be extended: implement `->update()` instead. * - * @param mixed $value The new value for the field. - * @param mixed $object_data Probably a WordPress object (e.g., WP_Post) - * @param string $field_name Should always match `->field_name` - * @param WP_REST_Request $request - * @param string $object_type Should always match `->object_type` + * @param mixed $value The new value for the field. + * @param mixed $object_data Probably a WordPress object (e.g., WP_Post). + * @param string $field_name Should always match `->field_name`. + * @param WP_REST_Request $request WP API request. + * @param string $object_type Should always match `->object_type`. * @return void|WP_Error */ - final public function update_from_request( $value, $object_data, $field_name, $request, $object_type ) { + final public function update_from_request( $value, $object_data, $field_name, $request, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $permission_check = $this->update_permission_check( $value, $object_data, $request ); if ( ! $permission_check ) { - /* translators: %s: update_permission_check() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::update_permission_check', sprintf( __( "Method '%s' must return either true or WP_Error.", 'jetpack' ), 'update_permission_check' ), 'Jetpack 6.8' ); - /* translators: %s: the name of an API response field */ - return new WP_Error( 'invalid_user_permission', sprintf( __( "You are not allowed to access the '%s' field.", 'jetpack' ), $this->field_name ) ); + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::update_permission_check', + sprintf( + /* translators: %s: update_permission_check() */ + esc_html__( "Method '%s' must return either true or WP_Error.", 'jetpack' ), + 'update_permission_check' + ), + 'jetpack-6.8' + ); + + return new WP_Error( + 'invalid_user_permission', + sprintf( + /* translators: %s: the name of an API response field */ + __( "You are not allowed to access the '%s' field.", 'jetpack' ), + $this->field_name + ) + ); } if ( is_wp_error( $permission_check ) ) { @@ -170,50 +235,83 @@ abstract class WPCOM_REST_API_V2_Field_Controller { * Permission Check for the field's getter. Must be implemented in the inheriting class. * * @param mixed $object_data Whatever the endpoint would return for its response. - * @param WP_REST_Request $request + * @param WP_REST_Request $request WP API request. + * * @return true|WP_Error */ - public function get_permission_check( $object_data, $request ) { - /* translators: %s: get_permission_check() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get_permission_check', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' ); + public function get_permission_check( $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::get_permission_check', + sprintf( + /* translators: %s: method name. */ + esc_html__( "Method '%s' must be overridden.", 'jetpack' ), + __METHOD__ + ), + 'jetpack-6.8' + ); + return null; } /** * The field's "raw" getter. Must be implemented in the inheriting class. * * @param mixed $object_data Whatever the endpoint would return for its response. - * @param WP_REST_Request $request + * @param WP_REST_Request $request WP API request. * @return mixed */ - public function get( $object_data, $request ) { - /* translators: %s: get() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' ); + public function get( $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::get', + sprintf( + /* translators: %s: method name. */ + esc_html__( "Method '%s' must be overridden.", 'jetpack' ), + __METHOD__ + ), + 'jetpack-6.8' + ); } /** * Permission Check for the field's setter. Must be implemented in the inheriting class. * * @param mixed $value The new value for the field. - * @param mixed $object_data Probably a WordPress object (e.g., WP_Post) - * @param WP_REST_Request $request + * @param mixed $object_data Probably a WordPress object (e.g., WP_Post). + * @param WP_REST_Request $request WP API request. + * * @return true|WP_Error */ - public function update_permission_check( $value, $object_data, $request ) { - /* translators: %s: update_permission_check() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::update_permission_check', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' ); + public function update_permission_check( $value, $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::update_permission_check', + sprintf( + /* translators: %s: method name. */ + esc_html__( "Method '%s' must be overridden.", 'jetpack' ), + __METHOD__ + ), + 'jetpack-6.8' + ); + return null; } /** * The field's "raw" setter. Must be implemented in the inheriting class. * * @param mixed $value The new value for the field. - * @param mixed $object_data Probably a WordPress object (e.g., WP_Post) - * @param WP_REST_Request $request + * @param mixed $object_data Probably a WordPress object (e.g., WP_Post). + * @param WP_REST_Request $request WP API request. + * * @return mixed */ - public function update( $value, $object_data, $request ) { - /* translators: %s: update() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::update', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' ); + public function update( $value, $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::update', + sprintf( + /* translators: %s: method name. */ + esc_html__( "Method '%s' must be overridden.", 'jetpack' ), + __METHOD__ + ), + 'jetpack-6.8' + ); } /** @@ -242,13 +340,23 @@ abstract class WPCOM_REST_API_V2_Field_Controller { * @return array */ public function get_schema() { - /* translators: %s: get_schema() */ - _doing_it_wrong( 'WPCOM_REST_API_V2_Field_Controller::get_schema', sprintf( __( "Method '%s' must be overridden.", 'jetpack' ), __METHOD__ ), 'Jetpack 6.8' ); + _doing_it_wrong( + 'WPCOM_REST_API_V2_Field_Controller::get_schema', + sprintf( + /* translators: %s: method name. */ + esc_html__( "Method '%s' must be overridden.", 'jetpack' ), + __METHOD__ + ), + 'jetpack-6.8' + ); + return null; } /** - * @param array $schema - * @param string $context REST API Request context + * Ensure that our request matches its expected context. + * + * @param array $schema Schema to validate against. + * @param string $context REST API Request context. * @return bool */ private function is_valid_for_context( $schema, $context ) { @@ -274,9 +382,10 @@ abstract class WPCOM_REST_API_V2_Field_Controller { * * This function handles that recursion. * - * @param mixed $value - * @param array $schema - * @param string $context REST API Request context + * @param mixed $value Value passed to API request. + * @param array $schema Schema to validate against. + * @param string $context REST API Request context. + * * @return mixed Filtered $value */ final public function filter_response_by_context( $value, $schema, $context ) { diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php index 04acfb91..5578bf33 100644 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php +++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php @@ -1,10 +1,16 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Tools to interact with Jetpack modules via API requests. + * + * @package automattic/jetpack + */ +use Automattic\Jetpack\Connection\REST_Connector; +use Automattic\Jetpack\Plugins_Installer; use Automattic\Jetpack\Status; /** * This is the base class for every Core API endpoint Jetpack uses. - * */ class Jetpack_Core_API_Module_Toggle_Endpoint extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { @@ -79,10 +85,12 @@ class Jetpack_Core_API_Module_Toggle_Endpoint } if ( Jetpack::activate_module( $module_slug, false, false ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'The requested Jetpack module was activated.', 'jetpack' ), - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'The requested Jetpack module was activated.', 'jetpack' ), + ) + ); } return new WP_Error( @@ -139,10 +147,12 @@ class Jetpack_Core_API_Module_Toggle_Endpoint } if ( Jetpack::deactivate_module( $module_slug ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'The requested Jetpack module was deactivated.', 'jetpack' ), - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'The requested Jetpack module was deactivated.', 'jetpack' ), + ) + ); } return new WP_Error( 'deactivation_failed', @@ -163,7 +173,13 @@ class Jetpack_Core_API_Module_Toggle_Endpoint } } +/** + * Interact with multiple modules at once (list or activate). + * + * // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound + */ class Jetpack_Core_API_Module_List_Endpoint { + // phpcs:enable Generic.Files.OneObjectStructurePerFile.MultipleFound /** * A WordPress REST API callback method that accepts a request object and decides what to do with it. @@ -190,7 +206,7 @@ class Jetpack_Core_API_Module_List_Endpoint { * @return array Array of Jetpack modules. */ public function get_modules() { - require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php' ); + require_once JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php'; $modules = Jetpack_Admin::init()->get_modules(); foreach ( $modules as $slug => $properties ) { @@ -237,7 +253,7 @@ class Jetpack_Core_API_Module_List_Endpoint { } $activated = array(); - $failed = array(); + $failed = array(); foreach ( $request['modules'] as $module ) { if ( Jetpack::activate_module( $module, false, false ) ) { @@ -248,10 +264,12 @@ class Jetpack_Core_API_Module_List_Endpoint { } if ( empty( $failed ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'All modules activated.', 'jetpack' ), - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'All modules activated.', 'jetpack' ), + ) + ); } $error = ''; @@ -262,12 +280,15 @@ class Jetpack_Core_API_Module_List_Endpoint { $activated_text = $activated_count > 1 ? sprintf( /* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */ __( '%1$s and %2$s', 'jetpack' ), - join( ', ', $activated ), $activated_last ) : $activated_last; + join( ', ', $activated ), + $activated_last + ) : $activated_last; $error = sprintf( /* Translators: the variable is a module name. */ _n( 'The module %s was activated.', 'The modules %s were activated.', $activated_count, 'jetpack' ), - $activated_text ) . ' '; + $activated_text + ) . ' '; } $failed_count = count( $failed ); @@ -276,12 +297,15 @@ class Jetpack_Core_API_Module_List_Endpoint { $failed_text = $failed_count > 1 ? sprintf( /* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */ __( '%1$s and %2$s', 'jetpack' ), - join( ', ', $failed ), $failed_last ) : $failed_last; + join( ', ', $failed ), + $failed_last + ) : $failed_last; $error = sprintf( /* Translators: the variable is a module name. */ _n( 'The module %s failed to be activated.', 'The modules %s failed to be activated.', $failed_count, 'jetpack' ), - $failed_text ) . ' '; + $failed_text + ) . ' '; } return new WP_Error( @@ -318,8 +342,11 @@ class Jetpack_Core_API_Module_List_Endpoint { * @since 4.4.0 Renamed Jetpack_Core_API_Module_Endpoint from to Jetpack_Core_API_Data. * * @author Automattic + * + * // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound */ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { + // phpcs:enable Generic.Files.OneObjectStructurePerFile.MultipleFound /** * Process request by returning the module or updating it. @@ -327,7 +354,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { * * @since 4.3.0 * - * @param WP_REST_Request $request + * @param WP_REST_Request $request WP API request. * * @return bool|mixed|void|WP_Error */ @@ -376,7 +403,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { $module['name'] = $i18n['name']; } if ( isset( $module['description'] ) ) { - $module['description'] = $i18n['description']; + $module['description'] = $i18n['description']; $module['short_description'] = $i18n['description']; } @@ -403,13 +430,13 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { $modules = Jetpack::get_available_modules(); if ( is_array( $modules ) && ! empty( $modules ) ) { foreach ( $modules as $module ) { - // Add all module options + // Add all module options. $options = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $module ); foreach ( $options as $option_name => $option ) { $response[ $option_name ] = $option['current_value']; } - // Add the module activation state + // Add the module activation state. $response[ $module ] = Jetpack::is_module_active( $module ); } } @@ -424,7 +451,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { switch ( $setting ) { case 'lang_id': if ( ! current_user_can( 'install_languages' ) ) { - // The user doesn't have caps to install language packs, so warn the client + // The user doesn't have caps to install language packs, so warn the client. $response[ $setting ] = 'error_cap'; break; } @@ -511,7 +538,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { public function update_data( $request ) { // If it's null, we're trying to update many module options from different modules. - if ( is_null( $request['slug'] ) ) { + if ( $request['slug'] === null ) { // Value admitted by Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list that will make it return all module options. // It will not be passed. It's just checked in this method to pass that method a string or array. @@ -526,13 +553,16 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } } - // Get parameters to update the module. We can not simply use $request->get_params() because when we registered - // this route, we are adding the entire output of Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list() to - // the current request object's params. We are interested in body of the actual request. - // This may be JSON: + /* + * Get parameters to update the module. + * We can not simply use $request->get_params() because when we registered this route, + * we are adding the entire output of Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list() + * to the current request object's params. We are interested in body of the actual request. + * This may be JSON: + */ $params = $request->get_json_params(); if ( ! is_array( $params ) ) { - // Or it may be standard POST key-value pairs: + // Or it may be standard POST key-value pairs. $params = $request->get_body_params(); } @@ -551,24 +581,25 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } // Get available module options. - $options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'any' === $request['slug'] + $options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( + 'any' === $request['slug'] ? $params : $request['slug'] ); - // Prepare to toggle module if needed + // Prepare to toggle module if needed. $toggle_module = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() ); // Options that are invalid or failed to update. - $invalid = array_keys( array_diff_key( $params, $options ) ); + $invalid = array_keys( array_diff_key( $params, $options ) ); $not_updated = array(); - // Remove invalid options + // Remove invalid options. $params = array_intersect_key( $params, $options ); // Used if response is successful. The message can be overwritten and additional data can be added here. $response = array( - 'code' => 'success', + 'code' => 'success', 'message' => esc_html__( 'The requested Jetpack data updates were successful.', 'jetpack' ), ); @@ -595,7 +626,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { && 'already_inactive' === $toggle_result->get_error_code() ) { - // If the module is already inactive, we don't fail + // If the module is already inactive, we don't fail. $updated = true; } elseif ( is_wp_error( $toggle_result ) ) { $error = $toggle_result->get_error_message(); @@ -603,7 +634,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { $updated = true; } } else { - $error = Jetpack_Core_Json_Api_Endpoints::$user_permissions_error_msg; + $error = REST_Connector::get_user_permissions_error_msg(); } // The module was not toggled. @@ -639,7 +670,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { && ! Jetpack::is_module_active( $option_attrs['jp_group'] ) ) { - // We only take note of skipped options when updating one module + // We only take note of skipped options when updating one module. $not_updated[ $option ] = esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' ); continue; } @@ -651,26 +682,26 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { switch ( $option ) { case 'lang_id': if ( ! current_user_can( 'install_languages' ) ) { - // We can't affect this setting + // We can't affect this setting. $updated = false; break; } - if ( $value === 'en_US' || empty( $value ) ) { + if ( 'en_US' === $value || empty( $value ) ) { return delete_option( 'WPLANG' ); } if ( ! function_exists( 'request_filesystem_credentials' ) ) { - require_once( ABSPATH . 'wp-admin/includes/file.php' ); + require_once ABSPATH . 'wp-admin/includes/file.php'; } if ( ! function_exists( 'wp_download_language_pack' ) ) { require_once ABSPATH . 'wp-admin/includes/translation-install.php'; } - // `wp_download_language_pack` only tries to download packs if they're not already available + // `wp_download_language_pack` only tries to download packs if they're not already available. $language = wp_download_language_pack( $value ); - if ( $language === false ) { + if ( false === $language ) { // The language pack download failed. $updated = false; break; @@ -690,9 +721,9 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { // If we got an email address (create or regenerate) or 1 (delete), consider it done. if ( is_string( $result ) && preg_match( '/[a-z0-9]+@post.wordpress.com/', $result ) ) { - $response[$option] = $result; - $updated = true; - } elseif ( 1 == $result ) { + $response[ $option ] = $result; + $updated = true; + } elseif ( 1 == $result ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual $updated = true; } elseif ( is_array( $result ) && isset( $result['message'] ) ) { $error = $result['message']; @@ -701,7 +732,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { case 'jetpack_protect_key': $protect = Jetpack_Protect_Module::instance(); - if ( 'create' == $value ) { + if ( 'create' === $value ) { $result = $protect->get_protect_key(); } else { $result = false; @@ -709,8 +740,8 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { // If we got one of Protect keys, consider it done. if ( preg_match( '/[a-z0-9]{40,}/i', $result ) ) { - $response[$option] = $result; - $updated = true; + $response[ $option ] = $result; + $updated = true; } break; @@ -728,28 +759,34 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { case 'show_headline': case 'show_thumbnails': - $grouped_options = $grouped_options_current = (array) Jetpack_Options::get_option( 'relatedposts' ); - $grouped_options[$option] = $value; + $grouped_options_current = (array) Jetpack_Options::get_option( 'relatedposts' ); + $grouped_options = $grouped_options_current; + $grouped_options[ $option ] = $value; // If option value was the same, consider it done. - $updated = $grouped_options_current != $grouped_options ? Jetpack_Options::update_option( 'relatedposts', $grouped_options ) : true; + $updated = $grouped_options_current !== $grouped_options ? Jetpack_Options::update_option( 'relatedposts', $grouped_options ) : true; break; case 'search_auto_config': if ( ! $value ) { + // Skip execution if no value is specified. $updated = true; - } elseif ( class_exists( 'Jetpack_Search' ) ) { - $jps = Jetpack_Search::instance(); - if ( is_a( $jps, 'Jetpack_Instant_Search' ) ) { - $jps->auto_config_search(); - $updated = true; - } else { - $updated = new WP_Error( 'instant_search_disabled', 'Instant Search Disabled', array( 'status' => 400 ) ); + } else { + $plan = new Automattic\Jetpack\Search\Plan(); + if ( ! $plan->supports_instant_search() ) { + $updated = new WP_Error( 'instant_search_not_supported', 'Instant Search is not supported by this site', array( 'status' => 400 ) ); $error = $updated->get_error_message(); + } else { + if ( ! Automattic\Jetpack\Search\Options::is_instant_enabled() ) { + $updated = new WP_Error( 'instant_search_disabled', 'Instant Search is disabled', array( 'status' => 400 ) ); + $error = $updated->get_error_message(); + } else { + $blog_id = Automattic\Jetpack\Search\Helper::get_wpcom_site_id(); + $instance = Automattic\Jetpack\Search\Instant_Search::instance( $blog_id ); + $instance->auto_config_search(); + $updated = true; + } } - } else { - $updated = new WP_Error( 'search_disabled', 'Search Disabled', array( 'status' => 400 ) ); - $error = $updated->get_error_message(); } break; @@ -758,9 +795,10 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { case 'pinterest': case 'yandex': case 'facebook': - $grouped_options = $grouped_options_current = (array) get_option( 'verification_services_codes' ); + $grouped_options_current = (array) get_option( 'verification_services_codes' ); + $grouped_options = $grouped_options_current; - // Extracts the content attribute from the HTML meta tag if needed + // Extracts the content attribute from the HTML meta tag if needed. if ( preg_match( '#.*<meta name="(?:[^"]+)" content="([^"]+)" />.*#i', $value, $matches ) ) { $grouped_options[ $option ] = $matches[1]; } else { @@ -768,56 +806,60 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } // If option value was the same, consider it done. - $updated = $grouped_options_current != $grouped_options ? update_option( 'verification_services_codes', $grouped_options ) : true; + $updated = $grouped_options_current !== $grouped_options + ? update_option( 'verification_services_codes', $grouped_options ) + : true; break; case 'sharing_services': - 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(); // If option value was the same, consider it done. - $updated = $value != $sharer->get_blog_services() ? $sharer->set_blog_services( $value['visible'], $value['hidden'] ) : true; + $updated = $value !== $sharer->get_blog_services() + ? $sharer->set_blog_services( $value['visible'], $value['hidden'] ) + : true; break; case 'button_style': case 'sharing_label': case 'show': - 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(); - $grouped_options = $sharer->get_global_options(); + $sharer = new Sharing_Service(); + $grouped_options = $sharer->get_global_options(); $grouped_options[ $option ] = $value; - $updated = $sharer->set_global_options( $grouped_options ); + $updated = $sharer->set_global_options( $grouped_options ); break; case 'custom': - 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(); + $sharer = new Sharing_Service(); $updated = $sharer->new_service( stripslashes( $value['sharing_name'] ), stripslashes( $value['sharing_url'] ), stripslashes( $value['sharing_icon'] ) ); - // Return new custom service - $response[$option] = $updated; + // Return new custom service. + $response[ $option ] = $updated; break; case 'sharing_delete_service': - 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(); + $sharer = new Sharing_Service(); $updated = $sharer->delete_service( $value ); break; case 'jetpack-twitter-cards-site-tag': - $value = trim( ltrim( strip_tags( $value ), '@' ) ); + $value = trim( ltrim( wp_strip_all_tags( $value ), '@' ) ); $updated = get_option( $option ) !== $value ? update_option( $option, $value ) : true; break; @@ -828,36 +870,41 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { case 'do_not_track': case 'version': case 'collapse_nudges': - $grouped_options = $grouped_options_current = (array) get_option( 'stats_options' ); - $grouped_options[$option] = $value; + $grouped_options_current = (array) get_option( 'stats_options' ); + $grouped_options = $grouped_options_current; + $grouped_options[ $option ] = $value; // If option value was the same, consider it done. - $updated = $grouped_options_current != $grouped_options ? update_option( 'stats_options', $grouped_options ) : true; + $updated = $grouped_options_current !== $grouped_options + ? update_option( 'stats_options', $grouped_options ) + : true; break; case 'akismet_show_user_comments_approved': - - // Save Akismet option '1' or '0' like it's done in akismet/class.akismet-admin.php - $updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? '1' : '0' ) : true; + // Save Akismet option '1' or '0' like it's done in akismet/class.akismet-admin.php. + $updated = get_option( $option ) != $value // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual + ? update_option( $option, $value ? '1' : '0' ) + : true; break; case 'wordpress_api_key': - if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) { - $error = esc_html__( 'Please install Akismet.', 'jetpack' ); + $error = esc_html__( 'Please install Akismet.', 'jetpack' ); $updated = false; break; } if ( ! defined( 'AKISMET_VERSION' ) ) { - $error = esc_html__( 'Please activate Akismet.', 'jetpack' ); + $error = esc_html__( 'Please activate Akismet.', 'jetpack' ); $updated = false; break; } - // Allow to clear the API key field + // Allow to clear the API key field. if ( '' === $value ) { - $updated = get_option( $option ) != $value ? update_option( $option, $value ) : true; + $updated = get_option( $option ) !== $value + ? update_option( $option, $value ) + : true; break; } @@ -868,8 +915,10 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { if ( Akismet::verify_key( $value ) === 'valid' ) { $akismet_user = Akismet_Admin::get_akismet_user( $value ); if ( $akismet_user ) { - if ( in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) ) ) { - $updated = get_option( $option ) != $value ? update_option( $option, $value ) : true; + if ( in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ), true ) ) { + $updated = get_option( $option ) !== $value + ? update_option( $option, $value ) + : true; break; } else { $error = esc_html__( "Akismet user status doesn't allow to update the key", 'jetpack' ); @@ -887,27 +936,36 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { break; case 'google_analytics_tracking_id': - $grouped_options = $grouped_options_current = (array) get_option( 'jetpack_wga' ); - $grouped_options[ 'code' ] = $value; + $grouped_options_current = (array) get_option( 'jetpack_wga' ); + $grouped_options = $grouped_options_current; + $grouped_options['code'] = $value; // If option value was the same, consider it done. - $updated = $grouped_options_current != $grouped_options ? update_option( 'jetpack_wga', $grouped_options ) : true; + $updated = $grouped_options_current !== $grouped_options + ? update_option( 'jetpack_wga', $grouped_options ) + : true; break; case 'dismiss_dash_app_card': case 'dismiss_empty_stats_card': // If option value was the same, consider it done. - $updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ) : true; + $updated = get_option( $option ) != $value // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual -- ensure we support bools or strings saved by update_option. + ? update_option( $option, (bool) $value ) + : true; break; case 'onboarding': jetpack_require_lib( 'widgets' ); // Break apart and set Jetpack onboarding options. - $result = $this->_process_onboarding( (array) $value ); + $result = $this->process_onboarding( (array) $value ); if ( empty( $result ) ) { $updated = true; } else { - $error = sprintf( esc_html__( 'Onboarding failed to process: %s', 'jetpack' ), $result ); + $error = sprintf( + /* Translators: placeholder is a list of error codes. */ + esc_html__( 'Onboarding failed to process: %s', 'jetpack' ), + $result + ); $updated = false; } break; @@ -919,9 +977,15 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { $updated = (string) get_option( $option ) !== (string) $sub_value ? update_option( $option, $sub_value ) : true; break; + case 'jetpack_blocks_disabled': + $updated = (bool) get_option( $option ) !== (bool) $value ? update_option( $option, (bool) $value ) : true; + break; + default: // If option value was the same, consider it done. - $updated = get_option( $option ) != $value ? update_option( $option, $value ) : true; + $updated = get_option( $option ) != $value // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual -- ensure we support scalars or strings saved by update_option. + ? update_option( $option, $value ) + : true; break; } @@ -935,9 +999,9 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { // The option was updated. return rest_ensure_response( $response ); } else { - $invalid_count = count( $invalid ); + $invalid_count = count( $invalid ); $not_updated_count = count( $not_updated ); - $error = ''; + $error = ''; if ( $invalid_count > 0 ) { $error = sprintf( /* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */ @@ -952,7 +1016,9 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { $not_updated_messages[] = sprintf( /* Translators: the first variable is a module option or slug, or setting. The second is the error message . */ __( '%1$s: %2$s', 'jetpack' ), - $not_updated_option, $not_updated_message ); + $not_updated_option, + $not_updated_message + ); } } if ( ! empty( $error ) ) { @@ -961,7 +1027,6 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { if ( ! empty( $not_updated_messages ) ) { $error .= ' ' . join( '. ', $not_updated_messages ); } - } // There was an error because some options were updated but others were invalid or failed to update. return new WP_Error( 'some_updated', esc_html( $error ), array( 'status' => 400 ) ); @@ -978,7 +1043,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { * * @return string Result of onboarding processing and, if there is one, an error message. */ - private function _process_onboarding( $data ) { + private function process_onboarding( $data ) { if ( isset( $data['end'] ) && $data['end'] ) { return Jetpack::invalidate_onboarding_token() ? '' @@ -989,69 +1054,102 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { if ( ! empty( $data['siteTitle'] ) ) { // If option value was the same, consider it done. - if ( ! ( update_option( 'blogname', $data['siteTitle'] ) || get_option( 'blogname' ) == $data['siteTitle'] ) ) { + if ( ! ( + update_option( 'blogname', $data['siteTitle'] ) + || get_option( 'blogname' ) === $data['siteTitle'] + ) ) { $error[] = 'siteTitle'; } } if ( isset( $data['siteDescription'] ) ) { // If option value was the same, consider it done. - if ( ! ( update_option( 'blogdescription', $data['siteDescription'] ) || get_option( 'blogdescription' ) == $data['siteDescription'] ) ) { + if ( ! ( + update_option( 'blogdescription', $data['siteDescription'] ) + || get_option( 'blogdescription' ) === $data['siteDescription'] + ) ) { $error[] = 'siteDescription'; } } $site_title = get_option( 'blogname' ); - $author = get_current_user_id() || 1; + $author = get_current_user_id() || 1; if ( ! empty( $data['siteType'] ) ) { - if ( ! ( update_option( 'jpo_site_type', $data['siteType'] ) || get_option( 'jpo_site_type' ) == $data['siteType'] ) ) { + if ( ! ( + update_option( 'jpo_site_type', $data['siteType'] ) + || get_option( 'jpo_site_type' ) === $data['siteType'] + ) ) { $error[] = 'siteType'; } } if ( isset( $data['homepageFormat'] ) ) { - // If $data['homepageFormat'] is 'posts', we have nothing to do since it's WordPress' default - // if it exists, just update + /* + * If $data['homepageFormat'] is 'posts', + * we have nothing to do since it's WordPress' default + * if it exists, just update + */ $homepage_format = get_option( 'jpo_homepage_format' ); if ( ! $homepage_format || $homepage_format !== $data['homepageFormat'] ) { if ( 'page' === $data['homepageFormat'] ) { - if ( ! ( update_option( 'show_on_front', 'page' ) || get_option( 'show_on_front' ) == 'page' ) ) { + if ( ! ( + update_option( 'show_on_front', 'page' ) + || get_option( 'show_on_front' ) === 'page' + ) ) { $error[] = 'homepageFormat'; } - $home = wp_insert_post( array( - 'post_type' => 'page', - /* translators: this references the home page of a site, also called front page. */ - 'post_title' => esc_html_x( 'Home Page', 'The home page of a website.', 'jetpack' ), - 'post_content' => sprintf( esc_html__( 'Welcome to %s.', 'jetpack' ), $site_title ), - 'post_status' => 'publish', - 'post_author' => $author, - ) ); - if ( 0 == $home ) { + $home = wp_insert_post( + array( + 'post_type' => 'page', + /* translators: this references the home page of a site, also called front page. */ + 'post_title' => esc_html_x( 'Home Page', 'The home page of a website.', 'jetpack' ), + 'post_content' => sprintf( + /* Translators: placeholder is the site title. */ + esc_html__( 'Welcome to %s.', 'jetpack' ), + $site_title + ), + 'post_status' => 'publish', + 'post_author' => $author, + ) + ); + if ( 0 === $home ) { $error[] = 'home insert: 0'; } elseif ( is_wp_error( $home ) ) { - $error[] = 'home creation: '. $home->get_error_message(); + $error[] = 'home creation: ' . $home->get_error_message(); } - if ( ! ( update_option( 'page_on_front', $home ) || get_option( 'page_on_front' ) == $home ) ) { + if ( ! ( + update_option( 'page_on_front', $home ) + || get_option( 'page_on_front' ) === $home + ) ) { $error[] = 'home set'; } - $blog = wp_insert_post( array( - 'post_type' => 'page', - /* translators: this references the page where blog posts are listed. */ - 'post_title' => esc_html_x( 'Blog', 'The blog of a website.', 'jetpack' ), - 'post_content' => sprintf( esc_html__( 'These are the latest posts in %s.', 'jetpack' ), $site_title ), - 'post_status' => 'publish', - 'post_author' => $author, - ) ); - if ( 0 == $blog ) { + $blog = wp_insert_post( + array( + 'post_type' => 'page', + /* translators: this references the page where blog posts are listed. */ + 'post_title' => esc_html_x( 'Blog', 'The blog of a website.', 'jetpack' ), + 'post_content' => sprintf( + /* Translators: placeholder is the site title. */ + esc_html__( 'These are the latest posts in %s.', 'jetpack' ), + $site_title + ), + 'post_status' => 'publish', + 'post_author' => $author, + ) + ); + if ( 0 === $blog ) { $error[] = 'blog insert: 0'; } elseif ( is_wp_error( $blog ) ) { - $error[] = 'blog creation: '. $blog->get_error_message(); + $error[] = 'blog creation: ' . $blog->get_error_message(); } - if ( ! ( update_option( 'page_for_posts', $blog ) || get_option( 'page_for_posts' ) == $blog ) ) { + if ( ! ( + update_option( 'page_for_posts', $blog ) + || get_option( 'page_for_posts' ) === $blog + ) ) { $error[] = 'blog set'; } } else { @@ -1069,7 +1167,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { update_option( 'jpo_homepage_format', $data['homepageFormat'] ); } - // Setup contact page and add a form and/or business info + // Setup contact page and add a form and/or business info. $contact_page = ''; if ( ! empty( $data['addContactForm'] ) && ! get_option( 'jpo_contact_page' ) ) { $contact_form_module_active = Jetpack::is_module_active( 'contact-form' ); @@ -1089,18 +1187,20 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } if ( ! empty( $contact_page ) ) { - $form = wp_insert_post( array( - 'post_type' => 'page', - /* translators: this references a page with contact details and possibly a form. */ - 'post_title' => esc_html_x( 'Contact us', 'Contact page for your website.', 'jetpack' ), - 'post_content' => esc_html__( 'Send us a message!', 'jetpack' ) . "\n" . $contact_page, - 'post_status' => 'publish', - 'post_author' => $author, - ) ); - if ( 0 == $form ) { + $form = wp_insert_post( + array( + 'post_type' => 'page', + /* translators: this references a page with contact details and possibly a form. */ + 'post_title' => esc_html_x( 'Contact us', 'Contact page for your website.', 'jetpack' ), + 'post_content' => esc_html__( 'Send us a message!', 'jetpack' ) . "\n" . $contact_page, + 'post_status' => 'publish', + 'post_author' => $author, + ) + ); + if ( 0 === $form ) { $error[] = 'form insert: 0'; } elseif ( is_wp_error( $form ) ) { - $error[] = 'form creation: '. $form->get_error_message(); + $error[] = 'form creation: ' . $form->get_error_message(); } else { update_option( 'jpo_contact_page', $form ); } @@ -1114,9 +1214,8 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } if ( ! empty( $data['installWooCommerce'] ) ) { - jetpack_require_lib( 'plugins' ); - $wc_install_result = Jetpack_Plugins::install_and_activate_plugin( 'woocommerce' ); - delete_transient( '_wc_activation_redirect' ); // Redirecting to WC setup would kill our users' flow + $wc_install_result = Plugins_Installer::install_and_activate_plugin( 'woocommerce' ); + delete_transient( '_wc_activation_redirect' ); // Redirecting to WC setup would kill our users' flow. if ( is_wp_error( $wc_install_result ) ) { $error[] = 'woocommerce installation'; } @@ -1148,8 +1247,8 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { * @param array $address Array of business address fields. * * @return WP_Error|true True if the data was saved correctly. - */ - static function handle_business_address( $address ) { + */ + private static function handle_business_address( $address ) { $first_sidebar = Jetpack_Widgets::get_first_sidebar(); $widgets_module_active = Jetpack::is_module_active( 'widgets' ); @@ -1161,11 +1260,11 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } if ( $first_sidebar ) { - $title = isset( $address['name'] ) ? sanitize_text_field( $address['name'] ) : ''; - $street = isset( $address['street'] ) ? sanitize_text_field( $address['street'] ) : ''; - $city = isset( $address['city'] ) ? sanitize_text_field( $address['city'] ) : ''; - $state = isset( $address['state'] ) ? sanitize_text_field( $address['state'] ) : ''; - $zip = isset( $address['zip'] ) ? sanitize_text_field( $address['zip'] ) : ''; + $title = isset( $address['name'] ) ? sanitize_text_field( $address['name'] ) : ''; + $street = isset( $address['street'] ) ? sanitize_text_field( $address['street'] ) : ''; + $city = isset( $address['city'] ) ? sanitize_text_field( $address['city'] ) : ''; + $state = isset( $address['state'] ) ? sanitize_text_field( $address['state'] ) : ''; + $zip = isset( $address['zip'] ) ? sanitize_text_field( $address['zip'] ) : ''; $country = isset( $address['country'] ) ? sanitize_text_field( $address['country'] ) : ''; $full_address = implode( ' ', array_filter( array( $street, $city, $state, $zip, $country ) ) ); @@ -1176,12 +1275,12 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { 'phone' => '', 'hours' => '', 'showmap' => false, - 'email' => '' + 'email' => '', ); $widget_updated = ''; if ( ! self::has_business_address_widget( $first_sidebar ) ) { - $widget_updated = Jetpack_Widgets::insert_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar ); + $widget_updated = Jetpack_Widgets::insert_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar ); } else { $widget_updated = Jetpack_Widgets::update_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar ); } @@ -1190,29 +1289,29 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } $address_save = array( - 'name' => $title, - 'street' => $street, - 'city' => $city, - 'state' => $state, - 'zip' => $zip, - 'country' => $country + 'name' => $title, + 'street' => $street, + 'city' => $city, + 'state' => $state, + 'zip' => $zip, + 'country' => $country, ); update_option( 'jpo_business_address', $address_save ); return true; } - // No sidebar to place the widget + // No sidebar to place the widget. return new WP_Error( 'sidebar_not_found', 'No sidebar.', 400 ); } /** * Check whether "Contact Info & Map" widget is present in a given sidebar. * - * @param string $sidebar ID of the sidebar to which the widget will be added. + * @param string $sidebar ID of the sidebar to which the widget will be added. * * @return bool Whether the widget is present in a given sidebar. - */ - static function has_business_address_widget( $sidebar ) { + */ + private static function has_business_address_widget( $sidebar ) { $sidebars_widgets = get_option( 'sidebars_widgets', array() ); if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) { return false; @@ -1251,7 +1350,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } $options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( $params ); foreach ( $options as $option => $definition ) { - if ( in_array( $options[ $option ]['jp_group'], array( 'post-by-email' ) ) ) { + if ( in_array( $options[ $option ]['jp_group'], array( 'post-by-email' ), true ) ) { $module = $options[ $option ]['jp_group']; break; } @@ -1266,10 +1365,23 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } } +/** + * Get detailed data from a specific module. + * + * phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound + */ class Jetpack_Core_API_Module_Data_Endpoint { + // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound + /** + * Process request and return different data based on the module we are interested in. + * + * @param WP_REST_Request $request WP API request. + * + * @return WP_REST_Response|WP_Error A REST response if the request was served successfully, otherwise an error. + */ public function process( $request ) { - switch( $request['slug'] ) { + switch ( $request['slug'] ) { case 'protect': return $this->get_protect_data(); case 'stats': @@ -1290,12 +1402,12 @@ class Jetpack_Core_API_Module_Data_Endpoint { * * @since 4.8.0 * - * @param WP_REST_Request $request + * @param WP_REST_Request $request WP API request. * * @return bool */ public function key_check( $request ) { - switch( $request['service'] ) { + switch ( $request['service'] ) { case 'akismet': $params = $request->get_json_params(); if ( isset( $params['api_key'] ) && ! empty( $params['api_key'] ) ) { @@ -1353,26 +1465,32 @@ class Jetpack_Core_API_Module_Data_Endpoint { public function check_akismet_key( $api_key = '' ) { $akismet_status = $this->akismet_class_exists(); if ( is_wp_error( $akismet_status ) ) { - return rest_ensure_response( array( - 'validKey' => false, - 'invalidKeyCode' => $akismet_status->get_error_code(), - 'invalidKeyMessage' => $akismet_status->get_error_message(), - ) ); + return rest_ensure_response( + array( + 'validKey' => false, + 'invalidKeyCode' => $akismet_status->get_error_code(), + 'invalidKeyMessage' => $akismet_status->get_error_message(), + ) + ); } $key_status = Akismet::check_key_status( empty( $api_key ) ? Akismet::get_api_key() : $api_key ); if ( ! $key_status || 'invalid' === $key_status || 'failed' === $key_status ) { - return rest_ensure_response( array( - 'validKey' => false, - 'invalidKeyCode' => 'invalid_key', - 'invalidKeyMessage' => esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), - ) ); + return rest_ensure_response( + array( + 'validKey' => false, + 'invalidKeyCode' => 'invalid_key', + 'invalidKeyMessage' => esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), + ) + ); } - return rest_ensure_response( array( - 'validKey' => isset( $key_status[1] ) && 'valid' === $key_status[1] - ) ); + return rest_ensure_response( + array( + 'validKey' => isset( $key_status[1] ) && 'valid' === $key_status[1], + ) + ); } /** @@ -1402,7 +1520,8 @@ class Jetpack_Core_API_Module_Data_Endpoint { * @return bool|WP_Error True if Akismet is active and registered. Otherwise, a WP_Error instance with the corresponding error. */ private function akismet_is_active_and_registered() { - if ( is_wp_error( $akismet_exists = $this->akismet_class_exists() ) ) { + $akismet_exists = $this->akismet_class_exists(); + if ( is_wp_error( $akismet_exists ) ) { return $akismet_exists; } @@ -1437,37 +1556,43 @@ class Jetpack_Core_API_Module_Data_Endpoint { // If no parameters were passed. if ( - empty ( $range ) + empty( $range ) || ! in_array( $range, array( 'day', 'week', 'month' ), true ) ) { $range = 'day'; } if ( ! function_exists( 'stats_get_from_restapi' ) ) { - require_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' ); + require_once JETPACK__PLUGIN_DIR . 'modules/stats.php'; } switch ( $range ) { - // This is always called first on page load + // This is always called first on page load. case 'day': $initial_stats = stats_get_from_restapi(); - return rest_ensure_response( array( - 'general' => $initial_stats, - - // Build data for 'day' as if it was stats_get_from_restapi( array(), 'visits?unit=day&quantity=30' ); - 'day' => isset( $initial_stats->visits ) - ? $initial_stats->visits - : array(), - ) ); + return rest_ensure_response( + array( + 'general' => $initial_stats, + + // Build data for 'day' as if it was stats_get_from_restapi( array(), 'visits?unit=day&quantity=30' ). + 'day' => isset( $initial_stats->visits ) + ? $initial_stats->visits + : array(), + ) + ); case 'week': - return rest_ensure_response( array( - 'week' => stats_get_from_restapi( array(), 'visits?unit=week&quantity=14' ), - ) ); + return rest_ensure_response( + array( + 'week' => stats_get_from_restapi( array(), 'visits?unit=week&quantity=14' ), + ) + ); case 'month': - return rest_ensure_response( array( - 'month' => stats_get_from_restapi( array(), 'visits?unit=month&quantity=12&' ), - ) ); + return rest_ensure_response( + array( + 'month' => stats_get_from_restapi( array(), 'visits?unit=month&quantity=12&' ), + ) + ); } } @@ -1491,16 +1616,20 @@ class Jetpack_Core_API_Module_Data_Endpoint { $last_downtime = $monitor->monitor_get_last_downtime(); if ( is_wp_error( $last_downtime ) ) { return $last_downtime; - } else if ( false === strtotime( $last_downtime ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'date' => null, - ) ); + } elseif ( false === strtotime( $last_downtime ) ) { + return rest_ensure_response( + array( + 'code' => 'success', + 'date' => null, + ) + ); } else { - return rest_ensure_response( array( - 'code' => 'success', - 'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ), - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ), + ) + ); } } @@ -1573,8 +1702,8 @@ class Jetpack_Core_API_Module_Data_Endpoint { ); } else { $copy_services = $services; - $last = count( $copy_services ) - 1; - $last_service = $copy_services[ $last ]; + $last = count( $copy_services ) - 1; + $last_service = $copy_services[ $last ]; unset( $copy_services[ $last ] ); $message = esc_html( sprintf( @@ -1586,11 +1715,13 @@ class Jetpack_Core_API_Module_Data_Endpoint { ); } - return rest_ensure_response( array( - 'code' => 'success', - 'message' => $message, - 'services' => $services, - ) ); + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => $message, + 'services' => $services, + ) + ); } /** @@ -1611,41 +1742,49 @@ class Jetpack_Core_API_Module_Data_Endpoint { $vaultpress = new VaultPress(); if ( ! $vaultpress->is_registered() ) { - return rest_ensure_response( array( - 'code' => 'not_registered', - 'message' => esc_html__( 'You need to register for VaultPress.', 'jetpack' ) - ) ); - } - - $data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) ); - if ( false == $data ) { - return rest_ensure_response( array( - 'code' => 'not_registered', - 'message' => esc_html__( 'Could not connect to VaultPress.', 'jetpack' ) - ) ); - } else if ( is_wp_error( $data ) || ! isset( $data->backups->last_backup ) ) { + return rest_ensure_response( + array( + 'code' => 'not_registered', + 'message' => esc_html__( 'You need to register for VaultPress.', 'jetpack' ), + ) + ); + } + + $data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + if ( false === $data ) { + return rest_ensure_response( + array( + 'code' => 'not_registered', + 'message' => esc_html__( 'Could not connect to VaultPress.', 'jetpack' ), + ) + ); + } elseif ( is_wp_error( $data ) || ! isset( $data->backups->last_backup ) ) { return $data; - } else if ( empty( $data->backups->last_backup ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'VaultPress is active and will back up your site soon.', 'jetpack' ), - 'data' => $data, - ) ); + } elseif ( empty( $data->backups->last_backup ) ) { + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'VaultPress is active and will back up your site soon.', 'jetpack' ), + 'data' => $data, + ) + ); } else { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html( - sprintf( - /* translators: placeholder is a unit of time (1 hour, 5 days, ...) */ - esc_html__( 'Your site was successfully backed up %s ago.', 'jetpack' ), - human_time_diff( - $data->backups->last_backup, - current_time( 'timestamp' ) + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html( + sprintf( + /* translators: placeholder is a unit of time (1 hour, 5 days, ...) */ + esc_html__( 'Your site was successfully backed up %s ago.', 'jetpack' ), + human_time_diff( + $data->backups->last_backup, + current_time( 'timestamp' ) // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested -- We cannot switch to time() or another "unix" timestamp option as long as $data->backups->last_backup uses WP timestamps. + ) ) - ) - ), - 'data' => $data, - ) ); + ), + 'data' => $data, + ) + ); } } diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php index 10ae6ca8..f820a6f9 100644 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php +++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php @@ -237,17 +237,19 @@ class Jetpack_Core_API_Site_Endpoint { } // Number of VideoPress videos on the site. - $videopress_attachments = wp_count_attachments( 'video/videopress' ); - if ( - isset( $videopress_attachments->{'video/videopress'} ) - && $videopress_attachments->{'video/videopress'} > 0 - ) { - $benefits[] = array( - 'name' => 'video-hosting', - 'title' => esc_html__( 'Video Hosting', 'jetpack' ), - 'description' => esc_html__( 'Ad-free, lightning-fast videos delivered by Jetpack', 'jetpack' ), - 'value' => absint( $videopress_attachments->{'video/videopress'} ), - ); + if ( Jetpack::is_module_active( 'videopress' ) ) { + $videopress_attachments = wp_count_attachments( 'video/videopress' ); + if ( + isset( $videopress_attachments->{'video/videopress'} ) + && $videopress_attachments->{'video/videopress'} > 0 + ) { + $benefits[] = array( + 'name' => 'video-hosting', + 'title' => esc_html__( 'Video Hosting', 'jetpack' ), + 'description' => esc_html__( 'Ad-free, lightning-fast videos delivered by Jetpack', 'jetpack' ), + 'value' => absint( $videopress_attachments->{'video/videopress'} ), + ); + } } // Number of active Publicize connections. @@ -280,6 +282,14 @@ class Jetpack_Core_API_Site_Endpoint { ); } + if ( Jetpack::is_module_active( 'search' ) && ! class_exists( 'Automattic\\Jetpack\\Search_Plugin\\Jetpack_Search_Plugin' ) ) { + $benefits[] = array( + 'name' => 'search', + 'title' => esc_html__( 'Search', 'jetpack' ), + 'description' => esc_html__( 'Help your visitors find exactly what they are looking for, fast', 'jetpack' ), + ); + } + // Finally, return the whole list of benefits. return rest_ensure_response( array( diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php index ffd62bb3..a9ac3409 100644 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php +++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php @@ -1,11 +1,19 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** - * Widget information getter endpoint. + * Interact with a specific widget via the REST API. + * Currently only supports the Milestone widget. * + * @package automattic/jetpack + */ + +/** + * Widget information getter endpoint. */ class Jetpack_Core_API_Widget_Endpoint { /** + * Get information about a widget that is supported by this endpoint. + * * @since 5.5.0 * * @param WP_REST_Request $request { @@ -18,9 +26,9 @@ class Jetpack_Core_API_Widget_Endpoint { */ public function process( $request ) { $widget_base = _get_widget_id_base( $request['id'] ); - $widget_id = (int) substr( $request['id'], strlen( $widget_base ) + 1 ); + $widget_id = (int) substr( $request['id'], strlen( $widget_base ) + 1 ); - switch( $widget_base ) { + switch ( $widget_base ) { case 'milestone_widget': $instances = get_option( 'widget_milestone_widget', array() ); @@ -30,7 +38,7 @@ class Jetpack_Core_API_Widget_Endpoint { && isset( $instances[ $widget_id ] ) ) { $instance = $instances[ $widget_id ]; - $widget = new Milestone_Widget(); + $widget = new Milestone_Widget(); return $widget->get_widget_data( $instance ); } } diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php index abfc8627..cf72f709 100644 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php +++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php @@ -1,7 +1,12 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * This is the base class for every Core API endpoint that needs an XMLRPC client. * + * @package automattic/jetpack + */ + +/** + * Base class for every Core API endpoint that needs an XMLRPC client. */ abstract class Jetpack_Core_API_XMLRPC_Consumer_Endpoint { @@ -14,10 +19,11 @@ abstract class Jetpack_Core_API_XMLRPC_Consumer_Endpoint { protected $xmlrpc; /** + * Constructor. * * @since 4.3.0 * - * @param Jetpack_IXR_Client $xmlrpc + * @param Jetpack_IXR_Client $xmlrpc Jetpack_IXR_Client instance. */ public function __construct( $xmlrpc = null ) { $this->xmlrpc = $xmlrpc; @@ -36,4 +42,4 @@ abstract class Jetpack_Core_API_XMLRPC_Consumer_Endpoint { } return false; } -}
\ No newline at end of file +} diff --git a/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php b/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php index 778600ef..2c9089a8 100644 --- a/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php +++ b/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php @@ -1,11 +1,12 @@ <?php - -/* +/** * Loader for WP REST API endpoints that are synced with WP.com. * * On WP.com see: * - wp-content/mu-plugins/rest-api.php * - wp-content/rest-api-plugins/jetpack-endpoints/ + * + * @package automattic/jetpack */ /** @@ -15,8 +16,13 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } +/** + * Loop through endpoint files and load them. + * + * @param string $file_pattern Path pattern to the endpoints (pattern must be supported by glob()). + */ function wpcom_rest_api_v2_load_plugin_files( $file_pattern ) { - $plugins = glob( dirname( __FILE__ ) . '/' . $file_pattern ); + $plugins = glob( __DIR__ . '/' . $file_pattern ); if ( ! is_array( $plugins ) ) { return; @@ -27,20 +33,25 @@ function wpcom_rest_api_v2_load_plugin_files( $file_pattern ) { } } -// API v2 plugins: define a class, then call this function. +/** + * API v2 plugins: define a class, then call this function. + * + * @param string $class_name The name of the class to load. + */ function wpcom_rest_api_v2_load_plugin( $class_name ) { global $wpcom_rest_api_v2_plugins; if ( ! isset( $wpcom_rest_api_v2_plugins ) ) { - $_GLOBALS['wpcom_rest_api_v2_plugins'] = $wpcom_rest_api_v2_plugins = array(); + $wpcom_rest_api_v2_plugins = array(); + $_GLOBALS['wpcom_rest_api_v2_plugins'] = array(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable } if ( ! isset( $wpcom_rest_api_v2_plugins[ $class_name ] ) ) { - $wpcom_rest_api_v2_plugins[ $class_name ] = new $class_name; + $wpcom_rest_api_v2_plugins[ $class_name ] = new $class_name(); } } -require dirname( __FILE__ ) . '/class-wpcom-rest-field-controller.php'; +require __DIR__ . '/class-wpcom-rest-field-controller.php'; /** * Load the REST API v2 plugin files during the plugins_loaded action. diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/business-hours.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/business-hours.php index 34798f8e..18d4491f 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/business-hours.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/business-hours.php @@ -1,4 +1,9 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Build localized strings for use with the Business Hours Block. + * + * @package automattic/jetpack + */ /** * Business Hours: Localized week @@ -6,22 +11,32 @@ * @since 7.1 */ class WPCOM_REST_API_V2_Endpoint_Business_Hours extends WP_REST_Controller { - function __construct() { + /** + * Constructor. + */ + public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'business-hours'; // This endpoint *does not* need to connect directly to Jetpack sites. add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } + /** + * Register endpoint route. + */ public function register_routes() { - // GET /sites/<blog_id>/business-hours/localized-week - Return the localized - register_rest_route( $this->namespace, '/' . $this->rest_base . '/localized-week', array( + // GET /sites/<blog_id>/business-hours/localized-week - Return the localized. + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/localized-week', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_localized_week' ), - 'permission_callback' => '__return_true', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_localized_week' ), + 'permission_callback' => '__return_true', + ), ) - ) ); + ); } /** diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php index b8b33d55..1fb0b79a 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php @@ -82,7 +82,7 @@ class WPCOM_REST_API_V2_Endpoint_External_Media extends WP_REST_Controller { * * @var string */ - private static $services_regex = '(?P<service>google_photos|pexels)'; + private static $services_regex = '(?P<service>google_photos|openverse|pexels)'; /** * Temporary filename. diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php index 1c25bb2d..fb10ffab 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php @@ -1,4 +1,10 @@ <?php +/** + * API endpoints to interact with WordPress.com + * to get info from the Mailchimp API for use with the Mailchimp block. + * + * @package automattic/jetpack + */ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Redirect; @@ -11,6 +17,9 @@ use Automattic\Jetpack\Redirect; * @since 7.1 */ class WPCOM_REST_API_V2_Endpoint_Mailchimp extends WP_REST_Controller { + /** + * Constructor. + */ public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'mailchimp'; diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-videopress.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-videopress.php index a7046c12..b4b83ee8 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-videopress.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-videopress.php @@ -31,7 +31,7 @@ class WPCOM_REST_API_V2_Endpoint_VideoPress extends WP_REST_Controller { $this->rest_base . '/meta', array( 'args' => array( - 'id' => array( + 'id' => array( 'description' => __( 'The post id for the attachment.', 'jetpack' ), 'type' => 'int', 'required' => true, @@ -39,36 +39,44 @@ class WPCOM_REST_API_V2_Endpoint_VideoPress extends WP_REST_Controller { return is_numeric( $param ); }, ), - 'title' => array( + 'title' => array( 'description' => __( 'The title of the video.', 'jetpack' ), 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field', ), - 'description' => array( + 'description' => array( 'description' => __( 'The description of the video.', 'jetpack' ), 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field', ), - 'rating' => array( + 'rating' => array( 'description' => __( 'The video content rating. One of G, PG-13 or R-17', 'jetpack' ), 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field', ), - 'display_embed' => array( + 'display_embed' => array( 'description' => __( 'Display the share menu in the player.', 'jetpack' ), 'type' => 'boolean', 'required' => false, 'sanitize_callback' => 'rest_sanitize_boolean', ), - 'allow_download' => array( + 'allow_download' => array( 'description' => __( 'Display download option and allow viewers to download this video', 'jetpack' ), 'type' => 'boolean', 'required' => false, 'sanitize_callback' => 'rest_sanitize_boolean', ), + 'privacy_setting' => array( + 'description' => __( 'How to determine if the video should be public or private', 'jetpack' ), + 'type' => 'int', + 'required' => false, + 'validate_callback' => function ( $param ) { + return is_numeric( $param ); + }, + ), ), 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'videopress_block_update_meta' ), @@ -163,6 +171,14 @@ class WPCOM_REST_API_V2_Endpoint_VideoPress extends WP_REST_Controller { } } + if ( isset( $json_params['privacy_setting'] ) ) { + $privacy_setting = $json_params['privacy_setting']; + if ( ! isset( $meta['videopress']['privacy_setting'] ) || $meta['videopress']['privacy_setting'] !== $privacy_setting ) { + $meta['videopress']['privacy_setting'] = $privacy_setting; + $should_update_meta = true; + } + } + if ( $should_update_meta ) { wp_update_attachment_metadata( $post_id, $meta ); } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php index a10a4056..3f005a8c 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php @@ -1,6 +1,12 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Interact with the list of available block editor extensions (blocks, plugins) + * made available by the Jetpack plugin. + * + * @package automattic/jetpack + */ -/* +/** * Gutenberg: List Available Gutenberg Extensions (Blocks and Plugins) * * [ @@ -14,23 +20,33 @@ * @since 6.9 */ class WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions extends WP_REST_Controller { - function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'gutenberg'; + /** + * Constructor. + */ + public function __construct() { + $this->namespace = 'wpcom/v2'; + $this->rest_base = 'gutenberg'; $this->wpcom_is_site_specific_endpoint = true; add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } + /** + * Register the endpoint route. + */ public function register_routes() { - register_rest_route( $this->namespace, $this->rest_base . '/available-extensions', array( + register_rest_route( + $this->namespace, + $this->rest_base . '/available-extensions', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( 'Jetpack_Gutenberg', 'get_availability' ), - 'permission_callback' => array( $this, 'get_items_permission_check' ), - ), - 'schema' => array( $this, 'get_item_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( 'Jetpack_Gutenberg', 'get_availability' ), + 'permission_callback' => array( $this, 'get_items_permission_check' ), + ), + 'schema' => array( $this, 'get_item_schema' ), + ) + ); } /** diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php index ff3349b8..6b78e7ce 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php @@ -1,23 +1,45 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Example of a WP.com endpoint. + * + * @package automattic/jetpack + */ +/** + * Example endpoint. + */ class WPCOM_REST_API_V2_Endpoint_Hello { + /** + * Constructor. + */ public function __construct() { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } + /** + * Register endpoint route. + */ public function register_routes() { - register_rest_route( 'wpcom/v2', '/hello', array( + register_rest_route( + 'wpcom/v2', + '/hello', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_data' ), - 'permission_callback' => '__return_true', - ), - ) ); + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_data' ), + 'permission_callback' => '__return_true', + ), + ) + ); } - public function get_data( $request ) { + /** + * Get data in response to the endpoint request. + * + * @param WP_REST_Request $request API request. + */ + public function get_data( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return array( 'hello' => 'world' ); } } - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Hello' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php index f56178cc..e26a16e0 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php @@ -38,20 +38,24 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { 'callback' => array( $this, 'get_status' ), 'permission_callback' => array( $this, 'get_status_permission_check' ), 'args' => array( - 'type' => array( + 'type' => array( 'type' => 'string', 'required' => false, 'validate_callback' => function ( $param ) { return in_array( $param, array( 'donation', 'all' ), true ); }, ), - 'source' => array( + 'source' => array( 'type' => 'string', 'required' => false, 'validate_callback' => function ( $param ) { return in_array( $param, array( 'calypso', 'earn', 'gutenberg', 'gutenberg-wpcom' ), true ); }, ), + 'is_editable' => array( + 'type' => 'boolean', + 'required' => false, + ), ), ), ) @@ -65,22 +69,29 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { 'callback' => array( $this, 'create_product' ), 'permission_callback' => array( $this, 'get_status_permission_check' ), 'args' => array( - 'title' => array( + 'title' => array( 'type' => 'string', 'required' => true, ), - 'price' => array( + 'price' => array( 'type' => 'float', 'required' => true, ), - 'currency' => array( + 'currency' => array( 'type' => 'string', 'required' => true, ), - 'interval' => array( + 'interval' => array( 'type' => 'string', 'required' => true, ), + 'is_editable' => array( + 'type' => 'boolean', + 'required' => false, + ), + 'buyer_can_change_amount' => array( + 'type' => 'boolean', + ), ), ), ) @@ -110,27 +121,39 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { /** * Do create a product based on data, or pass request to wpcom. * - * @param object $request - request passed from WP. + * @param WP_REST_Request $request - request passed from WP. * * @return array|WP_Error */ - public function create_product( $request ) { + public function create_product( WP_REST_Request $request ) { + $is_editable = isset( $request['is_editable'] ) ? (bool) $request['is_editable'] : null; + $type = isset( $request['type'] ) ? $request['type'] : null; + $buyer_can_change_amount = isset( $request['buyer_can_change_amount'] ) && (bool) $request['buyer_can_change_amount']; + + $payload = array( + 'title' => $request['title'], + 'price' => $request['price'], + 'currency' => $request['currency'], + 'buyer_can_change_amount' => $buyer_can_change_amount, + 'interval' => $request['interval'], + 'type' => $type, + ); + + // If we pass directly the value "null", it will break the argument validation. + if ( null !== $is_editable ) { + $payload['is_editable'] = $is_editable; + } + if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { jetpack_require_lib( 'memberships' ); $connected_destination_account_id = Jetpack_Memberships::get_connected_account_id(); if ( ! $connected_destination_account_id ) { return new WP_Error( 'no-destination-account', __( 'Please set up a Stripe account for this site first', 'jetpack' ) ); } - $product = Memberships_Product::create( - get_current_blog_id(), - array( - 'title' => $request['title'], - 'price' => $request['price'], - 'currency' => $request['currency'], - 'interval' => $request['interval'], - 'connected_destination_account_id' => $connected_destination_account_id, - ) - ); + + $payload['connected_destination_account_id'] = $connected_destination_account_id; + + $product = Memberships_Product::create( get_current_blog_id(), $payload ); if ( is_wp_error( $product ) ) { return new WP_Error( $product->get_error_code(), __( 'Creating product has failed.', 'jetpack' ) ); } @@ -143,12 +166,7 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { array( 'method' => 'POST', ), - array( - 'title' => $request['title'], - 'price' => $request['price'], - 'currency' => $request['currency'], - 'interval' => $request['interval'], - ) + $payload ); if ( is_wp_error( $response ) ) { if ( $response->get_error_code() === 'missing_token' ) { @@ -175,19 +193,33 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { * @return array|WP_Error */ public function create_products( $request ) { + $is_editable = isset( $request['is_editable'] ) ? (bool) $request['is_editable'] : null; + if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { jetpack_require_lib( 'memberships' ); $connected_destination_account_id = Jetpack_Memberships::get_connected_account_id(); if ( ! $connected_destination_account_id ) { return new WP_Error( 'no-destination-account', __( 'Please set up a Stripe account for this site first', 'jetpack' ) ); } - $result = Memberships_Product::generate_default_products( get_current_blog_id(), $request['type'], $request['currency'], $connected_destination_account_id ); + + $result = Memberships_Product::generate_default_products( get_current_blog_id(), $request['type'], $request['currency'], $connected_destination_account_id, $is_editable ); + if ( is_wp_error( $result ) ) { $status = 'invalid_param' === $result->get_error_code() ? 400 : 500; return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => $status ) ); } return $result; } else { + $payload = array( + 'type' => $request['type'], + 'currency' => $request['currency'], + ); + + // If we pass directly is_editable as null, it would break API argument validation. + if ( null !== $is_editable ) { + $payload['is_editable'] = $is_editable; + } + $blog_id = Jetpack_Options::get_option( 'id' ); $response = Client::wpcom_json_api_request_as_user( "/sites/$blog_id/{$this->rest_base}/products", @@ -195,10 +227,7 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { array( 'method' => 'POST', ), - array( - 'type' => $request['type'], - 'currency' => $request['currency'], - ) + $payload ); if ( is_wp_error( $response ) ) { if ( $response->get_error_code() === 'missing_token' ) { @@ -227,19 +256,29 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { public function get_status( \WP_REST_Request $request ) { $product_type = $request['type']; $source = $request['source']; + $is_editable = ! isset( $request['is_editable'] ) ? null : (bool) $request['is_editable']; + if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { jetpack_require_lib( 'memberships' ); $blog_id = get_current_blog_id(); - return (array) get_memberships_settings_for_site( $blog_id, $product_type ); + return (array) get_memberships_settings_for_site( $blog_id, $product_type, $is_editable ); } else { + $payload = array( + 'type' => $request['type'], + 'source' => $source, + ); + + // If we pass directly is_editable as null, it would break API argument validation. + // This also needs to be converted to int because boolean false is ignored by add_query_arg. + if ( null !== $is_editable ) { + $payload['is_editable'] = (int) $is_editable; + } + $blog_id = Jetpack_Options::get_option( 'id' ); $path = "/sites/$blog_id/{$this->rest_base}/status"; if ( $product_type ) { $path = add_query_arg( - array( - 'type' => $product_type, - 'source' => $source, - ), + $payload, $path ); } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 6e04a289..affa601c 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -1,6 +1,11 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Fetch information about Publicize connections on a site, including tests and connection status. + * + * @package automattic/jetpack + */ -require_once dirname( __FILE__ ) . '/publicize-connections.php'; +require_once __DIR__ . '/publicize-connections.php'; /** * Publicize: List Connection Test Result Data @@ -10,6 +15,9 @@ require_once dirname( __FILE__ ) . '/publicize-connections.php'; * @since 6.8 */ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections { + /** + * Constructor. + */ public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'publicize/connection-test-results'; @@ -74,11 +82,14 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends } /** - * @param WP_REST_Request + * Get list of Publicize Connections. + * + * @param WP_REST_Request $request Full details about the request. + * * @see Publicize::get_publicize_conns_test_results() * @return WP_REST_Response suitable for 1-page collection */ - public function get_items( $request ) { + public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable global $publicize; $items = $this->get_connections(); @@ -117,5 +128,4 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends return $response; } } - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php index 78cb3178..ac6dccf4 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php @@ -1,4 +1,9 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Fetch information about Publicize connections on a site. + * + * @package automattic/jetpack + */ /** * Publicize: List Connections @@ -24,6 +29,9 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Cont */ public $wpcom_is_wpcom_only_endpoint = true; + /** + * Constructor. + */ public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'publicize/connections'; @@ -86,6 +94,8 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Cont } /** + * Schema for the endpoint. + * * @return array */ public function get_item_schema() { @@ -122,7 +132,7 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Cont 'display_name' => $publicize->get_display_name( $service_name, $connection ), 'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '', 'profile_picture' => ! empty( $connection_meta['profile_picture'] ) ? $connection_meta['profile_picture'] : '', - // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- We expect an integer, but do loose comparison below in case some other type is stored. + // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored. 'global' => 0 == $connection_data['user_id'], ); } @@ -132,7 +142,10 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Cont } /** - * @param WP_REST_Request $request + * Get list of connected Publicize connections. + * + * @param WP_REST_Request $request Full details about the request. + * * @return WP_REST_Response suitable for 1-page collection */ public function get_items( $request ) { @@ -152,8 +165,9 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Cont /** * Filters out data based on ?_fields= request parameter * - * @param array $connection - * @param WP_REST_Request $request + * @param array $connection Array of info about a specific Publicize connection. + * @param WP_REST_Request $request Full details about the request. + * * @return array filtered $connection */ public function prepare_item_for_response( $connection, $request ) { @@ -200,5 +214,4 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Cont ); } } - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php index da93dd6e..6fdd1231 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php @@ -1,4 +1,9 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Endpoint used to fetch information to connect to a Publicize service. + * + * @package automattic/jetpack + */ /** * Publicize: List Publicize Services @@ -23,6 +28,9 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Control */ public $wpcom_is_wpcom_only_endpoint = true; + /** + * Constructor. + */ public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'publicize/services'; @@ -49,6 +57,8 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Control } /** + * Schema for the publicize services endpoint. + * * @return array */ public function get_item_schema() { @@ -81,7 +91,8 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Control * * @see Publicize::get_available_service_data() * - * @param WP_REST_Request $request + * @param WP_REST_Request $request Full details about the request. + * * @return WP_REST_Response suitable for 1-page collection */ public function get_items( $request ) { @@ -113,8 +124,9 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Control /** * Filters out data based on ?_fields= request parameter * - * @param array $service - * @param WP_REST_Request $request + * @param array $service UI service connection data for a specific Publicize service. + * @param WP_REST_Request $request Full details about the request. + * * @return array filtered $service */ public function prepare_item_for_response( $service, $request ) { @@ -161,5 +173,4 @@ class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Control ); } } - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Services' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/service-api-keys.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/service-api-keys.php index 8f279ea9..fde032ca 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/service-api-keys.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/service-api-keys.php @@ -1,4 +1,10 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Get and save API keys for a site. + * + * @package automattic/jetpack + */ + /** * Service API Keys: Exposes 3rd party api keys that are used on a site. * @@ -17,13 +23,19 @@ */ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { - function __construct() { + /** + * Constructor. + */ + public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'service-api-keys'; add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } + /** + * Register endpoint routes. + */ public function register_routes() { register_rest_route( 'wpcom/v2', @@ -54,6 +66,9 @@ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { ); } + /** + * Permission check. + */ public static function edit_others_posts_check() { if ( current_user_can( 'edit_others_posts' ) ) { return true; @@ -133,7 +148,7 @@ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { $option = self::key_for_api_service( $service ); $service_api_key = Jetpack_Options::get_option( $option, '' ); $service_api_key_source = 'site'; - }; + } $message = esc_html__( 'API key retrieved successfully.', 'jetpack' ); @@ -160,10 +175,10 @@ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { if ( ! $service ) { return self::service_api_invalid_service_response(); } - $json_params = $request->get_json_params(); - $params = ! empty( $json_params ) ? $json_params : $request->get_body_params(); - $service_api_key = trim( $params['service_api_key'] ); - $option = self::key_for_api_service( $service ); + $json_params = $request->get_json_params(); + $params = ! empty( $json_params ) ? $json_params : $request->get_body_params(); + $service_api_key = trim( $params['service_api_key'] ); + $option = self::key_for_api_service( $service ); $validation = self::validate_service_api_key( $service_api_key, $service, $params ); if ( ! $validation['status'] ) { @@ -211,7 +226,7 @@ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { default: $service_api_key = Jetpack_Options::get_option( $option, '' ); $service_api_key_source = 'site'; - }; + } return array( 'code' => 'success', @@ -298,9 +313,9 @@ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { $mapbox_geocode_response = wp_safe_remote_get( esc_url_raw( $mapbox_geocode_url ) ); $mapbox_geocode_body = wp_remote_retrieve_body( $mapbox_geocode_response ); $mapbox_geocode_json = json_decode( $mapbox_geocode_body ); - if ( isset( $mapbox_geocode_json->message ) && ! isset( $mapbox_geocode_json->query ) ) { + if ( isset( $mapbox_geocode_json->message ) || ! isset( $mapbox_geocode_json->query ) ) { $status = false; - $msg = $mapbox_geocode_json->message; + $msg = isset( $mapbox_geocode_json->message ) ? $mapbox_geocode_json->message : 'Unknown error'; } return array( 'status' => $status, @@ -317,5 +332,4 @@ class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { return $service . '_api_key'; } } - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php index 4c34161c..26be9838 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php @@ -1,18 +1,29 @@ -<?php - -/* - * Plugin Name: WPCOM Add Featured Media URL - * +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * WPCOM Add Featured Media URL * Adds `jetpack_featured_media_url` to post responses + * + * @package automattic/jetpack */ +/** + * Add featured media url to API post responses. + */ class WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL { - function __construct() { + /** + * Constructor. + */ + public function __construct() { add_action( 'rest_api_init', array( $this, 'add_featured_media_url' ) ); } - function add_featured_media_url() { - register_rest_field( 'post', 'jetpack_featured_media_url', + /** + * Add featured media url to post responses. + */ + public function add_featured_media_url() { + register_rest_field( + 'post', + 'jetpack_featured_media_url', array( 'get_callback' => array( $this, 'get_featured_media_url' ), 'update_callback' => null, @@ -21,9 +32,16 @@ class WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL { ); } - function get_featured_media_url( $object, $field_name, $request ) { + /** + * Get featured media url. + * + * @param mixed $object What the endpoint returns. + * @param string $field_name Should always match `->field_name`. + * @param WP_REST_Request $request WP API request. + */ + public function get_featured_media_url( $object, $field_name, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $featured_media_url = ''; - $image_attributes = wp_get_attachment_image_src( + $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $object['id'] ), 'full' ); @@ -33,5 +51,4 @@ class WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL { return $featured_media_url; } } - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php index 47c95b26..acc5a050 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php @@ -1,5 +1,9 @@ -<?php - +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Get subscriber count from Jetpack's Subscriptions module. + * + * @package automattic/jetpack + */ use Automattic\Jetpack\Constants; /** @@ -8,7 +12,10 @@ use Automattic\Jetpack\Constants; * @since 6.9 */ class WPCOM_REST_API_V2_Endpoint_Subscribers extends WP_REST_Controller { - function __construct() { + /** + * Constructor. + */ + public function __construct() { $this->namespace = 'wpcom/v2'; $this->rest_base = 'subscribers'; // This endpoint *does not* need to connect directly to Jetpack sites. @@ -16,17 +23,27 @@ class WPCOM_REST_API_V2_Endpoint_Subscribers extends WP_REST_Controller { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } + /** + * Register API routes. + */ public function register_routes() { // GET /sites/<blog_id>/subscribers/count - Return number of subscribers for this site. - register_rest_route( $this->namespace, '/' . $this->rest_base . '/count', array( + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/count', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_subscriber_count' ), - 'permission_callback' => array( $this, 'readable_permission_check' ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_subscriber_count' ), + 'permission_callback' => array( $this, 'readable_permission_check' ), + ), ) - ) ); + ); } + /** + * Permission check. Only authors can access this endpoint. + */ public function readable_permission_check() { if ( ! current_user_can_for_blog( get_current_blog_id(), 'edit_posts' ) ) { return new WP_Error( 'authorization_required', 'Only users with the permission to edit posts can see the subscriber count.', array( 'status' => 401 ) ); @@ -38,20 +55,20 @@ class WPCOM_REST_API_V2_Endpoint_Subscribers extends WP_REST_Controller { /** * Retrieves subscriber count * - * @param WP_REST_Request $request incoming API request info + * @param WP_REST_Request $request incoming API request info. * @return array data object containing subscriber count */ - public function get_subscriber_count( $request ) { - // Get the most up to date subscriber count when request is not a test + public function get_subscriber_count( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Get the most up to date subscriber count when request is not a test. if ( ! Constants::is_defined( 'TESTING_IN_JETPACK' ) ) { delete_transient( 'wpcom_subscribers_total' ); } - $subscriber_info = Jetpack_Subscriptions_Widget::fetch_subscriber_count(); + $subscriber_info = Jetpack_Subscriptions_Widget::fetch_subscriber_count(); $subscriber_count = $subscriber_info['value']; return array( - 'count' => $subscriber_count + 'count' => $subscriber_count, ); } } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/attachment-fields-videopress.php b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/attachment-fields-videopress.php index 84890c3c..636a1ebe 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/attachment-fields-videopress.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/attachment-fields-videopress.php @@ -1,4 +1,4 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * Extend the REST API functionality for VideoPress users. * @@ -62,7 +62,7 @@ class WPCOM_REST_API_V2_Attachment_VideoPress_Field extends WPCOM_REST_API_V2_Fi * * @return string */ - public function get( $attachment, $request ) { + public function get( $attachment, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { $blog_id = get_current_blog_id(); } else { @@ -131,7 +131,7 @@ class WPCOM_REST_API_V2_Attachment_VideoPress_Field extends WPCOM_REST_API_V2_Fi * * @return null */ - public function update( $value, $object, $request ) { + public function update( $value, $object, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return null; } @@ -144,7 +144,7 @@ class WPCOM_REST_API_V2_Attachment_VideoPress_Field extends WPCOM_REST_API_V2_Fi * * @return true */ - public function get_permission_check( $object, $request ) { + public function get_permission_check( $object, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return true; } @@ -158,7 +158,7 @@ class WPCOM_REST_API_V2_Attachment_VideoPress_Field extends WPCOM_REST_API_V2_Fi * * @return true */ - public function update_permission_check( $value, $object, $request ) { + public function update_permission_check( $value, $object, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return true; } } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/class-wpcom-rest-api-v2-attachment-videopress-data.php b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/class-wpcom-rest-api-v2-attachment-videopress-data.php index 9e508ff5..41ad8ab3 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/class-wpcom-rest-api-v2-attachment-videopress-data.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/class-wpcom-rest-api-v2-attachment-videopress-data.php @@ -93,8 +93,11 @@ class WPCOM_REST_API_V2_Attachment_VideoPress_Data extends WPCOM_REST_API_V2_Fie public function get_videopress_data( $attachment_id, $blog_id ) { $info = video_get_info_by_blogpostid( $blog_id, $attachment_id ); return array( - 'guid' => $info->guid, - 'rating' => $info->rating, + 'guid' => $info->guid, + 'rating' => $info->rating, + 'allow_download' => + isset( $info->allow_download ) && $info->allow_download ? 1 : 0, + 'privacy_setting' => ! isset( $info->privacy_setting ) ? VIDEOPRESS_PRIVACY::SITE_DEFAULT : intval( $info->privacy_setting ), ); } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php index 8e4b282d..e9935fc0 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php @@ -1,4 +1,9 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Handle Publicize connection information for each post. + * + * @package automattic/jetpack + */ /** * Add per-post Publicize Connection data. @@ -25,11 +30,32 @@ * @since 6.8.0 */ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_V2_Field_Controller { + /** + * Array of post types that can handle Publicize. + * + * @var array + */ protected $object_type = array( 'post' ); - protected $field_name = 'jetpack_publicize_connections'; - private $_meta_saved = array(); + /** + * Field name + * + * @var string + */ + protected $field_name = 'jetpack_publicize_connections'; + + /** + * Array of post IDs that have been updated. + * + * @var array + */ + private $meta_saved = array(); + /** + * Used to memoize the updates for a given post. + * + * @var array + */ public $memoized_updates = array(); /** @@ -39,6 +65,9 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ public function register_fields() { $this->object_type = get_post_types_by_support( 'publicize' ); foreach ( $this->object_type as $post_type ) { + if ( $this->is_registered( $post_type ) ) { + continue; + } // Adds meta support for those post types that don't already have it. // Only runs during REST API requests, so it doesn't impact UI. if ( ! post_type_supports( $post_type, 'custom-fields' ) ) { @@ -66,6 +95,9 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ ); } + /** + * Schema for the endpoint. + */ private function post_connection_schema() { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', @@ -118,10 +150,13 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ } /** - * @param int $post_id + * Permission check, based on module availability and user capabilities. + * + * @param int $post_id Post ID. + * * @return true|WP_Error */ - function permission_check( $post_id ) { + public function permission_check( $post_id ) { global $publicize; if ( ! $publicize ) { @@ -146,21 +181,26 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ /** * Getter permission check * - * @param array $post_array Response data from Post Endpoint + * @param mixed $post_array Response from the post endpoint. + * @param WP_REST_Request $request API request. + * * @return true|WP_Error */ - function get_permission_check( $post_array, $request ) { + public function get_permission_check( $post_array, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return $this->permission_check( isset( $post_array['id'] ) ? $post_array['id'] : 0 ); } /** - * Setter permission check + * Setter permission check. + * + * @param mixed $value The new value for the field. + * @param WP_Post $post The post object. + * @param WP_REST_Request $request API request. * - * @param WP_Post $post * @return true|WP_Error */ - public function update_permission_check( $value, $post, $request ) { + public function update_permission_check( $value, $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return $this->permission_check( isset( $post->ID ) ? $post->ID : 0 ); } @@ -169,12 +209,12 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ * * @see Publicize::get_filtered_connection_data() * - * @param array $post_array Response from Post Endpoint - * @param WP_REST_Request + * @param array $post_array Response from Post Endpoint. + * @param WP_REST_Request $request API request. * * @return array List of connections */ - public function get( $post_array, $request ) { + public function get( $post_array, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable global $publicize; if ( ! $publicize ) { @@ -207,8 +247,9 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ * Prior to updating the post, first calculate which Services to * Publicize to and which to skip. * - * @param object $post Post data to insert/update. - * @param WP_REST_Request $request + * @param object $post Post data to insert/update. + * @param WP_REST_Request $request API request. + * * @return Filtered $post */ public function rest_pre_insert( $post, $request ) { @@ -220,7 +261,7 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ if ( is_wp_error( $permission_check ) ) { return $permission_check; } - // memoize + // memoize. $this->get_meta_to_update( $request['jetpack_publicize_connections'], isset( $post->ID ) ? $post->ID : 0 ); if ( isset( $post->ID ) ) { @@ -236,9 +277,9 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ * After creating a new post, update our cached data to reflect * the new post ID. * - * @param WP_Post $post - * @param WP_REST_Request $request - * @param bool $is_new + * @param WP_Post $post Post data to update. + * @param WP_REST_Request $request API request. + * @param bool $is_new Is this a new post. */ public function rest_insert( $post, $request, $is_new ) { if ( ! $is_new ) { @@ -260,6 +301,13 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ unset( $this->memoized_updates[0] ); } + /** + * Get list of meta data to update per post ID. + * + * @param array $requested_connections Publicize conenctions to update. + * Items are either `{ id: (string) }` or `{ service_name: (string) }`. + * @param int $post_id Post ID. + */ protected function get_meta_to_update( $requested_connections, $post_id = 0 ) { global $publicize; @@ -267,15 +315,15 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ return array(); } - if ( isset( $this->memoized_updates[$post_id] ) ) { - return $this->memoized_updates[$post_id]; + if ( isset( $this->memoized_updates[ $post_id ] ) ) { + return $this->memoized_updates[ $post_id ]; } $available_connections = $publicize->get_filtered_connection_data( $post_id ); $changed_connections = array(); - // Build lookup mappings + // Build lookup mappings. $available_connections_by_unique_id = array(); $available_connections_by_service_name = array(); foreach ( $available_connections as $available_connection ) { @@ -287,7 +335,8 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ $available_connections_by_service_name[ $available_connection['service_name'] ][] = $available_connection; } - // Handle { service_name: $service_name, enabled: (bool) } + // Handle { service_name: $service_name, enabled: (bool) }. + // If the service is not available, it will be skipped. foreach ( $requested_connections as $requested_connection ) { if ( ! isset( $requested_connection['service_name'] ) ) { continue; @@ -303,7 +352,7 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ } // Handle { id: $id, enabled: (bool) } - // These override the service_name settings + // These override the service_name settings. foreach ( $requested_connections as $requested_connection ) { if ( ! isset( $requested_connection['id'] ) ) { continue; @@ -316,7 +365,7 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ $changed_connections[ $requested_connection['id'] ] = $requested_connection['enabled']; } - // Set all changed connections to their new value + // Set all changed connections to their new value. foreach ( $changed_connections as $unique_id => $enabled ) { $connection = $available_connections_by_unique_id[ $unique_id ]; @@ -328,16 +377,16 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ } $meta_to_update = array(); - // For all connections, ensure correct post_meta + // For all connections, ensure correct post_meta. foreach ( $available_connections_by_unique_id as $unique_id => $available_connection ) { if ( $available_connection['enabled'] ) { - $meta_to_update[$publicize->POST_SKIP . $unique_id] = null; + $meta_to_update[ $publicize->POST_SKIP . $unique_id ] = null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } else { - $meta_to_update[$publicize->POST_SKIP . $unique_id] = 1; + $meta_to_update[ $publicize->POST_SKIP . $unique_id ] = 1; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } } - $this->memoized_updates[$post_id] = $meta_to_update; + $this->memoized_updates[ $post_id ] = $meta_to_update; return $meta_to_update; } @@ -345,23 +394,23 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ /** * Update the connections slated to be shared to. * - * @param array $requested_connections - * Items are either `{ id: (string) }` or `{ service_name: (string) }` - * @param WP_Post $post - * @param WP_REST_Request + * @param array $requested_connections Publicize conenctions to update. + * Items are either `{ id: (string) }` or `{ service_name: (string) }`. + * @param WP_Post $post Post data. + * @param WP_REST_Request $request API request. */ - public function update( $requested_connections, $post, $request ) { - if ( isset( $this->_meta_saved[ $post->ID ] ) ) { // Make sure we only save it once - per request. + public function update( $requested_connections, $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( isset( $this->meta_saved[ $post->ID ] ) ) { // Make sure we only save it once - per request. return; } foreach ( $this->get_meta_to_update( $requested_connections, $post->ID ) as $meta_key => $meta_value ) { - if ( is_null( $meta_value ) ) { + if ( $meta_value === null ) { delete_post_meta( $post->ID, $meta_key ); } else { update_post_meta( $post->ID, $meta_key, $meta_value ); } } - $this->_meta_saved[ $post->ID ] = true; + $this->meta_saved[ $post->ID ] = true; } } diff --git a/plugins/jetpack/_inc/lib/debugger.php b/plugins/jetpack/_inc/lib/debugger.php index a0e13e76..60eba5cb 100644 --- a/plugins/jetpack/_inc/lib/debugger.php +++ b/plugins/jetpack/_inc/lib/debugger.php @@ -6,15 +6,15 @@ */ /* Jetpack Connection Testing Framework */ -require_once 'debugger/class-jetpack-cxn-test-base.php'; +require_once __DIR__ . '/debugger/class-jetpack-cxn-test-base.php'; /* Jetpack Connection Tests */ -require_once 'debugger/class-jetpack-cxn-tests.php'; +require_once __DIR__ . '/debugger/class-jetpack-cxn-tests.php'; /* Jetpack Debug Data */ -require_once 'debugger/class-jetpack-debug-data.php'; +require_once __DIR__ . '/debugger/class-jetpack-debug-data.php'; /* The "In-Plugin Debugger" admin page. */ -require_once 'debugger/class-jetpack-debugger.php'; +require_once __DIR__ . '/debugger/class-jetpack-debugger.php'; /* General Debugging Functions */ -require_once 'debugger/debug-functions.php'; +require_once __DIR__ . '/debugger/debug-functions.php'; add_filter( 'debug_information', array( 'Jetpack_Debug_Data', 'core_debug_data' ) ); add_filter( 'site_status_tests', 'jetpack_debugger_site_status_tests' ); diff --git a/plugins/jetpack/_inc/lib/debugger/0-load.php b/plugins/jetpack/_inc/lib/debugger/0-load.php deleted file mode 100644 index ad069244..00000000 --- a/plugins/jetpack/_inc/lib/debugger/0-load.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php -/** - * Loading the various functions used for Jetpack Debugging. - * - * @package Jetpack. - */ - -/* Jetpack Connection Testing Framework */ -require_once 'class-jetpack-cxn-test-base.php'; -/* Jetpack Connection Tests */ -require_once 'class-jetpack-cxn-tests.php'; -/* Jetpack Debug Data */ -require_once 'class-jetpack-debug-data.php'; -/* The "In-Plugin Debugger" admin page. */ -require_once 'class-jetpack-debugger.php'; -/* General Debugging Functions */ -require_once 'debug-functions.php'; - -add_filter( 'debug_information', array( 'Jetpack_Debug_Data', 'core_debug_data' ) ); -add_filter( 'site_status_tests', 'jetpack_debugger_site_status_tests' ); -add_action( 'wp_ajax_health-check-jetpack-local_testing_suite', 'jetpack_debugger_ajax_local_testing_suite' ); diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php index b67fc472..370d1d1e 100644 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php +++ b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php @@ -265,7 +265,7 @@ class Jetpack_Debug_Data { if ( isset( $_SERVER[ $header ] ) ) { $debug_info[ $header ] = array( 'label' => 'Server Variable ' . $header, - 'value' => ( $_SERVER[ $header ] ) ? $_SERVER[ $header ] : 'false', + 'value' => empty( $_SERVER[ $header ] ) ? 'false' : filter_var( wp_unslash( $_SERVER[ $header ] ) ), 'private' => true, // This isn't really 'private' information, but we don't want folks to easily paste these into public forums. ); } diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php index 6534c4d3..f8c83a90 100644 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php +++ b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php @@ -20,11 +20,11 @@ class Jetpack_Debugger { * Used in class.jetpack-admin.php. */ public static function disconnect_and_redirect() { - if ( ! ( isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'jp_disconnect' ) ) ) { + if ( ! ( isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'jp_disconnect' ) ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash return; } - if ( isset( $_GET['disconnect'] ) && $_GET['disconnect'] ) { + if ( ! empty( $_GET['disconnect'] ) ) { if ( Jetpack::is_connection_ready() ) { Jetpack::disconnect(); wp_safe_redirect( Jetpack::admin_url() ); diff --git a/plugins/jetpack/_inc/lib/functions.wp-notify.php b/plugins/jetpack/_inc/lib/functions.wp-notify.php index 4963b0f1..344818a7 100644 --- a/plugins/jetpack/_inc/lib/functions.wp-notify.php +++ b/plugins/jetpack/_inc/lib/functions.wp-notify.php @@ -13,7 +13,8 @@ use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Redirect; -// phpcs:disable WordPress.WP.I18n.MissingArgDomain --reason: WP Core string. +// phpcs:disable WordPress.WP.I18n.MissingArgDomain --reason: Code copied from Core, so using Core strings. +// phpcs:disable WordPress.Utils.I18nTextDomainFixer.MissingArgDomain --reason: Code copied from Core, so using Core strings. /** * Short circuits the {@see `wp_notify_postauthor`} function via the `comment_notification_recipients` filter. @@ -50,12 +51,12 @@ function jetpack_notify_postauthor( $emails, $comment_id ) { $notify_author = apply_filters( 'comment_notification_notify_author', false, $comment->comment_ID ); // The comment was left by the author. - if ( $author && ! $notify_author && $comment->user_id == $post->post_author ) { + if ( $author && ! $notify_author && $comment->user_id === $post->post_author ) { unset( $emails[ $author->user_email ] ); } // The author moderated a comment on their own post. - if ( $author && ! $notify_author && get_current_user_id() == $post->post_author ) { + if ( $author && ! $notify_author && get_current_user_id() === $post->post_author ) { unset( $emails[ $author->user_email ] ); } @@ -84,7 +85,10 @@ function jetpack_notify_postauthor( $emails, $comment_id ) { $comment_content = wp_specialchars_decode( $comment->comment_content ); // Original function modified. - $moderate_on_wpcom = ! in_array( false, array_map( 'jetpack_notify_is_user_connected_by_email', $emails ) ); + $moderate_on_wpcom = ! in_array( // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict + false, + array_map( 'jetpack_notify_is_user_connected_by_email', $emails ) + ); switch ( $comment->comment_type ) { case 'trackback': @@ -179,16 +183,16 @@ function jetpack_notify_postauthor( $emails, $comment_id ) { ) . "\r\n"; } - $wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', strtolower( $_SERVER['SERVER_NAME'] ) ); + $wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', strtolower( isset( $_SERVER['SERVER_NAME'] ) ? filter_var( wp_unslash( $_SERVER['SERVER_NAME'] ) ) : '' ) ); - if ( '' == $comment->comment_author ) { + if ( '' === $comment->comment_author ) { $from = "From: \"$blogname\" <$wp_email>"; - if ( '' != $comment->comment_author_email ) { + if ( '' !== $comment->comment_author_email ) { $reply_to = "Reply-To: $comment->comment_author_email"; } } else { $from = "From: \"$comment->comment_author\" <$wp_email>"; - if ( '' != $comment->comment_author_email ) { + if ( '' !== $comment->comment_author_email ) { $reply_to = "Reply-To: \"$comment->comment_author_email\" <$comment->comment_author_email>"; } } @@ -324,7 +328,10 @@ function jetpack_notify_moderator( $notify_moderator, $comment_id ) { $emails = apply_filters( 'comment_moderation_recipients', $emails, $comment_id ); // Original function modified. - $moderate_on_wpcom = ! in_array( false, array_map( 'jetpack_notify_is_user_connected_by_email', $emails ) ); + $moderate_on_wpcom = ! in_array( // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict + false, + array_map( 'jetpack_notify_is_user_connected_by_email', $emails ) + ); $base_wpcom_edit_comment_url = Redirect::get_url( 'calypso-edit-comment', diff --git a/plugins/jetpack/_inc/lib/icalendar-reader.php b/plugins/jetpack/_inc/lib/icalendar-reader.php index 998f4c13..c9f30991 100644 --- a/plugins/jetpack/_inc/lib/icalendar-reader.php +++ b/plugins/jetpack/_inc/lib/icalendar-reader.php @@ -1,15 +1,46 @@ -<?php - +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** - * Gets and renders iCal feeds for the Upcoming Events widget and shortcode + * Get and render iCal feeds. + * Used by the Upcoming Events widget and the [upcomingevents] shortcode. + * + * @package automattic/jetpack */ +/** + * Calendar utilities class. + * + * phpcs:disable PEAR.NamingConventions.ValidClassName.StartWithCapital + */ class iCalendarReader { - + // phpcs:enable PEAR.NamingConventions.ValidClassName.StartWithCapital + // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date -- we manually handle timezones all over the file. + // @todo Verify that we're manually handling timezones *correctly*. We probably need more `DateTime` with `$this->timezone` and maybe `wp_date()` and less `strtotime()` and `date()` and `date_i18n()`. + /** + * Count To Do events in calendar. + * + * @var int + */ public $todo_count = 0; + + /** + * How many events can be found in calendar. + * + * @var int + */ public $event_count = 0; + + /** + * Details about our calendar. + * + * @var array + */ public $cal = array(); - public $_lastKeyWord = ''; + + /** + * Timezone parsed from the iCalendar feed, if any. + * + * @var null|DateTimeZone + */ public $timezone = null; /** @@ -22,24 +53,28 @@ class iCalendarReader { /** * Return an array of events * - * @param string $url (default: '') + * @param string $url (default: '') URL of the iCal feed. + * @param int $count Count the number of events. + * * @return array | false on failure */ public function get_events( $url = '', $count = 5 ) { - $count = (int) $count; + $count = (int) $count; $transient_id = 'icalendar_vcal_' . md5( $url ) . '_' . $count; $vcal = get_transient( $transient_id ); - + $vcal = false; if ( ! empty( $vcal ) ) { - if ( isset( $vcal['TIMEZONE'] ) ) + if ( isset( $vcal['TIMEZONE'] ) ) { $this->timezone = $this->timezone_from_string( $vcal['TIMEZONE'] ); + } if ( isset( $vcal['VEVENT'] ) ) { $vevent = $vcal['VEVENT']; - if ( $count > 0 ) + if ( $count > 0 ) { $vevent = array_slice( $vevent, 0, $count ); + } $this->cal['VEVENT'] = $vevent; @@ -47,8 +82,9 @@ class iCalendarReader { } } - if ( ! $this->parse( $url ) ) + if ( ! $this->parse( $url ) ) { return false; + } $vcal = array(); @@ -61,13 +97,13 @@ class iCalendarReader { if ( ! empty( $this->cal['VEVENT'] ) ) { $vevent = $this->cal['VEVENT']; - // check for recurring events - // $vevent = $this->add_recurring_events( $vevent ); + // check for recurring events. + // $vevent = $this->add_recurring_events( $vevent );. - // remove before caching - no sense in hanging onto the past + // remove before caching - no sense in hanging onto the past. $vevent = $this->filter_past_and_recurring_events( $vevent ); - // order by soonest start date + // order by soonest start date. $vevent = $this->sort_by_recent( $vevent ); $vcal['VEVENT'] = $vevent; @@ -75,16 +111,25 @@ class iCalendarReader { set_transient( $transient_id, $vcal, HOUR_IN_SECONDS ); - if ( !isset( $vcal['VEVENT'] ) ) + if ( ! isset( $vcal['VEVENT'] ) ) { return false; + } - if ( $count > 0 ) + if ( $count > 0 ) { return array_slice( $vcal['VEVENT'], 0, $count ); + } return $vcal['VEVENT']; } - function apply_timezone_offset( $events ) { + /** + * Adjust event's time based on site's timezone. + * + * @param array $events Array of events. + * + * @return array + */ + public function apply_timezone_offset( $events ) { if ( ! $events ) { return $events; } @@ -95,7 +140,7 @@ class iCalendarReader { $offsetted_events = array(); foreach ( $events as $event ) { - // Don't handle all-day events + // Don't handle all-day events. if ( 8 < strlen( $event['DTSTART'] ) ) { $start_time = preg_replace( '/Z$/', '', $event['DTSTART'] ); $start_time = new DateTime( $start_time, $this->timezone ); @@ -106,7 +151,7 @@ class iCalendarReader { $end_time->setTimeZone( $timezone ); $event['DTSTART'] = $start_time->format( 'YmdHis\Z' ); - $event['DTEND'] = $end_time->format( 'YmdHis\Z' ); + $event['DTEND'] = $end_time->format( 'YmdHis\Z' ); } $offsetted_events[] = $event; @@ -115,10 +160,16 @@ class iCalendarReader { return $offsetted_events; } + /** + * Reorganize events into an array of events with standardized data. + * + * @param array $events Array of events. + * + * @return array + */ protected function filter_past_and_recurring_events( $events ) { - $upcoming = array(); + $upcoming = array(); $set_recurring_events = array(); - $recurrences = array(); /** * This filter allows any time to be passed in for testing or changing timezones, etc... * @@ -139,31 +190,31 @@ class iCalendarReader { $duration = 0; } - if ( isset( $event['RRULE'] ) && $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) { + if ( isset( $event['RRULE'] ) && $this->timezone->getName() && 8 !== strlen( $event['DTSTART'] ) ) { try { - $adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone('UTC') ); + $adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone( 'UTC' ) ); $adjusted_time->setTimeZone( new DateTimeZone( $this->timezone->getName() ) ); - $event['DTSTART'] = $adjusted_time->format('Ymd\THis'); - $date_from_ics = strtotime( $event['DTSTART'] ); + $event['DTSTART'] = $adjusted_time->format( 'Ymd\THis' ); + $date_from_ics = strtotime( $event['DTSTART'] ); $event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); - } catch ( Exception $e ) { - // Invalid argument to DateTime + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Invalid argument to DateTime. } if ( isset( $event['EXDATE'] ) ) { $exdates = array(); foreach ( (array) $event['EXDATE'] as $exdate ) { try { - $adjusted_time = new DateTime( $exdate, new DateTimeZone('UTC') ); + $adjusted_time = new DateTime( $exdate, new DateTimeZone( 'UTC' ) ); $adjusted_time->setTimeZone( new DateTimeZone( $this->timezone->getName() ) ); - if ( 8 == strlen( $event['DTSTART'] ) ) { + if ( 8 === strlen( $event['DTSTART'] ) ) { $exdates[] = $adjusted_time->format( 'Ymd' ); } else { $exdates[] = $adjusted_time->format( 'Ymd\THis' ); } - } catch ( Exception $e ) { - // Invalid argument to DateTime + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Invalid argument to DateTime. } } $event['EXDATE'] = $exdates; @@ -176,76 +227,95 @@ class iCalendarReader { continue; } - // Process events with RRULE before other events - $rrule = isset( $event['RRULE'] ) ? $event['RRULE'] : false ; - $uid = $event['UID']; + // Process events with RRULE before other events. + $rrule = isset( $event['RRULE'] ) ? $event['RRULE'] : false; + $uid = $event['UID']; - if ( $rrule && ! in_array( $uid, $set_recurring_events ) ) { + if ( $rrule && ! in_array( $uid, $set_recurring_events, true ) ) { - // Break down the RRULE into digestible chunks + // Break down the RRULE into digestible chunks. $rrule_array = array(); - foreach ( explode( ";", $event['RRULE'] ) as $rline ) { - list( $rkey, $rvalue ) = explode( "=", $rline, 2 ); - $rrule_array[$rkey] = $rvalue; + foreach ( explode( ';', $event['RRULE'] ) as $rline ) { + list( $rkey, $rvalue ) = explode( '=', $rline, 2 ); + $rrule_array[ $rkey ] = $rvalue; } - $interval = ( isset( $rrule_array['INTERVAL'] ) ) ? $rrule_array['INTERVAL'] : 1; + $interval = ( isset( $rrule_array['INTERVAL'] ) ) ? $rrule_array['INTERVAL'] : 1; $rrule_count = ( isset( $rrule_array['COUNT'] ) ) ? $rrule_array['COUNT'] : 0; - $until = ( isset( $rrule_array['UNTIL'] ) ) ? strtotime( $rrule_array['UNTIL'] ) : strtotime( '+1 year', $current ); + $until = ( isset( $rrule_array['UNTIL'] ) ) ? strtotime( $rrule_array['UNTIL'] ) : strtotime( '+1 year', $current ); - // Used to bound event checks + // Used to bound event checks. $echo_limit = 10; - $noop = false; + $noop = false; - // Set bydays for the event + // Set bydays for the event. $weekdays = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); - $bydays = $weekdays; + $bydays = $weekdays; - // Calculate a recent start date for incrementing depending on the frequency and interval + // Calculate a recent start date for incrementing depending on the frequency and interval. switch ( $rrule_array['FREQ'] ) { case 'DAILY': - $frequency = 'day'; + $frequency = 'day'; $echo_limit = 10; if ( $date_from_ics >= $current ) { $recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) ); } else { - // Interval and count + // Interval and count. $catchup = floor( ( $current - strtotime( $event['DTSTART'] ) ) / ( $interval * DAY_IN_SECONDS ) ); if ( $rrule_count && $catchup > 0 ) { if ( $catchup < $rrule_count ) { - $rrule_count = $rrule_count - $catchup; - $recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' days', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); + $rrule_count = $rrule_count - $catchup; + $recurring_event_date_start = date( + 'Ymd', + strtotime( + '+ ' . ( $interval * $catchup ) . ' days', + strtotime( $event['DTSTART'] ) + ) + ) . date( + '\THis', + strtotime( $event['DTSTART'] ) + ); } else { $noop = true; } } else { - $recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' days', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); + $recurring_event_date_start = date( + 'Ymd', + strtotime( + '+ ' . ( $interval * $catchup ) . ' days', + strtotime( $event['DTSTART'] ) + ) + ) . date( + '\THis', + strtotime( $event['DTSTART'] ) + ); } } break; case 'WEEKLY': - $frequency = 'week'; + $frequency = 'week'; $echo_limit = 4; - // BYDAY exception to current date + // BYDAY exception to current date. $day = false; if ( ! isset( $rrule_array['BYDAY'] ) ) { - $day = $rrule_array['BYDAY'] = strtoupper( substr( date( 'D', strtotime( $event['DTSTART'] ) ), 0, 2 ) ); + $rrule_array['BYDAY'] = strtoupper( substr( date( 'D', strtotime( $event['DTSTART'] ) ), 0, 2 ) ); + $day = $rrule_array['BYDAY']; } $bydays = explode( ',', $rrule_array['BYDAY'] ); if ( $date_from_ics >= $current ) { $recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) ); } else { - // Interval and count + // Interval and count. $catchup = floor( ( $current - strtotime( $event['DTSTART'] ) ) / ( $interval * WEEK_IN_SECONDS ) ); if ( $rrule_count && $catchup > 0 ) { if ( ( $catchup * count( $bydays ) ) < $rrule_count ) { - $rrule_count = $rrule_count - ( $catchup * count( $bydays ) ); // Estimate current event count + $rrule_count = $rrule_count - ( $catchup * count( $bydays ) ); // Estimate current event count. $recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' weeks', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); } else { $noop = true; @@ -255,34 +325,48 @@ class iCalendarReader { } } - // Set to Sunday start + // Set to Sunday start. if ( ! $noop && 'SU' !== strtoupper( substr( date( 'D', strtotime( $recurring_event_date_start ) ), 0, 2 ) ) ) { - $recurring_event_date_start = date( 'Ymd', strtotime( "last Sunday", strtotime( $recurring_event_date_start ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); + $recurring_event_date_start = date( 'Ymd', strtotime( 'last Sunday', strtotime( $recurring_event_date_start ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); } break; case 'MONTHLY': - $frequency = 'month'; + $frequency = 'month'; $echo_limit = 1; if ( $date_from_ics >= $current ) { $recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) ); } else { - // Describe the date in the month + // Describe the date in the month. if ( isset( $rrule_array['BYDAY'] ) ) { - $day_number = substr( $rrule_array['BYDAY'], 0, 1 ); - $week_day = substr( $rrule_array['BYDAY'], 1 ); - $day_cardinals = array( 1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth' ); - $weekdays = array( 'SU' => 'Sunday', 'MO' => 'Monday', 'TU' => 'Tuesday', 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', 'SA' => 'Saturday' ); + $day_number = substr( $rrule_array['BYDAY'], 0, 1 ); + $week_day = substr( $rrule_array['BYDAY'], 1 ); + $day_cardinals = array( + 1 => 'first', + 2 => 'second', + 3 => 'third', + 4 => 'fourth', + 5 => 'fifth', + ); + $weekdays = array( + 'SU' => 'Sunday', + 'MO' => 'Monday', + 'TU' => 'Tuesday', + 'WE' => 'Wednesday', + 'TH' => 'Thursday', + 'FR' => 'Friday', + 'SA' => 'Saturday', + ); $event_date_desc = "{$day_cardinals[$day_number]} {$weekdays[$week_day]} of "; } else { $event_date_desc = date( 'd ', strtotime( $event['DTSTART'] ) ); } - // Interval only + // Interval only. if ( $interval > 1 ) { $catchup = 0; - $maybe = strtotime( $event['DTSTART'] ); + $maybe = strtotime( $event['DTSTART'] ); while ( $maybe < $current ) { $maybe = strtotime( '+ ' . ( $interval * $catchup ) . ' months', strtotime( $event['DTSTART'] ) ); $catchup++; @@ -292,7 +376,7 @@ class iCalendarReader { $recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', $current ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); } - // Add one interval if necessary + // Add one interval if necessary. if ( strtotime( $recurring_event_date_start ) < $current ) { if ( $interval > 1 ) { $recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', strtotime( '+ ' . ( $interval * $catchup ) . ' months', strtotime( $event['DTSTART'] ) ) ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); @@ -301,8 +385,8 @@ class iCalendarReader { $adjustment = new DateTime( date( 'Y-m-d', $current ) ); $adjustment->modify( 'first day of next month' ); $recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . $adjustment->format( 'F Y' ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); - } catch ( Exception $e ) { - // Invalid argument to DateTime + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Invalid argument to DateTime. } } } @@ -310,20 +394,20 @@ class iCalendarReader { break; case 'YEARLY': - $frequency = 'year'; + $frequency = 'year'; $echo_limit = 1; if ( $date_from_ics >= $current ) { - $recurring_event_date_start = date( "Ymd\THis", strtotime( $event['DTSTART'] ) ); + $recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) ); } else { - $recurring_event_date_start = date( 'Y', $current ) . date( "md\THis", strtotime( $event['DTSTART'] ) ); + $recurring_event_date_start = date( 'Y', $current ) . date( 'md\THis', strtotime( $event['DTSTART'] ) ); if ( strtotime( $recurring_event_date_start ) < $current ) { try { $next = new DateTime( date( 'Y-m-d', $current ) ); $next->modify( 'first day of next year' ); - $recurring_event_date_start = $next->format( 'Y' ) . date ( 'md\THis', strtotime( $event['DTSTART'] ) ); - } catch ( Exception $e ) { - // Invalid argument to DateTime + $recurring_event_date_start = $next->format( 'Y' ) . date( 'md\THis', strtotime( $event['DTSTART'] ) ); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Invalid argument to DateTime. } } } @@ -333,94 +417,107 @@ class iCalendarReader { $frequency = false; } - if ( $frequency !== false && ! $noop ) { + if ( false !== $frequency && ! $noop ) { $count_counter = 1; - // If no COUNT limit, go to 10 + // If no COUNT limit, go to 10. if ( empty( $rrule_count ) ) { $rrule_count = 10; } - // Set up EXDATE handling for the event + // Set up EXDATE handling for the event. $exdates = ( isset( $event['EXDATE'] ) ) ? $event['EXDATE'] : array(); for ( $i = 1; $i <= $echo_limit; $i++ ) { - // Weeks need a daily loop and must check for inclusion in BYDAYS - if ( 'week' == $frequency ) { + // Weeks need a daily loop and must check for inclusion in BYDAYS. + if ( 'week' === $frequency ) { $byday_event_date_start = strtotime( $recurring_event_date_start ); foreach ( $weekdays as $day ) { $event_start_timestamp = $byday_event_date_start; - $start_time = date( 'His', $event_start_timestamp ); - $event_end_timestamp = $event_start_timestamp + $duration; - $end_time = date( 'His', $event_end_timestamp ); - if ( 8 == strlen( $event['DTSTART'] ) ) { + $start_time = date( 'His', $event_start_timestamp ); + $event_end_timestamp = $event_start_timestamp + $duration; + $end_time = date( 'His', $event_end_timestamp ); + if ( 8 === strlen( $event['DTSTART'] ) ) { $exdate_compare = date( 'Ymd', $event_start_timestamp ); } else { $exdate_compare = date( 'Ymd\THis', $event_start_timestamp ); } - if ( in_array( $day, $bydays ) && $event_end_timestamp > $current && $event_start_timestamp < $until && $count_counter <= $rrule_count && $event_start_timestamp >= $date_from_ics && ! in_array( $exdate_compare, $exdates ) ) { - if ( 8 == strlen( $event['DTSTART'] ) ) { + if ( + in_array( $day, $bydays, true ) + && $event_end_timestamp > $current + && $event_start_timestamp < $until + && $count_counter <= $rrule_count + && $event_start_timestamp >= $date_from_ics + && ! in_array( $exdate_compare, $exdates, true ) + ) { + if ( 8 === strlen( $event['DTSTART'] ) ) { $event['DTSTART'] = date( 'Ymd', $event_start_timestamp ); - $event['DTEND'] = date( 'Ymd', $event_end_timestamp ); + $event['DTEND'] = date( 'Ymd', $event_end_timestamp ); } else { $event['DTSTART'] = date( 'Ymd\THis', $event_start_timestamp ); - $event['DTEND'] = date( 'Ymd\THis', $event_end_timestamp ); + $event['DTEND'] = date( 'Ymd\THis', $event_end_timestamp ); } - if ( $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) { + if ( $this->timezone->getName() && 8 !== strlen( $event['DTSTART'] ) ) { try { $adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone( $this->timezone->getName() ) ); $adjusted_time->setTimeZone( new DateTimeZone( 'UTC' ) ); - $event['DTSTART'] = $adjusted_time->format('Ymd\THis'); + $event['DTSTART'] = $adjusted_time->format( 'Ymd\THis' ); $event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); - } catch ( Exception $e ) { - // Invalid argument to DateTime + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Invalid argument to DateTime. } } $upcoming[] = $event; $count_counter++; } - // Move forward one day + // Move forward one day. $byday_event_date_start = strtotime( date( 'Ymd\T', strtotime( '+ 1 day', $event_start_timestamp ) ) . $start_time ); } - // Restore first event timestamp + // Restore first event timestamp. $event_start_timestamp = strtotime( $recurring_event_date_start ); } else { $event_start_timestamp = strtotime( $recurring_event_date_start ); - $start_time = date( 'His', $event_start_timestamp ); - $event_end_timestamp = $event_start_timestamp + $duration; - $end_time = date( 'His', $event_end_timestamp ); - if ( 8 == strlen( $event['DTSTART'] ) ) { + $start_time = date( 'His', $event_start_timestamp ); + $event_end_timestamp = $event_start_timestamp + $duration; + $end_time = date( 'His', $event_end_timestamp ); + if ( 8 === strlen( $event['DTSTART'] ) ) { $exdate_compare = date( 'Ymd', $event_start_timestamp ); } else { $exdate_compare = date( 'Ymd\THis', $event_start_timestamp ); } - if ( $event_end_timestamp > $current && $event_start_timestamp < $until && $count_counter <= $rrule_count && $event_start_timestamp >= $date_from_ics && ! in_array( $exdate_compare, $exdates ) ) { - if ( 8 == strlen( $event['DTSTART'] ) ) { + if ( + $event_end_timestamp > $current + && $event_start_timestamp < $until + && $count_counter <= $rrule_count + && $event_start_timestamp >= $date_from_ics + && ! in_array( $exdate_compare, $exdates, true ) + ) { + if ( 8 === strlen( $event['DTSTART'] ) ) { $event['DTSTART'] = date( 'Ymd', $event_start_timestamp ); - $event['DTEND'] = date( 'Ymd', $event_end_timestamp ); + $event['DTEND'] = date( 'Ymd', $event_end_timestamp ); } else { $event['DTSTART'] = date( 'Ymd\T', $event_start_timestamp ) . $start_time; - $event['DTEND'] = date( 'Ymd\T', $event_end_timestamp ) . $end_time; + $event['DTEND'] = date( 'Ymd\T', $event_end_timestamp ) . $end_time; } - if ( $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) { + if ( $this->timezone->getName() && 8 !== strlen( $event['DTSTART'] ) ) { try { $adjusted_time = new DateTime( $event['DTSTART'], new DateTimeZone( $this->timezone->getName() ) ); $adjusted_time->setTimeZone( new DateTimeZone( 'UTC' ) ); - $event['DTSTART'] = $adjusted_time->format('Ymd\THis'); + $event['DTSTART'] = $adjusted_time->format( 'Ymd\THis' ); $event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); - } catch ( Exception $e ) { - // Invalid argument to DateTime + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Invalid argument to DateTime. } } $upcoming[] = $event; @@ -428,25 +525,24 @@ class iCalendarReader { } } - // Set up next interval and reset $event['DTSTART'] and $event['DTEND'], keeping timestamps intact + // Set up next interval and reset $event['DTSTART'] and $event['DTEND'], keeping timestamps intact. $next_start_timestamp = strtotime( "+ {$interval} {$frequency}s", $event_start_timestamp ); - if ( 8 == strlen( $event['DTSTART'] ) ) { + if ( 8 === strlen( $event['DTSTART'] ) ) { $event['DTSTART'] = date( 'Ymd', $next_start_timestamp ); - $event['DTEND'] = date( 'Ymd', strtotime( $event['DTSTART'] ) + $duration ); + $event['DTEND'] = date( 'Ymd', strtotime( $event['DTSTART'] ) + $duration ); } else { $event['DTSTART'] = date( 'Ymd\THis', $next_start_timestamp ); - $event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); + $event['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); } - // Move recurring event date forward + // Move recurring event date forward. $recurring_event_date_start = $event['DTSTART']; } $set_recurring_events[] = $uid; } - } else { - // Process normal events + // Process normal events. if ( strtotime( isset( $event['DTEND'] ) ? $event['DTEND'] : $event['DTSTART'] ) >= $current ) { $upcoming[] = $event; } @@ -458,42 +554,53 @@ class iCalendarReader { /** * Parse events from an iCalendar feed * - * @param string $url (default: '') + * @param string $url (default: ''). * @return array | false on failure */ public function parse( $url = '' ) { - $cache_group = 'icalendar_reader_parse'; + $cache_group = 'icalendar_reader_parse'; $disable_get_key = 'disable:' . md5( $url ); - // Check to see if previous attempts have failed - if ( false !== wp_cache_get( $disable_get_key, $cache_group ) ) + // Check to see if previous attempts have failed. + if ( false !== wp_cache_get( $disable_get_key, $cache_group ) ) { return false; + } - // rewrite webcal: URI schem to HTTP - $url = preg_replace('/^webcal/', 'http', $url ); - // try to fetch - $r = wp_remote_get( $url, array( 'timeout' => 3, 'sslverify' => false ) ); + // rewrite webcal: URI schem to HTTP. + $url = preg_replace( '/^webcal/', 'http', $url ); + // try to fetch. + $r = wp_remote_get( + $url, + array( + 'timeout' => 3, + 'sslverify' => false, + ) + ); if ( 200 !== wp_remote_retrieve_response_code( $r ) ) { - // We were unable to fetch any content, so don't try again for another 60 seconds + // We were unable to fetch any content, so don't try again for another 60 seconds. wp_cache_set( $disable_get_key, 1, $cache_group, 60 ); return false; } $body = wp_remote_retrieve_body( $r ); - if ( empty( $body ) ) + if ( empty( $body ) ) { return false; + } - $body = str_replace( "\r\n", "\n", $body ); + $body = str_replace( "\r\n", "\n", $body ); $lines = preg_split( "/\n(?=[A-Z])/", $body ); - if ( empty( $lines ) ) + if ( empty( $lines ) ) { return false; + } - if ( false === stristr( $lines[0], 'BEGIN:VCALENDAR' ) ) + if ( false === stristr( $lines[0], 'BEGIN:VCALENDAR' ) ) { return false; + } + $type = ''; foreach ( $lines as $line ) { - $add = $this->key_value_from_string( $line ); + $add = $this->key_value_from_string( $line ); if ( ! $add ) { $this->add_component( $type, false, $line ); continue; @@ -529,12 +636,17 @@ class iCalendarReader { } break; case 'TZID': - if ( 'VTIMEZONE' == $type && ! $this->timezone ) + if ( + 'VTIMEZONE' === $type + && ! $this->timezone + ) { $this->timezone = $this->timezone_from_string( $value ); + } break; case 'X-WR-TIMEZONE': - if ( ! $this->timezone ) + if ( ! $this->timezone ) { $this->timezone = $this->timezone_from_string( $value ); + } break; default: $this->add_component( $type, $keyword, $value ); @@ -542,7 +654,7 @@ class iCalendarReader { } } - // Filter for RECURRENCE-IDs + // Filter for RECURRENCE-IDs. $recurrences = array(); if ( array_key_exists( 'VEVENT', $this->cal ) ) { foreach ( $this->cal['VEVENT'] as $event ) { @@ -551,8 +663,12 @@ class iCalendarReader { } } foreach ( $recurrences as $recurrence ) { - for ( $i = 0; $i < count( $this->cal['VEVENT'] ); $i++ ) { - if ( $this->cal['VEVENT'][ $i ]['UID'] == $recurrence['UID'] && ! isset( $this->cal['VEVENT'][ $i ]['RECURRENCE-ID'] ) ) { + $count_vevent = count( $this->cal['VEVENT'] ); + for ( $i = 0; $i < $count_vevent; $i++ ) { + if ( + $this->cal['VEVENT'][ $i ]['UID'] === $recurrence['UID'] + && ! isset( $this->cal['VEVENT'][ $i ]['RECURRENCE-ID'] ) + ) { $this->cal['VEVENT'][ $i ]['EXDATE'][] = $recurrence['RECURRENCE-ID']; break; } @@ -566,14 +682,15 @@ class iCalendarReader { /** * Parse key:value from a string * - * @param string $text (default: '') + * @param string $text (default: ''). * @return array */ public function key_value_from_string( $text = '' ) { preg_match( '/([^:]+)(;[^:]+)?[:]([\w\W]*)/', $text, $matches ); - if ( 0 == count( $matches ) ) + if ( 0 === count( $matches ) ) { return false; + } return array( $matches[1], $matches[3] ); } @@ -581,7 +698,7 @@ class iCalendarReader { /** * Convert a timezone name into a timezone object. * - * @param string $text Timezone name. Example: America/Chicago + * @param string $text Timezone name. Example: America/Chicago. * @return object|null A DateTimeZone object if the conversion was successful. */ private function timezone_from_string( $text ) { @@ -602,21 +719,21 @@ class iCalendarReader { /** * Add a component to the calendar array * - * @param string $component (default: '') - * @param string $keyword (default: '') - * @param string $value (default: '') + * @param string $component (default: ''). + * @param bool|string $keyword (default: ''). + * @param string $value (default: ''). * @return void */ public function add_component( $component = '', $keyword = '', $value = '' ) { - if ( false == $keyword ) { + if ( ! $keyword ) { $keyword = $this->last_keyword; switch ( $component ) { - case 'VEVENT': - $value = $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ] . $value; - break; - case 'VTODO' : - $value = $this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ] . $value; - break; + case 'VEVENT': + $value = $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ] . $value; + break; + case 'VTODO': + $value = $this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ] . $value; + break; } } @@ -632,58 +749,58 @@ class iCalendarReader { * EXDATE;TZID=Pacific Standard Time:20120615T140000,20120629T140000,20120706T140000 */ - // Always store EXDATE as an array + // Always store EXDATE as an array. if ( stristr( $keyword, 'EXDATE' ) ) { $value = explode( ',', $value ); } - // Adjust DTSTART, DTEND, and EXDATE according to their TZID if set + // Adjust DTSTART, DTEND, and EXDATE according to their TZID if set. if ( strpos( $keyword, ';' ) && ( stristr( $keyword, 'DTSTART' ) || stristr( $keyword, 'DTEND' ) || stristr( $keyword, 'EXDATE' ) || stristr( $keyword, 'RECURRENCE-ID' ) ) ) { $keyword = explode( ';', $keyword ); $tzid = false; - if ( 2 == count( $keyword ) ) { + if ( 2 === count( $keyword ) ) { $tparam = $keyword[1]; - if ( strpos( $tparam, "TZID" ) !== false ) { + if ( strpos( $tparam, 'TZID' ) !== false ) { $tzid = $this->timezone_from_string( str_replace( 'TZID=', '', $tparam ) ); } } - // Normalize all times to default UTC + // Normalize all times to default UTC. if ( $tzid ) { $adjusted_times = array(); foreach ( (array) $value as $v ) { try { $adjusted_time = new DateTime( $v, $tzid ); $adjusted_time->setTimeZone( new DateTimeZone( 'UTC' ) ); - $adjusted_times[] = $adjusted_time->format('Ymd\THis'); + $adjusted_times[] = $adjusted_time->format( 'Ymd\THis' ); } catch ( Exception $e ) { - // Invalid argument to DateTime + // Invalid argument to DateTime. return; } } $value = $adjusted_times; } - // Format for adding to event + // Format for adding to event. $keyword = $keyword[0]; - if ( 'EXDATE' != $keyword ) { + if ( 'EXDATE' !== $keyword ) { $value = implode( (array) $value ); } } foreach ( (array) $value as $v ) { - switch ($component) { + switch ( $component ) { case 'VTODO': - if ( 'EXDATE' == $keyword ) { + if ( 'EXDATE' === $keyword ) { $this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ][] = $v; } else { $this->cal[ $component ][ $this->todo_count - 1 ][ $keyword ] = $v; } break; case 'VEVENT': - if ( 'EXDATE' == $keyword ) { + if ( 'EXDATE' === $keyword ) { $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ][] = $v; } else { $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ] = $v; @@ -700,19 +817,19 @@ class iCalendarReader { /** * Escape strings with wp_kses, allow links * - * @param string $string (default: '') + * @param string $string (default: '') The string to escape. * @return string */ public function escape( $string = '' ) { - // Unfold content lines per RFC 5545 + // Unfold content lines per RFC 5545. $string = str_replace( "\n\t", '', $string ); $string = str_replace( "\n ", '', $string ); $allowed_html = array( 'a' => array( 'href' => array(), - 'title' => array() - ) + 'title' => array(), + ), ); $allowed_tags = ''; @@ -729,43 +846,62 @@ class iCalendarReader { /** * Render the events * - * @param string $url (default: '') - * @param string $context (default: 'widget') or 'shortcode' + * @param string $url (default: '') URL of the iCal feed. + * @param array $args Event options. + * * @return mixed bool|string false on failure, rendered HTML string on success. */ public function render( $url = '', $args = array() ) { - $args = wp_parse_args( $args, array( - 'context' => 'widget', - 'number' => 5 - ) ); + $args = wp_parse_args( + $args, + array( + 'context' => 'widget', + 'number' => 5, + ) + ); $events = $this->get_events( $url, $args['number'] ); $events = $this->apply_timezone_offset( $events ); - if ( empty( $events ) ) + if ( empty( $events ) ) { return false; + } ob_start(); - if ( 'widget' == $args['context'] ) : ?> + if ( 'widget' === $args['context'] ) : ?> <ul class="upcoming-events"> <?php foreach ( $events as $event ) : ?> <li> - <strong class="event-summary"><?php echo $this->escape( stripslashes( $event['SUMMARY'] ) ); ?></strong> - <span class="event-when"><?php echo $this->formatted_date( $event ); ?></span> + <strong class="event-summary"> + <?php + echo $this->escape( stripslashes( $event['SUMMARY'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- this method is built to escape. + ?> + </strong> + <span class="event-when"><?php echo esc_html( $this->formatted_date( $event ) ); ?></span> <?php if ( ! empty( $event['LOCATION'] ) ) : ?> - <span class="event-location"><?php echo $this->escape( stripslashes( $event['LOCATION'] ) ); ?></span> + <span class="event-location"> + <?php + echo $this->escape( stripslashes( $event['LOCATION'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- this method is built to escape. + ?> + </span> <?php endif; ?> <?php if ( ! empty( $event['DESCRIPTION'] ) ) : ?> - <span class="event-description"><?php echo wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); ?></span> + <span class="event-description"> + <?php + echo wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- this method is built to escape. + ?> + </span> <?php endif; ?> </li> <?php endforeach; ?> </ul> - <?php endif; + <?php + endif; - if ( 'shortcode' == $args['context'] ) : ?> + if ( 'shortcode' === $args['context'] ) : + ?> <table class="upcoming-events"> <thead> <tr> @@ -778,68 +914,102 @@ class iCalendarReader { <tbody> <?php foreach ( $events as $event ) : ?> <tr> - <td><?php echo empty( $event['LOCATION'] ) ? ' ' : $this->escape( stripslashes( $event['LOCATION'] ) ); ?></td> - <td><?php echo $this->formatted_date( $event ); ?></td> - <td><?php echo empty( $event['SUMMARY'] ) ? ' ' : $this->escape( stripslashes( $event['SUMMARY'] ) ); ?></td> - <td><?php echo empty( $event['DESCRIPTION'] ) ? ' ' : wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); ?></td> + <td> + <?php + echo empty( $event['LOCATION'] ) + ? ' ' + : $this->escape( stripslashes( $event['LOCATION'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- this method is built to escape. + ?> + </td> + <td><?php echo esc_html( $this->formatted_date( $event ) ); ?></td> + <td> + <?php + echo empty( $event['SUMMARY'] ) + ? ' ' + : $this->escape( stripslashes( $event['SUMMARY'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- this method is built to escape. + ?> + </td> + <td> + <?php + echo empty( $event['DESCRIPTION'] ) + ? ' ' + : wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- this method is built to escape. + ?> + </td> </tr> <?php endforeach; ?> </tbody> </table> - <?php endif; + <?php + endif; $rendered = ob_get_clean(); - if ( empty( $rendered ) ) + if ( empty( $rendered ) ) { return false; + } return $rendered; } + /** + * Return a localized string with information about the event's date and time, + * or starting date and end date. + * + * @param array $event Info about the event. + * + * @return string + */ public function formatted_date( $event ) { - $date_format = get_option( 'date_format' ); $time_format = get_option( 'time_format' ); - $start = strtotime( $event['DTSTART'] ); - $end = isset( $event['DTEND'] ) ? strtotime( $event['DTEND'] ) : false; + $start = strtotime( $event['DTSTART'] ); + $end = isset( $event['DTEND'] ) ? strtotime( $event['DTEND'] ) : false; - $all_day = ( 8 == strlen( $event['DTSTART'] ) ); + $all_day = ( 8 === strlen( $event['DTSTART'] ) ); - if ( !$all_day && $this->timezone ) { + if ( ! $all_day && $this->timezone ) { try { - $start_time = new DateTime( $event['DTSTART'] ); + $start_time = new DateTime( $event['DTSTART'] ); $timezone_offset = $this->timezone->getOffset( $start_time ); - $start += $timezone_offset; + $start += $timezone_offset; if ( $end ) { $end += $timezone_offset; } } catch ( Exception $e ) { - // Invalid argument to DateTime + // Invalid argument to DateTime. + return ''; } } $single_day = $end ? ( $end - $start ) <= DAY_IN_SECONDS : true; /* translators: Date and time */ - $date_with_time = __( '%1$s at %2$s' , 'jetpack' ); + $date_with_time = __( '%1$s at %2$s', 'jetpack' ); /* translators: Two dates with a separator */ - $two_dates = __( '%1$s – %2$s' , 'jetpack' ); + $two_dates = __( '%1$s – %2$s', 'jetpack' ); - // we'll always have the start date. Maybe with time - if ( $all_day ) + // we'll always have the start date. Maybe with time. + if ( $all_day ) { $date = date_i18n( $date_format, $start ); - else - $date = sprintf( $date_with_time, date_i18n( $date_format, $start ), date_i18n( $time_format, $start ) ); + } else { + $date = sprintf( + $date_with_time, + date_i18n( $date_format, $start ), + date_i18n( $time_format, $start ) + ); + } - // single day, timed - if ( $single_day && ! $all_day && false !== $end ) + // single day, timed. + if ( $single_day && ! $all_day && false !== $end ) { $date = sprintf( $two_dates, $date, date_i18n( $time_format, $end ) ); + } - // multi-day + // multi-day. if ( ! $single_day ) { if ( $all_day ) { - // DTEND for multi-day events represents "until", not "including", so subtract one minute + // DTEND for multi-day events represents "until", not "including", so subtract one minute. $end_date = date_i18n( $date_format, $end - 60 ); } else { $end_date = sprintf( $date_with_time, date_i18n( $date_format, $end ), date_i18n( $time_format, $end ) ); @@ -852,35 +1022,48 @@ class iCalendarReader { return $date; } + /** + * Sort list of events by event date. + * + * @param array $list List of events. + * + * @return array + */ protected function sort_by_recent( $list ) { - $dates = $sorted_list = array(); + $dates = array(); + $sorted_list = array(); foreach ( $list as $key => $row ) { $date = $row['DTSTART']; - // pad some time onto an all day date - if ( 8 === strlen( $date ) ) + // pad some time onto an all day date. + if ( 8 === strlen( $date ) ) { $date .= 'T000000Z'; - $dates[$key] = $date; + } + $dates[ $key ] = $date; } asort( $dates ); - foreach( $dates as $key => $value ) { - $sorted_list[$key] = $list[$key]; + foreach ( $dates as $key => $value ) { + $sorted_list[ $key ] = $list[ $key ]; } - unset($list); + unset( $list ); return $sorted_list; } + // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date } - /** * Wrapper function for iCalendarReader->get_events() * - * @param string $url (default: '') + * @param string $url (default: ''). + * @param int $count Number of events to fetch. * @return array */ function icalendar_get_events( $url = '', $count = 5 ) { - // Find your calendar's address https://support.google.com/calendar/bin/answer.py?hl=en&answer=37103 + /* + * Find your calendar's address + * https://support.google.com/calendar/bin/answer.py?hl=en&answer=37103 + */ $ical = new iCalendarReader(); return $ical->get_events( $url, $count ); } @@ -888,8 +1071,9 @@ function icalendar_get_events( $url = '', $count = 5 ) { /** * Wrapper function for iCalendarReader->render() * - * @param string $url (default: '') - * @param string $context (default: 'widget') or 'shortcode' + * @param string $url (default: ''). + * @param array $args Options when rendering events. + * * @return mixed bool|string false on failure, rendered HTML string on success. */ function icalendar_render_events( $url = '', $args = array() ) { diff --git a/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-builder.php b/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-builder.php deleted file mode 100644 index 62b0ddda..00000000 --- a/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-builder.php +++ /dev/null @@ -1,400 +0,0 @@ -<?php - -/** - * Provides an interface for easily building a complex search query that - * combines multiple ranking signals. - * - * - * $bldr = new Jetpack_WPES_Query_Builder(); - * $bldr->add_filter( ... ); - * $bldr->add_filter( ... ); - * $bldr->add_query( ... ); - * $es_query = $bldr->build_query(); - * - * - * All ES queries take a standard form with main query (with some filters), - * wrapped in a function_score - * - * Most functions are chainable, e.g. $bldr->add_filter( ... )->add_query( ... )->build_query(); - * - * Bucketed queries use an aggregation to diversify results. eg a bunch - * of separate filters where to get different sets of results. - * - */ - -class Jetpack_WPES_Query_Builder { - - protected $es_filters = array(); - - // Custom boosting with function_score - protected $functions = array(); - protected $weighting_functions = array(); - protected $decays = array(); - protected $scripts = array(); - protected $functions_max_boost = 2.0; - protected $functions_score_mode = 'multiply'; - protected $functions_boost_mode = 'multiply'; - protected $query_bool_boost = null; - - // General aggregations for buckets and metrics - protected $aggs_query = false; - protected $aggs = array(); - - // The set of top level text queries to combine - protected $must_queries = array(); - protected $should_queries = array(); - protected $dis_max_queries = array(); - - protected $diverse_buckets_query = false; - protected $bucket_filters = array(); - protected $bucket_sub_aggs = array(); - - public function get_langs() { - if ( isset( $this->langs ) ) { - return $this->langs; - } - return false; - } - - //////////////////////////////////// - // Methods for building a query - - public function add_filter( $filter ) { - $this->es_filters[] = $filter; - - return $this; - } - - public function add_query( $query, $type = 'must' ) { - switch ( $type ) { - case 'dis_max': - $this->dis_max_queries[] = $query; - break; - - case 'should': - $this->should_queries[] = $query; - break; - - case 'must': - default: - $this->must_queries[] = $query; - break; - } - - return $this; - } - - /** - * Add any weighting function to the query - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html - * - * @param $function array A function structure to apply to the query - * - * @return void - */ - public function add_weighting_function( $function ) { - $this->weighting_functions[] = $function; - - return $this; - } - - /** - * Add a scoring function to the query - * - * NOTE: For decays (linear, exp, or gauss), use Jetpack_WPES_Query_Builder::add_decay() instead - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html - * - * @param $function string name of the function - * @param $params array functions parameters - * - * @return void - */ - public function add_function( $function, $params ) { - $this->functions[ $function ][] = $params; - - return $this; - } - - /** - * Add a decay function to score results - * - * This method should be used instead of Jetpack_WPES_Query_Builder::add_function() for decays, as the internal ES structure - * is slightly different for them. - * - * @see https://www.elastic.co/guide/en/elasticsearch/guide/current/decay-functions.html - * - * @param $function string name of the decay function - linear, exp, or gauss - * @param $params array The decay functions parameters, passed to ES directly - * - * @return void - */ - public function add_decay( $function, $params ) { - $this->decays[ $function ][] = $params; - - return $this; - } - - /** - * Add a scoring mode to the query - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html - * - * @param $mode string name of how to score - * - * @return void - */ - public function add_score_mode_to_functions( $mode='multiply' ) { - $this->functions_score_mode = $mode; - - return $this; - } - - public function add_boost_mode_to_functions( $mode='multiply' ) { - $this->functions_boost_mode = $mode; - - return $this; - } - - public function add_max_boost_to_functions( $boost ) { - $this->functions_max_boost = $boost; - - return $this; - } - - public function add_boost_to_query_bool( $boost ) { - $this->query_bool_boost = $boost; - - return $this; - } - - public function add_aggs( $aggs_name, $aggs ) { - $this->aggs_query = true; - $this->aggs[$aggs_name] = $aggs; - - return $this; - } - - public function set_all_aggs( $aggs ) { - $this->aggs_query = true; - $this->aggs = $aggs; - - return $this; - } - - public function add_aggs_sub_aggs( $aggs_name, $sub_aggs ) { - if ( ! array_key_exists( 'aggs', $this->aggs[$aggs_name] ) ) { - $this->aggs[$aggs_name]['aggs'] = array(); - } - $this->aggs[$aggs_name]['aggs'] = $sub_aggs; - - return $this; - } - - public function add_bucketed_query( $name, $query ) { - $this->_add_bucket_filter( $name, $query ); - - $this->add_query( $query, 'dis_max' ); - - return $this; - } - - public function add_bucketed_terms( $name, $field, $terms, $boost = 1 ) { - if ( ! is_array( $terms ) ) { - $terms = array( $terms ); - } - - $this->_add_bucket_filter( $name, array( - 'terms' => array( - $field => $terms, - ), - )); - - $this->add_query( array( - 'constant_score' => array( - 'filter' => array( - 'terms' => array( - $field => $terms, - ), - ), - 'boost' => $boost, - ), - ), 'dis_max' ); - - return $this; - } - - public function add_bucket_sub_aggs( $agg ) { - $this->bucket_sub_aggs = array_merge( $this->bucket_sub_aggs, $agg ); - - return $this; - } - - protected function _add_bucket_filter( $name, $filter ) { - $this->diverse_buckets_query = true; - $this->bucket_filters[ $name ] = $filter; - } - - //////////////////////////////////// - // Building Final Query - - /** - * Combine all the queries, functions, decays, scripts, and max_boost into an ES query - * - * @return array Array representation of the built ES query - */ - public function build_query() { - $query = array(); - - //dis_max queries just become a single must query - if ( ! empty( $this->dis_max_queries ) ) { - $this->must_queries[] = array( - 'dis_max' => array( - 'queries' => $this->dis_max_queries, - ), - ); - } - - if ( empty( $this->must_queries ) ) { - $this->must_queries = array( - array( - 'match_all' => array(), - ), - ); - } - - if ( empty( $this->should_queries ) ) { - $query = array( - 'bool' => array( - 'must' => $this->must_queries, - ), - ); - } else { - $query = array( - 'bool' => array( - 'must' => $this->must_queries, - 'should' => $this->should_queries, - ), - ); - } - - if ( ! is_null( $this->query_bool_boost ) && isset( $query['bool'] ) ) { - $query['bool']['boost'] = $this->query_bool_boost; - } - - // If there are any function score adjustments, then combine those - if ( $this->functions || $this->decays || $this->scripts || $this->weighting_functions ) { - $weighting_functions = array(); - - if ( $this->functions ) { - foreach ( $this->functions as $function_type => $configs ) { - foreach ( $configs as $config ) { - foreach ( $config as $field => $params ) { - $func_arr = $params; - - $func_arr['field'] = $field; - - $weighting_functions[] = array( - $function_type => $func_arr, - ); - } - } - } - } - - if ( $this->decays ) { - foreach ( $this->decays as $decay_type => $configs ) { - foreach ( $configs as $config ) { - foreach ( $config as $field => $params ) { - $weighting_functions[] = array( - $decay_type => array( - $field => $params, - ), - ); - } - } - } - } - - if ( $this->scripts ) { - foreach ( $this->scripts as $script ) { - $weighting_functions[] = array( - 'script_score' => array( - 'script' => $script, - ), - ); - } - } - - $query = array( - 'function_score' => array( - 'query' => $query, - 'functions' => $weighting_functions, - 'max_boost' => $this->functions_max_boost, - 'score_mode' => $this->functions_score_mode, - 'boost_mode' => $this->functions_boost_mode, - ), - ); - } // End if(). - - return $query; - } - - /** - * Assemble the 'filter' portion of an ES query, from all registered filters - * - * @return array|null Combined ES filters, or null if none have been defined - */ - public function build_filter() { - if ( empty( $this->es_filters ) ) { - $filter = null; - } elseif ( 1 == count( $this->es_filters ) ) { - $filter = $this->es_filters[0]; - } else { - $filter = array( - 'and' => $this->es_filters, - ); - } - - return $filter; - } - - /** - * Assemble the 'aggregation' portion of an ES query, from all general aggregations. - * - * @return array An aggregation query as an array of topics, filters, and bucket names - */ - public function build_aggregation() { - if ( empty( $this->bucket_sub_aggs ) && empty( $this->aggs_query ) ) { - return array(); - } - - if ( ! $this->diverse_buckets_query && empty( $this->aggs_query ) ) { - return $this->bucket_sub_aggs; - } - - $aggregations = array( - 'topics' => array( - 'filters' => array( - 'filters' => array(), - ), - ), - ); - - if ( ! empty( $this->bucket_sub_aggs ) ) { - $aggregations['topics']['aggs'] = $this->bucket_sub_aggs; - } - - foreach ( $this->bucket_filters as $bucket_name => $filter ) { - $aggregations['topics']['filters']['filters'][ $bucket_name ] = $filter; - } - - if ( ! empty( $this->aggs_query ) ) { - $aggregations = $this->aggs; - } - - return $aggregations; - } - -} diff --git a/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-parser.php b/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-parser.php deleted file mode 100644 index 42a82ede..00000000 --- a/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-parser.php +++ /dev/null @@ -1,691 +0,0 @@ -<?php - -/** - * Parse a pure text query into WordPress Elasticsearch query. This builds on - * the Jetpack_WPES_Query_Builder() to provide search query parsing. - * - * The key part of this parser is taking a user's query string typed into a box - * and converting it into an ES search query. - * - * This varies by application, but roughly it means extracting some parts of the query - * (authors, tags, and phrases) that are treated as a filter. Then taking the - * remaining words and building the correct query (possibly with prefix searching - * if we are doing search as you type) - * - * This class only supports ES 2.x+ - * - * This parser builds queries of the form: - * bool: - * must: - * AND match of a single field (ideally an edgengram field) - * filter: - * filter clauses from context (eg @gibrown, #news, etc) - * should: - * boosting of results by various fields - * - * Features supported: - * - search as you type - * - phrases - * - supports querying across multiple languages at once - * - * Example usage (from Search on Reader Manage): - * - * require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-search-query-parser' ); - * $parser = new Jetpack_WPES_Search_Query_Parser( $args['q'], array( $lang ) ); - * - * //author - * $parser->author_field_filter( array( - * 'prefixes' => array( '@' ), - * 'wpcom_id_field' => 'author_id', - * 'must_query_fields' => array( 'author.engram', 'author_login.engram' ), - * 'boost_query_fields' => array( 'author^2', 'author_login^2', 'title.default.engram' ), - * ) ); - * - * //remainder of query - * $match_content_fields = $parser->merge_ml_fields( - * array( - * 'all_content' => 0.1, - * ), - * array( - * 'all_content.default.engram^0.1', - * ) - * ); - * $boost_content_fields = $parser->merge_ml_fields( - * array( - * 'title' => 2, - * 'description' => 1, - * 'tags' => 1, - * ), - * array( - * 'author_login^2', - * 'author^2', - * ) - * ); - * - * $parser->phrase_filter( array( - * 'must_query_fields' => $match_content_fields, - * 'boost_query_fields' => $boost_content_fields, - * ) ); - * $parser->remaining_query( array( - * 'must_query_fields' => $match_content_fields, - * 'boost_query_fields' => $boost_content_fields, - * ) ); - * - * //Boost on phrases - * $parser->remaining_query( array( - * 'boost_query_fields' => $boost_content_fields, - * 'boost_query_type' => 'phrase', - * ) ); - * - * //boosting - * $parser->add_max_boost_to_functions( 20 ); - * $parser->add_function( 'field_value_factor', array( - * 'follower_count' => array( - * 'modifier' => 'sqrt', - * 'factor' => 1, - * 'missing' => 0, - * ) ) ); - * - * //Filtering - * $parser->add_filter( array( - * 'exists' => array( 'field' => 'langs.' . $lang ) - * ) ); - * - * //run the query - * $es_query_args = array( - * 'name' => 'feeds', - * 'blog_id' => false, - * 'security_strategy' => 'a8c', - * 'type' => 'feed,blog', - * 'fields' => array( 'blog_id', 'feed_id' ), - * 'query' => $parser->build_query(), - * 'filter' => $parser->build_filter(), - * 'size' => $size, - * 'from' => $from - * ); - * $es_results = es_api_search_index( $es_query_args, 'api-feed-find' ); - * - */ - -jetpack_require_lib( 'jetpack-wpes-query-builder' ); - -class Jetpack_WPES_Search_Query_Parser extends Jetpack_WPES_Query_Builder { - - protected $orig_query = ''; - protected $current_query = ''; - protected $langs; - protected $avail_langs = array( 'ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'eu', 'fa', 'fi', 'fr', 'he', 'hi', 'hu', 'hy', 'id', 'it', 'ja', 'ko', 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' ); - - public function __construct( $user_query, $langs ) { - $this->orig_query = $user_query; - $this->current_query = $this->orig_query; - $this->langs = $this->norm_langs( $langs ); - } - - protected $extracted_phrases = array(); - - public function get_current_query() { - return $this->current_query; - } - - public function set_current_query( $q ) { - $this->current_query = $q; - } - - /////////////////////////////////////////////////////// - // Methods for Building arrays of multilingual fields - - /* - * Normalize language codes - */ - public function norm_langs( $langs ) { - $lst = array(); - foreach( $langs as $l ) { - $l = strtok( $l, '-_' ); - if ( in_array( $l, $this->avail_langs ) ) { - $lst[$l] = true; - } else { - $lst['default'] = true; - } - } - return array_keys( $lst ); - } - - /* - * Take a list of field prefixes and expand them for multi-lingual - * with the provided boostings. - */ - public function merge_ml_fields( $fields2boosts, $additional_fields ) { - $flds = array(); - foreach( $fields2boosts as $f => $b ) { - foreach( $this->langs as $l ) { - $flds[] = $f . '.' . $l . '^' . $b; - } - } - foreach( $additional_fields as $f ) { - $flds[] = $f; - } - return $flds; - } - - //////////////////////////////////// - // Extract Fields for Filtering on - - /* - * Extract any @mentions from the user query - * use them as a filter if we can find a wp.com id - * otherwise use them as a - * - * args: - * wpcom_id_field: wp.com id field - * must_query_fields: array of fields to search for matching results (optional) - * boost_query_fields: array of fields to search in for boosting results (optional) - * prefixes: array of prefixes that the user can use to indicate an author - * - * returns true/false of whether any were found - * - * See also: https://github.com/twitter/twitter-text/blob/master/java/src/com/twitter/Regex.java - */ - public function author_field_filter( $args ) { - $defaults = array( - 'wpcom_id_field' => 'author_id', - 'must_query_fields' => null, - 'boost_query_fields' => null, - 'prefixes' => array( '@' ), - ); - $args = wp_parse_args( $args, $defaults ); - - $names = array(); - foreach( $args['prefixes'] as $p ) { - $found = $this->get_fields( $p ); - if ( $found ) { - foreach( $found as $f ) { - $names[] = $f; - } - } - } - - if ( empty( $names ) ) { - return false; - } - - foreach( $args['prefixes'] as $p ) { - $this->remove_fields( $p ); - } - - $user_ids = array(); - $query_names = array(); - - //loop through the matches and separate into filters and queries - foreach( $names as $n ) { - //check for exact match on login - $userdata = get_user_by( 'login', strtolower( $n ) ); - $filtering = false; - if ( $userdata ) { - $user_ids[ $userdata->ID ] = true; - $filtering = true; - } - - $is_phrase = false; - if ( preg_match( '/"/', $n ) ) { - $is_phrase = true; - $n = preg_replace( '/"/', '', $n ); - } - - if ( !empty( $args['must_query_fields'] ) && !$filtering ) { - if ( $is_phrase ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $n, - 'type' => 'phrase', - ) ) ); - } else { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $n, - ) ) ); - } - } - - if ( !empty( $args['boost_query_fields'] ) ) { - if ( $is_phrase ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $n, - 'type' => 'phrase', - ) ), 'should' ); - } else { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $n, - ) ), 'should' ); - } - } - } - - if ( ! empty( $user_ids ) ) { - $user_ids = array_keys( $user_ids ); - $this->add_filter( array( 'terms' => array( $args['wpcom_id_field'] => $user_ids ) ) ); - } - - return true; - } - - /* - * Extract any prefix followed by text use them as a must clause, - * and optionally as a boost to the should query - * This can be used for hashtags. eg #News, or #"current events", - * but also works for any arbitrary field. eg from:Greg - * - * args: - * must_query_fields: array of fields that must match the tag (optional) - * boost_query_fields: array of fields to boost search on (optional) - * prefixes: array of prefixes that the user can use to indicate a tag - * - * returns true/false of whether any were found - * - */ - public function text_field_filter( $args ) { - $defaults = array( - 'must_query_fields' => array( 'tag.name' ), - 'boost_query_fields' => array( 'tag.name' ), - 'prefixes' => array( '#' ), - ); - $args = wp_parse_args( $args, $defaults ); - - $tags = array(); - foreach( $args['prefixes'] as $p ) { - $found = $this->get_fields( $p ); - if ( $found ) { - foreach( $found as $f ) { - $tags[] = $f; - } - } - } - - if ( empty( $tags ) ) { - return false; - } - - foreach( $args['prefixes'] as $p ) { - $this->remove_fields( $p ); - } - - foreach( $tags as $t ) { - $is_phrase = false; - if ( preg_match( '/"/', $t ) ) { - $is_phrase = true; - $t = preg_replace( '/"/', '', $t ); - } - - if ( ! empty( $args['must_query_fields'] ) ) { - if ( $is_phrase ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $t, - 'type' => 'phrase', - ) ) ); - } else { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $t, - ) ) ); - } - } - - if ( ! empty( $args['boost_query_fields'] ) ) { - if ( $is_phrase ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $t, - 'type' => 'phrase', - ) ), 'should' ); - } else { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $t, - ) ), 'should' ); - } - } - } - - return true; - } - - /* - * Extract anything surrounded by quotes or if there is an opening quote - * that is not complete, and add them to the query as a phrase query. - * Quotes can be either '' or "" - * - * args: - * must_query_fields: array of fields that must match the phrases - * boost_query_fields: array of fields to boost the phrases on (optional) - * - * returns true/false of whether any were found - * - */ - public function phrase_filter( $args ) { - $defaults = array( - 'must_query_fields' => array( 'all_content' ), - 'boost_query_fields' => array( 'title' ), - ); - $args = wp_parse_args( $args, $defaults ); - - $phrases = array(); - if ( preg_match_all( '/"([^"]+)"/', $this->current_query, $matches ) ) { - foreach ( $matches[1] as $match ) { - $phrases[] = $match; - } - $this->current_query = preg_replace( '/"([^"]+)"/', '', $this->current_query ); - } - - if ( preg_match_all( "/'([^']+)'/", $this->current_query, $matches ) ) { - foreach ( $matches[1] as $match ) { - $phrases[] = $match; - } - $this->current_query = preg_replace( "/'([^']+)'/", '', $this->current_query ); - } - - //look for a final, uncompleted phrase - $phrase_prefix = false; - if ( preg_match_all( '/"([^"]+)$/', $this->current_query, $matches ) ) { - $phrase_prefix = $matches[1][0]; - $this->current_query = preg_replace( '/"([^"]+)$/', '', $this->current_query ); - } - if ( preg_match_all( "/(?:'\B|\B')([^']+)$/", $this->current_query, $matches ) ) { - $phrase_prefix = $matches[1][0]; - $this->current_query = preg_replace( "/(?:'\B|\B')([^']+)$/", '', $this->current_query ); - } - - if ( $phrase_prefix ) { - $phrases[] = $phrase_prefix; - } - if ( empty( $phrases ) ) { - return false; - } - - foreach ( $phrases as $p ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $p, - 'type' => 'phrase', - ) ) ); - - if ( ! empty( $args['boost_query_fields'] ) ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $p, - 'operator' => 'and', - ) ), 'should' ); - } - } - - return true; - } - - /* - * Query fields based on the remaining parts of the query - * This could be the final AND part of the query terms to match, or it - * could be boosting certain elements of the query - * - * args: - * must_query_fields: array of fields that must match the remaining terms (optional) - * boost_query_fields: array of fields to boost the remaining terms on (optional) - * - */ - public function remaining_query( $args ) { - $defaults = array( - 'must_query_fields' => null, - 'boost_query_fields' => null, - 'boost_operator' => 'and', - 'boost_query_type' => 'best_fields', - ); - $args = wp_parse_args( $args, $defaults ); - - if ( empty( $this->current_query ) || ctype_space( $this->current_query ) ) { - return; - } - - if ( ! empty( $args['must_query_fields'] ) ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $this->current_query, - 'operator' => 'and', - ) ) ); - } - - if ( ! empty( $args['boost_query_fields'] ) ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $this->current_query, - 'operator' => $args['boost_operator'], - 'type' => $args['boost_query_type'], - ) ), 'should' ); - } - - } - - /* - * Query fields using a prefix query (alphabetical expansions on the index). - * This is not recommended. Slower performance and worse relevancy. - * - * (UNTESTED! Copied from old prefix expansion code) - * - * args: - * must_query_fields: array of fields that must match the remaining terms (optional) - * boost_query_fields: array of fields to boost the remaining terms on (optional) - * - */ - public function remaining_prefix_query( $args ) { - $defaults = array( - 'must_query_fields' => array( 'all_content' ), - 'boost_query_fields' => array( 'title' ), - 'boost_operator' => 'and', - 'boost_query_type' => 'best_fields', - ); - $args = wp_parse_args( $args, $defaults ); - - if ( empty( $this->current_query ) || ctype_space( $this->current_query ) ) { - return; - } - - ////////////////////////////////// - // Example cases to think about: - // "elasticse" - // "elasticsearch" - // "elasticsearch " - // "elasticsearch lucen" - // "elasticsearch lucene" - // "the future" - note the stopword which will match nothing! - // "F1" - an exact match that also has tons of expansions - // "こんにちは" ja "hello" - // "こんにちは友人" ja "hello friend" - we just rely on the prefix phrase and ES to split words - // - this could still be better I bet. Maybe we need to analyze with ES first? - // - - ///////////////////////////// - //extract pieces of query - // eg: "PREFIXREMAINDER PREFIXWORD" - // "elasticsearch lucen" - - $prefix_word = false; - $prefix_remainder = false; - if ( preg_match_all( '/([^ ]+)$/', $this->current_query, $matches ) ) { - $prefix_word = $matches[1][0]; - } - - $prefix_remainder = preg_replace( '/([^ ]+)$/', '', $this->current_query ); - if ( ctype_space( $prefix_remainder ) ) { - $prefix_remainder = false; - } - - if ( ! $prefix_word ) { - //Space at the end of the query, so skip using a prefix query - if ( ! empty( $args['must_query_fields'] ) ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['must_query_fields'], - 'query' => $this->current_query, - 'operator' => 'and', - ) ) ); - } - - if ( ! empty( $args['boost_query_fields'] ) ) { - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $this->current_query, - 'operator' => $args['boost_operator'], - 'type' => $args['boost_query_type'], - ) ), 'should' ); - } - } else { - - //must match the prefix word and the prefix remainder - if ( ! empty( $args['must_query_fields'] ) ) { - //need to do an OR across a few fields to handle all cases - $must_q = array( 'bool' => array( 'should' => array( ), 'minimum_should_match' => 1 ) ); - - //treat all words as an exact search (boosts complete word like "news" - //from prefixes of "newspaper") - $must_q['bool']['should'][] = array( 'multi_match' => array( - 'fields' => $this->all_fields, - 'query' => $full_text, - 'operator' => 'and', - 'type' => 'cross_fields', - ) ); - - //always optimistically try and match the full text as a phrase - //prefix "the futu" should try to match "the future" - //otherwise the first stopword kinda breaks - //This also works as the prefix match for a single word "elasticsea" - $must_q['bool']['should'][] = array( 'multi_match' => array( - 'fields' => $this->phrase_fields, - 'query' => $full_text, - 'operator' => 'and', - 'type' => 'phrase_prefix', - 'max_expansions' => 100, - ) ); - - if ( $prefix_remainder ) { - //Multiple words found, so treat each word on its own and not just as - //a part of a phrase - //"elasticsearch lucen" => "elasticsearch" exact AND "lucen" prefix - $q['bool']['should'][] = array( 'bool' => array( - 'must' => array( - array( 'multi_match' => array( - 'fields' => $this->phrase_fields, - 'query' => $prefix_word, - 'operator' => 'and', - 'type' => 'phrase_prefix', - 'max_expansions' => 100, - ) ), - array( 'multi_match' => array( - 'fields' => $this->all_fields, - 'query' => $prefix_remainder, - 'operator' => 'and', - 'type' => 'cross_fields', - ) ), - ) - ) ); - } - - $this->add_query( $must_q ); - } - - //Now add any boosting of the query - if ( ! empty( $args['boost_query_fields'] ) ) { - //treat all words as an exact search (boosts complete word like "news" - //from prefixes of "newspaper") - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $this->current_query, - 'operator' => $args['boost_query_operator'], - 'type' => $args['boost_query_type'], - ) ), 'should' ); - - //optimistically boost the full phrase prefix match - $this->add_query( array( - 'multi_match' => array( - 'fields' => $args['boost_query_fields'], - 'query' => $this->current_query, - 'operator' => 'and', - 'type' => 'phrase_prefix', - 'max_expansions' => 100, - ) ) ); - } - } - } - - /* - * Boost results based on the lang probability overlaps - * - * args: - * langs2prob: list of languages to search in with associated boosts - */ - public function boost_lang_probs( $langs2prob ) { - foreach( $langs2prob as $l => $p ) { - $this->add_function( 'field_value_factor', array( - 'modifier' => 'none', - 'factor' => $p, - 'missing' => 0.01, //1% chance doc did not have right lang detected - ) ); - } - } - - //////////////////////////////////// - // Helper Methods - - //Get the text after some prefix. eg @gibrown, or @"Greg Brown" - protected function get_fields( $field_prefix ) { - $regex = '/' . $field_prefix . '(("[^"]+")|([^\\p{Z}]+))/'; - if ( preg_match_all( $regex, $this->current_query, $match ) ) { - return $match[1]; - } - return false; - } - - //Remove the prefix and text from the query - protected function remove_fields( $field_name ) { - $regex = '/' . $field_name . '(("[^"]+")|([^\\p{Z}]+))/'; - $this->current_query = preg_replace( $regex, '', $this->current_query ); - } - - //Best effort string truncation that splits on word breaks - protected function truncate_string( $string, $limit, $break=" " ) { - if ( mb_strwidth( $string ) <= $limit ) { - return $string; - } - - // walk backwards from $limit to find first break - $breakpoint = $limit; - $broken = false; - while ( $breakpoint > 0 ) { - if ( $break === mb_strimwidth( $string, $breakpoint, 1 ) ) { - $string = mb_strimwidth( $string, 0, $breakpoint ); - $broken = true; - break; - } - $breakpoint--; - } - // if we weren't able to find a break, need to chop mid-word - if ( !$broken ) { - $string = mb_strimwidth( $string, 0, $limit ); - } - return $string; - } - -} diff --git a/plugins/jetpack/_inc/lib/markdown/0-load.php b/plugins/jetpack/_inc/lib/markdown/0-load.php deleted file mode 100644 index bf5993e3..00000000 --- a/plugins/jetpack/_inc/lib/markdown/0-load.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php - -if ( ! class_exists( 'MarkdownExtra_Parser' ) ) - jetpack_require_lib( 'markdown/extra' ); - -jetpack_require_lib( 'markdown/gfm' ); diff --git a/plugins/jetpack/_inc/lib/plans.php b/plugins/jetpack/_inc/lib/plans.php index 6dc43fca..f1867dab 100644 --- a/plugins/jetpack/_inc/lib/plans.php +++ b/plugins/jetpack/_inc/lib/plans.php @@ -36,7 +36,7 @@ class Jetpack_Plans { array( 'method' => 'GET', 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + 'X-Forwarded-For' => ( new Automattic\Jetpack\Status\Visitor() )->get_ip( true ), ), ), null, diff --git a/plugins/jetpack/_inc/lib/plugins.php b/plugins/jetpack/_inc/lib/plugins.php index 0bffc529..cc8e8bb5 100644 --- a/plugins/jetpack/_inc/lib/plugins.php +++ b/plugins/jetpack/_inc/lib/plugins.php @@ -1,205 +1,10 @@ -<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +<?php /** - * Plugins Library + * This file has been moved to the jetpack-plugins-installer package * - * Helper functions for installing and activating plugins. + * @deprecated 10.7 * - * Used by the REST API - * - * @autounit api plugins - */ - -require_once 'class.jetpack-automatic-install-skin.php'; - -use Automattic\Jetpack\A8c_Mc_Stats; - -/** - * Plugins management tools. + * @package jetpack */ -class Jetpack_Plugins { - - /** - * Install and activate a plugin. - * - * @since 5.8.0 - * - * @param string $slug Plugin slug. - * - * @return bool|WP_Error True if installation succeeded, error object otherwise. - */ - public static function install_and_activate_plugin( $slug ) { - $plugin_id = self::get_plugin_id_by_slug( $slug ); - - if ( ! $plugin_id ) { - $installed = self::install_plugin( $slug ); - if ( is_wp_error( $installed ) ) { - return $installed; - } - $plugin_id = self::get_plugin_id_by_slug( $slug ); - } elseif ( is_plugin_active( $plugin_id ) ) { - return true; // Already installed and active. - } - - if ( ! current_user_can( 'activate_plugins' ) ) { - return new WP_Error( 'not_allowed', __( 'You are not allowed to activate plugins on this site.', 'jetpack' ) ); - } - $activated = activate_plugin( $plugin_id ); - if ( is_wp_error( $activated ) ) { - return $activated; - } - - return true; - } - - /** - * Install a plugin. - * - * @since 5.8.0 - * - * @param string $slug Plugin slug. - * - * @return bool|WP_Error True if installation succeeded, error object otherwise. - */ - public static function install_plugin( $slug ) { - if ( is_multisite() && ! current_user_can( 'manage_network' ) ) { - return new WP_Error( 'not_allowed', __( 'You are not allowed to install plugins on this site.', 'jetpack' ) ); - } - - $skin = new Jetpack_Automatic_Install_Skin(); - $upgrader = new Plugin_Upgrader( $skin ); - $zip_url = self::generate_wordpress_org_plugin_download_link( $slug ); - $mc_stats = new A8c_Mc_Stats(); - - $result = $upgrader->install( $zip_url ); - - if ( is_wp_error( $result ) ) { - $mc_stats->add( 'install-plugin', "fail-$slug" ); - return $result; - } - - $plugin = self::get_plugin_id_by_slug( $slug ); - $error_code = 'install_error'; - if ( ! $plugin ) { - $error = __( 'There was an error installing your plugin', 'jetpack' ); - } - - if ( ! $result ) { - $error_code = $upgrader->skin->get_main_error_code(); - $message = $upgrader->skin->get_main_error_message(); - $error = $message ? $message : __( 'An unknown error occurred during installation', 'jetpack' ); - } - - if ( ! empty( $error ) ) { - if ( 'download_failed' === $error_code ) { - // For backwards compatibility: versions prior to 3.9 would return no_package instead of download_failed. - $error_code = 'no_package'; - } - - $mc_stats->add( 'install-plugin', "fail-$slug" ); - return new WP_Error( $error_code, $error, 400 ); - } - - $mc_stats->add( 'install-plugin', "success-$slug" ); - return (array) $upgrader->skin->get_upgrade_messages(); - } - - /** - * Get WordPress.org zip download link from a plugin slug - * - * @param string $plugin_slug Plugin slug. - */ - protected static function generate_wordpress_org_plugin_download_link( $plugin_slug ) { - return "https://downloads.wordpress.org/plugin/$plugin_slug.latest-stable.zip"; - } - - /** - * Get the plugin ID (composed of the plugin slug and the name of the main plugin file) from a plugin slug. - * - * @param string $slug Plugin slug. - */ - public static function get_plugin_id_by_slug( $slug ) { - // Check if get_plugins() function exists. This is required on the front end of the - // site, since it is in a file that is normally only loaded in the admin. - 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() ); - if ( ! is_array( $plugins ) ) { - return false; - } - - foreach ( $plugins as $plugin_file => $plugin_data ) { - if ( self::get_slug_from_file_path( $plugin_file ) === $slug ) { - return $plugin_file; - } - } - - return false; - } - - /** - * Get the plugin slug from the plugin ID (composed of the plugin slug and the name of the main plugin file) - * - * @param string $plugin_file Plugin file (ID -- e.g. hello-dolly/hello.php). - */ - protected static function get_slug_from_file_path( $plugin_file ) { - // Similar to get_plugin_slug() method. - $slug = dirname( $plugin_file ); - if ( '.' === $slug ) { - $slug = preg_replace( '/(.+)\.php$/', '$1', $plugin_file ); - } - - return $slug; - } - - /** - * Get the activation status for a plugin. - * - * @since 8.9.0 - * - * @param string $plugin_file The plugin file to check. - * @return string Either 'network-active', 'active' or 'inactive'. - */ - public static function get_plugin_status( $plugin_file ) { - if ( is_plugin_active_for_network( $plugin_file ) ) { - return 'network-active'; - } - - if ( is_plugin_active( $plugin_file ) ) { - return 'active'; - } - - return 'inactive'; - } - - /** - * Returns a list of all plugins in the site. - * - * @since 8.9.0 - * @uses get_plugins() - * - * @return array - */ - public static function 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() ); - - if ( is_array( $plugins ) && ! empty( $plugins ) ) { - foreach ( $plugins as $plugin_slug => $plugin_data ) { - $plugins[ $plugin_slug ]['active'] = in_array( - self::get_plugin_status( $plugin_slug ), - array( 'active', 'network-active' ), - true - ); - } - return $plugins; - } - return array(); - } -} +class_alias( Automattic\Jetpack\Plugins_Installer::class, 'Jetpack_Plugins' ); diff --git a/plugins/jetpack/_inc/lib/tonesque.php b/plugins/jetpack/_inc/lib/tonesque.php index 0e148e5c..fcf970fd 100644 --- a/plugins/jetpack/_inc/lib/tonesque.php +++ b/plugins/jetpack/_inc/lib/tonesque.php @@ -1,22 +1,45 @@ -<?php -/* -Plugin Name: Tonesque -Plugin URI: https://automattic.com/ -Description: Grab an average color representation from an image. -Version: 1.0 -Author: Automattic, Matias Ventura -Author URI: https://automattic.com/ -License: GNU General Public License v2 or later -License URI: https://www.gnu.org/licenses/gpl-2.0.html -*/ - +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Tonesque + * Grab an average color representation from an image. + * Author: Automattic, Matias Ventura + * Author URI: https://automattic.com/ + * License: GNU General Public License v2 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + * + * @package automattic/jetpack + */ + +/** + * Color representation class. + */ class Tonesque { + /** + * Image URL. + * + * @var string + */ private $image_url = ''; - private $image_obj = NULL; + /** + * Image identifier representing the image. + * + * @var null|object + */ + private $image_obj = null; + /** + * Color code. + * + * @var string + */ private $color = ''; - function __construct( $image_url ) { + /** + * Constructor. + * + * @param string $image_url Image URL. + */ + public function __construct( $image_url ) { if ( ! class_exists( 'Jetpack_Color' ) ) { jetpack_require_lib( 'class.color' ); } @@ -37,23 +60,29 @@ class Tonesque { $this->image_obj = self::imagecreatefromurl( $this->image_url ); } + /** + * Get an image object from a URL. + * + * @param string $image_url Image URL. + * + * @return object Image object. + */ public static function imagecreatefromurl( $image_url ) { $data = null; - // If it's a URL: + // If it's a URL. if ( preg_match( '#^https?://#i', $image_url ) ) { - // If it's a url pointing to a local media library url: + // If it's a url pointing to a local media library url. $content_url = content_url(); $_image_url = set_url_scheme( $image_url ); if ( wp_startswith( $_image_url, $content_url ) ) { $_image_path = str_replace( $content_url, WP_CONTENT_DIR, $_image_url ); if ( file_exists( $_image_path ) ) { $filetype = wp_check_filetype( $_image_path ); - $ext = $filetype['ext']; - $type = $filetype['type']; + $type = $filetype['type']; if ( wp_startswith( $type, 'image/' ) ) { - $data = file_get_contents( $_image_path ); + $data = file_get_contents( $_image_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents } } } @@ -67,14 +96,13 @@ class Tonesque { } } - // If it's a local path in our WordPress install: + // If it's a local path in our WordPress install. if ( file_exists( $image_url ) ) { $filetype = wp_check_filetype( $image_url ); - $ext = $filetype['ext']; - $type = $filetype['type']; + $type = $filetype['type']; if ( wp_startswith( $type, 'image/' ) ) { - $data = file_get_contents( $image_url ); + $data = file_get_contents( $image_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents } } @@ -83,44 +111,45 @@ class Tonesque { } /** - * * Construct object from image. * - * @param optional $type (hex, rgb, hsv) - * @return color as a string formatted as $type + * @param string $type Type (hex, rgb, hsv) (optional). * - */ - function color( $type = 'hex' ) { - // Bail if there is no image to work with - if ( ! $this->image_obj ) + * @return color as a string formatted as $type + */ + public function color( $type = 'hex' ) { + // Bail if there is no image to work with. + if ( ! $this->image_obj ) { return false; + } - // Finds dominant color + // Finds dominant color. $color = self::grab_color(); - // Passes value to Color class + // Passes value to Color class. $color = self::get_color( $color, $type ); return $color; } /** - * * Grabs the color index for each of five sample points of the image * - * @param $image - * @param $type can be 'index' or 'hex' - * @return array() with color indices + * @param string $type can be 'index' or 'hex'. * - */ - function grab_points( $type = 'index' ) { + * @return array with color indices + */ + public function grab_points( $type = 'index' ) { $img = $this->image_obj; - if ( ! $img ) + if ( ! $img ) { return false; + } $height = imagesy( $img ); $width = imagesx( $img ); - // Sample five points in the image - // Based on rule of thirds and center + /* + * Sample five points in the image + * based on rule of thirds and center. + */ $topy = round( $height / 3 ); $bottomy = round( ( $height / 3 ) * 2 ); $leftx = round( $width / 3 ); @@ -128,7 +157,7 @@ class Tonesque { $centery = round( $height / 2 ); $centerx = round( $width / 2 ); - // Cast those colors into an array + // Cast those colors into an array. $points = array( imagecolorat( $img, $leftx, $topy ), imagecolorat( $img, $rightx, $topy ), @@ -137,14 +166,17 @@ class Tonesque { imagecolorat( $img, $centerx, $centery ), ); - if ( 'hex' == $type ) { + if ( 'hex' === $type ) { foreach ( $points as $i => $p ) { - $c = imagecolorsforindex( $img, $p ); - $points[ $i ] = self::get_color( array( - 'r' => $c['red'], - 'g' => $c['green'], - 'b' => $c['blue'], - ), 'hex' ); + $c = imagecolorsforindex( $img, $p ); + $points[ $i ] = self::get_color( + array( + 'r' => $c['red'], + 'g' => $c['green'], + 'b' => $c['blue'], + ), + 'hex' + ); } } @@ -152,34 +184,37 @@ class Tonesque { } /** - * * Finds the average color of the image based on five sample points * - * @param $image - * @return array() with rgb color - * - */ - function grab_color() { + * @return array with rgb color + */ + public function grab_color() { $img = $this->image_obj; - if ( ! $img ) + if ( ! $img ) { return false; + } $rgb = self::grab_points(); - // Process the color points - // Find the average representation + $r = array(); + $g = array(); + $b = array(); + + /* + * Process the color points + * Find the average representation + */ foreach ( $rgb as $color ) { $index = imagecolorsforindex( $img, $color ); - $r[] = $index['red']; - $g[] = $index['green']; - $b[] = $index['blue']; - - $red = round( array_sum( $r ) / 5 ); - $green = round( array_sum( $g ) / 5 ); - $blue = round( array_sum( $b ) / 5 ); + $r[] = $index['red']; + $g[] = $index['green']; + $b[] = $index['blue']; } + $red = round( array_sum( $r ) / 5 ); + $green = round( array_sum( $g ) / 5 ); + $blue = round( array_sum( $b ) / 5 ); - // The average color of the image as rgb array + // The average color of the image as rgb array. $color = array( 'r' => $red, 'g' => $green, @@ -190,29 +225,31 @@ class Tonesque { } /** - * * Get a Color object using /lib class.color * Convert to appropriate type * - * @return string + * @param string $color Color code. + * @param string $type Color type (rgb, hex, hsv). * + * @return string */ - function get_color( $color, $type ) { - $c = new Jetpack_Color( $color, 'rgb' ); + public function get_color( $color, $type ) { + $c = new Jetpack_Color( $color, 'rgb' ); $this->color = $c; switch ( $type ) { - case 'rgb' : + case 'rgb': $color = implode( ',', $c->toRgbInt() ); break; - case 'hex' : + case 'hex': $color = $c->toHex(); break; - case 'hsv' : + case 'hsv': $color = implode( ',', $c->toHsvInt() ); break; default: - return $color = $c->toHex(); + $color = $c->toHex(); + return $color; } return $color; @@ -224,14 +261,14 @@ class Tonesque { * Gives either black or white for using with opacity * * @return string - * - */ - function contrast() { - if ( ! $this->color ) + */ + public function contrast() { + if ( ! $this->color ) { return false; + } $c = $this->color->getMaxContrastColor(); return implode( ',', $c->toRgbInt() ); } -}; +} diff --git a/plugins/jetpack/_inc/lib/tracks/tracks-ajax.js b/plugins/jetpack/_inc/lib/tracks/tracks-ajax.js deleted file mode 100644 index 98a9aaac..00000000 --- a/plugins/jetpack/_inc/lib/tracks/tracks-ajax.js +++ /dev/null @@ -1,62 +0,0 @@ -/* global jpTracksAJAX, jQuery */ -( function( $, jpTracksAJAX ) { - window.jpTracksAJAX = window.jpTracksAJAX || {}; - var debugSet = localStorage.getItem( 'debug' ) === 'dops:analytics'; - - window.jpTracksAJAX.record_ajax_event = function( eventName, eventType, eventProp ) { - var data = { - tracksNonce: jpTracksAJAX.jpTracksAJAX_nonce, - action: 'jetpack_tracks', - tracksEventType: eventType, - tracksEventName: eventName, - tracksEventProp: eventProp || false, - }; - - return $.ajax( { - type: 'POST', - url: jpTracksAJAX.ajaxurl, - data: data, - success: function( response ) { - if ( debugSet ) { - // eslint-disable-next-line - console.log( 'AJAX tracks event recorded: ', data, response ); - } - }, - } ); - }; - - $( document ).ready( function() { - $( 'body' ).on( 'click', '.jptracks a, a.jptracks', function( event ) { - var $this = $( event.target ); - // We know that the jptracks element is either this, or its ancestor - var $jptracks = $this.closest( '.jptracks' ); - // We need an event name at least - var eventName = $jptracks.attr( 'data-jptracks-name' ); - if ( undefined === eventName ) { - return; - } - - var eventProp = $jptracks.attr( 'data-jptracks-prop' ) || false; - - var url = $this.attr( 'href' ); - var target = $this.get( 0 ).target; - if ( url && target && '_self' !== target ) { - var newTabWindow = window.open( '', target ); - newTabWindow.opener = null; - } - - event.preventDefault(); - - window.jpTracksAJAX.record_ajax_event( eventName, 'click', eventProp ).always( function() { - // Continue on to whatever url they were trying to get to. - if ( url && ! $this.hasClass( 'thickbox' ) ) { - if ( newTabWindow ) { - newTabWindow.location = url; - return; - } - window.location = url; - } - } ); - } ); - } ); -} )( jQuery, jpTracksAJAX ); diff --git a/plugins/jetpack/_inc/lib/tracks/tracks-callables.js b/plugins/jetpack/_inc/lib/tracks/tracks-callables.js deleted file mode 100644 index 4e033d2c..00000000 --- a/plugins/jetpack/_inc/lib/tracks/tracks-callables.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * This was abstracted from wp-calypso's analytics lib: https://github.com/Automattic/wp-calypso/blob/master/client/lib/analytics/README.md - * Some stuff was removed like GA tracking and other things not necessary for Jetpack tracking. - * - * This library should only be used and loaded if the Jetpack site is connected. - */ - -// Load tracking scripts -window._tkq = window._tkq || []; - -var _user; -var debug = console.error; // eslint-disable-line no-console - -function buildQuerystring( group, name ) { - var uriComponent = ''; - - if ( 'object' === typeof group ) { - for ( var key in group ) { - uriComponent += '&x_' + encodeURIComponent( key ) + '=' + encodeURIComponent( group[ key ] ); - } - } else { - uriComponent = '&x_' + encodeURIComponent( group ) + '=' + encodeURIComponent( name ); - } - - return uriComponent; -} - -var analytics = { - initialize: function( userId, username ) { - analytics.setUser( userId, username ); - analytics.identifyUser(); - }, - - mc: { - bumpStat: function( group, name ) { - var uriComponent = buildQuerystring( group, name ); // prints debug info - new Image().src = - document.location.protocol + - '//pixel.wp.com/g.gif?v=wpcom-no-pv' + - uriComponent + - '&t=' + - Math.random(); - }, - }, - - tracks: { - recordEvent: function( eventName, eventProperties ) { - eventProperties = eventProperties || {}; - - if ( eventName.indexOf( 'jetpack_' ) !== 0 ) { - debug( '- Event name must be prefixed by "jetpack_"' ); - return; - } - - window._tkq.push( [ 'recordEvent', eventName, eventProperties ] ); - }, - - recordPageView: function( urlPath ) { - analytics.tracks.recordEvent( 'jetpack_page_view', { - path: urlPath, - } ); - }, - }, - - setUser: function( userId, username ) { - _user = { ID: userId, username: username }; - }, - - identifyUser: function() { - // Don't identify the user if we don't have one - if ( _user ) { - window._tkq.push( [ 'identifyUser', _user.ID, _user.username ] ); - } - }, - - clearedIdentity: function() { - window._tkq.push( [ 'clearIdentity' ] ); - }, -}; diff --git a/plugins/jetpack/_inc/lib/widgets.php b/plugins/jetpack/_inc/lib/widgets.php index 6215a98a..d2cd5eda 100644 --- a/plugins/jetpack/_inc/lib/widgets.php +++ b/plugins/jetpack/_inc/lib/widgets.php @@ -1,4 +1,4 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** * Widgets and Sidebars Library * @@ -7,9 +7,14 @@ * * Used by the REST API * + * @package automattic/jetpack + * * @autounit api widgets */ +/** + * Widgets and Sidebars Library + */ class Jetpack_Widgets { /** @@ -31,18 +36,17 @@ class Jetpack_Widgets { * The output looks like: * * array( - * 'id' => 'text-3', - * 'sidebar' => 'sidebar-1', - * 'position' => '0', - * 'settings' => array( - * 'title' => 'hello world' - * ) + * 'id' => 'text-3', + * 'sidebar' => 'sidebar-1', + * 'position' => '0', + * 'settings' => array( + * 'title' => 'hello world' + * ) * ) * - * - * @param string|integer $position The position of the widget in its sidebar. - * @param string $widget_id The widget's id (eg: 'text-3'). - * @param string $sidebar The widget's sidebar id (eg: 'sidebar-1'). + * @param string|integer $position The position of the widget in its sidebar. + * @param string $widget_id The widget's id (eg: 'text-3'). + * @param string $sidebar The widget's sidebar id (eg: 'sidebar-1'). * @param array (Optional) $settings The settings for the widget. * * @return array A normalized array representing this widget. @@ -50,8 +54,8 @@ class Jetpack_Widgets { public static function format_widget( $position, $widget_id, $sidebar, $settings = null ) { if ( ! $settings ) { $all_settings = get_option( self::get_widget_option_name( $widget_id ) ); - $instance = self::get_widget_instance_key( $widget_id ); - $settings = $all_settings[$instance]; + $instance = self::get_widget_instance_key( $widget_id ); + $settings = $all_settings[ $instance ]; } $widget = array(); @@ -67,7 +71,7 @@ class Jetpack_Widgets { /** * Return a widget's id_base from its id. * - * @param string $widget_id The id of a widget. (eg: 'text-3') + * @param string $widget_id The id of a widget. (eg: 'text-3'). * * @return string The id_base of a widget (eg: 'text'). */ @@ -80,7 +84,7 @@ class Jetpack_Widgets { * Determine a widget's option name (the WP option where the widget's settings * are stored - generally `widget_` + the widget's id_base). * - * @param string $widget_id The id of a widget. (eg: 'text-3') + * @param string $widget_id The id of a widget. (eg: 'text-3'). * * @return string The option name of the widget's settings. (eg: 'widget_text') */ @@ -98,7 +102,7 @@ class Jetpack_Widgets { */ public static function get_widget_instance_key( $widget_id ) { // Grab all numbers from the end of the id. - preg_match('/(\d+)$/', $widget_id, $matches ); + preg_match( '/(\d+)$/', $widget_id, $matches ); return (int) $matches[0]; } @@ -126,7 +130,7 @@ class Jetpack_Widgets { * @return array An array of all widgets (see format_widget). */ public static function get_all_widgets() { - $all_widgets = array(); + $all_widgets = array(); $sidebars_widgets = self::get_all_sidebars(); foreach ( $sidebars_widgets as $sidebar => $widgets ) { @@ -148,8 +152,8 @@ class Jetpack_Widgets { */ public static function get_active_widgets() { $active_widgets = array(); - $all_widgets = self::get_all_widgets(); - foreach( $all_widgets as $widget ) { + $all_widgets = self::get_all_widgets(); + foreach ( $all_widgets as $widget ) { if ( 'wp_inactive_widgets' === $widget['sidebar'] ) { continue; } @@ -164,7 +168,7 @@ class Jetpack_Widgets { * @return array An array of all widget IDs. */ public static function get_all_widget_ids() { - $all_widgets = array(); + $all_widgets = array(); $sidebars_widgets = self::get_all_sidebars(); foreach ( array_values( $sidebars_widgets ) as $widgets ) { if ( ! is_array( $widgets ) ) { @@ -205,7 +209,6 @@ class Jetpack_Widgets { public static function get_widgets_in_sidebar( $sidebar ) { $sidebars = self::get_all_sidebars(); - if ( ! $sidebars || ! is_array( $sidebars ) ) { return null; } @@ -305,8 +308,11 @@ class Jetpack_Widgets { public static function move_widget_to_sidebar( $widget, $sidebar, $position ) { $sidebars_widgets = self::get_sidebars_widgets(); - // If a position is passed and the sidebar isn't empty, - // splice the widget into the sidebar, update the sidebar option, and return the result + /* + * If a position is passed and the sidebar isn't empty, + * splice the widget into the sidebar, + * update the sidebar option, and return the result. + */ if ( isset( $widget['sidebar'] ) && isset( $widget['position'] ) ) { array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 ); } @@ -316,9 +322,9 @@ class Jetpack_Widgets { $sidebars_widgets[ $sidebar ] = array(); } - // If no position is passed, set one from items in sidebar + // If no position is passed, set one from items in sidebar. if ( ! isset( $position ) ) { - $position = 0; + $position = 0; $last_position = self::get_last_position_in_sidebar( $sidebar ); if ( isset( $last_position ) && is_numeric( $last_position ) ) { $position = $last_position + 1; @@ -329,10 +335,16 @@ class Jetpack_Widgets { if ( empty( $sidebars_widgets[ $sidebar ] ) ) { $sidebars_widgets[ $sidebar ][] = $widget['id']; } else { - array_splice( $sidebars_widgets[ $sidebar ], (int)$position, 0, $widget['id'] ); + array_splice( $sidebars_widgets[ $sidebar ], (int) $position, 0, $widget['id'] ); } - set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $sidebars_widgets ) ); + set_theme_mod( + 'sidebars_widgets', + array( + 'time' => time(), + 'data' => $sidebars_widgets, + ) + ); return update_option( 'sidebars_widgets', $sidebars_widgets ); } @@ -365,21 +377,22 @@ class Jetpack_Widgets { * any existing widget with the same `$widget_id`. * * @param string $widget_id The id of a widget. - * @param array $settings An associative array of settings to merge with any existing settings on this widget. + * @param array $settings An associative array of settings to merge with any existing settings on this widget. * * @return boolean|WP_Error True if update was successful. */ public static function set_widget_settings( $widget_id, $settings ) { $widget_option_name = self::get_widget_option_name( $widget_id ); - $widget_settings = get_option( $widget_option_name ); - $instance_key = self::get_widget_instance_key( $widget_id ); - $old_settings = $widget_settings[ $instance_key ]; + $widget_settings = get_option( $widget_option_name ); + $instance_key = self::get_widget_instance_key( $widget_id ); + $old_settings = $widget_settings[ $instance_key ]; + $settings = self::sanitize_widget_settings( $widget_id, $settings, $old_settings ); - if ( ! $settings = self::sanitize_widget_settings( $widget_id, $settings, $old_settings ) ) { + if ( ! $settings ) { return new WP_Error( 'invalid_data', 'Update failed.', 500 ); } if ( is_array( $old_settings ) ) { - // array_filter prevents empty arguments from replacing existing ones + // array_filter prevents empty arguments from replacing existing ones. $settings = wp_parse_args( array_filter( $settings ), $old_settings ); } @@ -392,13 +405,15 @@ class Jetpack_Widgets { * Sanitize an associative array for saving. * * @param string $widget_id The id of a widget. - * @param array $settings A widget settings array. - * @param array $old_settings The existing widget settings array. + * @param array $settings A widget settings array. + * @param array $old_settings The existing widget settings array. * * @return array|false The settings array sanitized by `WP_Widget::update` or false if sanitization failed. */ private static function sanitize_widget_settings( $widget_id, $settings, $old_settings ) { - if ( ! $widget = self::get_registered_widget_object( self::get_widget_id_base( $widget_id ) ) ) { + $widget = self::get_registered_widget_object( self::get_widget_id_base( $widget_id ) ); + + if ( ! $widget ) { return false; } $new_settings = $widget->update( $settings, $old_settings ); @@ -416,7 +431,7 @@ class Jetpack_Widgets { */ public static function remove_widget_settings( $widget ) { $widget_option_name = self::get_widget_option_name( $widget['id'] ); - $widget_settings = get_option( $widget_option_name ); + $widget_settings = get_option( $widget_option_name ); unset( $widget_settings[ self::get_widget_instance_key( $widget['id'] ) ] ); update_option( $widget_option_name, $widget_settings ); } @@ -425,9 +440,9 @@ class Jetpack_Widgets { * Update a widget's settings, sidebar, and position. Returns the (updated) * formatted widget if successful or a WP_Error if it fails. * - * @param string $widget_id The id of a widget to update. - * @param string $sidebar (Optional) A sidebar to which this widget will be moved. - * @param string|integer (Optional) A new position to which this widget will be moved within its new or existing sidebar. + * @param string $widget_id The id of a widget to update. + * @param string $sidebar (Optional) A sidebar to which this widget will be moved. + * @param string|integer $position (Optional) A new position to which this widget will be moved within its new or existing sidebar. * @param array|object|string $settings Settings to merge with the existing settings of the widget (will be passed through `decode_settings`). * * @return array|WP_Error The newly added widget as an associative array with all the above properties. @@ -470,7 +485,7 @@ class Jetpack_Widgets { * Deletes a widget entirely including all its settings. Returns a WP_Error if * the widget could not be found. Otherwise returns an empty array. * - * @param string $widget_id The id of a widget to delete. (eg: 'text-2') + * @param string $widget_id The id of a widget to delete. (eg: 'text-2'). * * @return array|WP_Error An empty array if successful. */ @@ -493,14 +508,14 @@ class Jetpack_Widgets { * @return array Decoded associative array of settings. */ public static function decode_settings( $settings ) { - // Treat as string in case JSON was passed + // Treat as string in case JSON was passed. if ( is_object( $settings ) && property_exists( $settings, 'scalar' ) ) { $settings = $settings->scalar; } if ( is_object( $settings ) ) { $settings = (array) $settings; } - // Attempt to decode JSON string + // Attempt to decode JSON string. if ( is_string( $settings ) ) { $settings = (array) json_decode( $settings ); } @@ -510,9 +525,9 @@ class Jetpack_Widgets { /** * Activate a new widget. * - * @param string $id_base The id_base of the new widget (eg: 'text') - * @param string $sidebar The id of the sidebar where this widget will go. Dependent on theme. (eg: 'sidebar-1') - * @param string|integer $position (Optional) The position of the widget in the sidebar. Defaults to the last position. + * @param string $id_base The id_base of the new widget (eg: 'text'). + * @param string $sidebar The id of the sidebar where this widget will go. Dependent on theme. (eg: 'sidebar-1'). + * @param string|integer $position (Optional) The position of the widget in the sidebar. Defaults to the last position. * @param array|object|string $settings (Optional) An associative array of settings for this widget (will be passed through `decode_settings`). Varies by widget. * * @return array|WP_Error The newly added widget as an associative array with all the above properties except 'id_base' replaced with the generated 'id'. @@ -541,7 +556,7 @@ class Jetpack_Widgets { } $widget_counter = 1 + self::get_last_widget_instance_key_with_id_base( $id_base ); - $widget_id = $id_base . '-' . $widget_counter; + $widget_id = $id_base . '-' . $widget_counter; if ( 0 >= $widget_counter ) { return new WP_Error( 'invalid_data', 'Error creating widget ID' . $widget_id, 500 ); } @@ -585,7 +600,7 @@ class Jetpack_Widgets { $added_widgets = array(); - foreach( $widgets as $widget ) { + foreach ( $widgets as $widget ) { $added_widgets[] = self::activate_widget( $widget['id_base'], $widget['sidebar'], $widget['position'], $widget['settings'] ); } @@ -597,7 +612,7 @@ class Jetpack_Widgets { * `$id_base`. So if you pass in `text`, and there is a widget with the id * `text-2`, this function will return `2`. * - * @param string $id_base The id_base of a type of widget. (eg: 'rss') + * @param string $id_base The id_base of a type of widget. (eg: 'rss'). * * @return integer The last instance key of that type of widget. */ @@ -605,11 +620,11 @@ class Jetpack_Widgets { $similar_widgets = self::get_widgets_with_id_base( $id_base ); if ( ! empty( $similar_widgets ) ) { - // If the last widget with the same name is `text-3`, we want `text-4` + // If the last widget with the same name is `text-3`, we want `text-4`. usort( $similar_widgets, __CLASS__ . '::sort_widgets' ); $last_widget = array_pop( $similar_widgets ); - $last_val = (int) self::get_widget_instance_key( $last_widget['id'] ); + $last_val = (int) self::get_widget_instance_key( $last_widget['id'] ); return $last_val; } @@ -622,8 +637,8 @@ class Jetpack_Widgets { * * @since 5.4 * - * @param array $a - * @param array $b + * @param array $a A normalized array representing a widget. + * @param array $b A normalized array representing a widget. * * @return int */ @@ -685,36 +700,36 @@ class Jetpack_Widgets { * Insert a new widget in a given sidebar. * * @param string $widget_id ID of the widget. - * @param array $widget_options Content of the widget. - * @param string $sidebar ID of the sidebar to which the widget will be added. - * - * @return WP_Error|true True when data has been saved correctly, error otherwise. - */ - static function insert_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) { - // Retrieve sidebars, widgets and their instances + * @param array $widget_options Content of the widget. + * @param string $sidebar ID of the sidebar to which the widget will be added. + * + * @return WP_Error|true True when data has been saved correctly, error otherwise. + */ + public static function insert_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) { + // Retrieve sidebars, widgets and their instances. $sidebars_widgets = get_option( 'sidebars_widgets', array() ); $widget_instances = get_option( 'widget_' . $widget_id, array() ); - // Retrieve the key of the next widget instance + // Retrieve the key of the next widget instance. $numeric_keys = array_filter( array_keys( $widget_instances ), 'is_int' ); - $next_key = $numeric_keys ? max( $numeric_keys ) + 1 : 2; + $next_key = $numeric_keys ? max( $numeric_keys ) + 1 : 2; - // Add this widget to the sidebar + // Add this widget to the sidebar. if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) { $sidebars_widgets[ $sidebar ] = array(); } $sidebars_widgets[ $sidebar ][] = $widget_id . '-' . $next_key; - // Add the new widget instance + // Add the new widget instance. $widget_instances[ $next_key ] = $widget_options; - // Store updated sidebars, widgets and their instances + // Store updated sidebars, widgets and their instances. if ( ! ( update_option( 'sidebars_widgets', $sidebars_widgets ) ) || ( ! ( update_option( 'widget_' . $widget_id, $widget_instances ) ) ) ) { return new WP_Error( 'widget_update_failed', 'Failed to update widget or sidebar.', 400 ); - }; + } return true; } @@ -723,17 +738,17 @@ class Jetpack_Widgets { * Update the content of an existing widget in a given sidebar. * * @param string $widget_id ID of the widget. - * @param array $widget_options New content for the update. - * @param string $sidebar ID of the sidebar to which the widget will be added. - * - * @return WP_Error|true True when data has been updated correctly, error otherwise. - */ - static function update_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) { - // Retrieve sidebars, widgets and their instances + * @param array $widget_options New content for the update. + * @param string $sidebar ID of the sidebar to which the widget will be added. + * + * @return WP_Error|true True when data has been updated correctly, error otherwise. + */ + public static function update_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) { + // Retrieve sidebars, widgets and their instances. $sidebars_widgets = get_option( 'sidebars_widgets', array() ); $widget_instances = get_option( 'widget_' . $widget_id, array() ); - // Retrieve index of first widget instance in that sidebar + // Retrieve index of first widget instance in that sidebar. $widget_key = false; foreach ( $sidebars_widgets[ $sidebar ] as $widget ) { if ( strpos( $widget, $widget_id ) !== false ) { @@ -742,23 +757,23 @@ class Jetpack_Widgets { } } - // There is no widget instance + // There is no widget instance. if ( ! $widget_key ) { return new WP_Error( 'invalid_data', 'No such widget.', 400 ); } - // Update the widget instance and option if the data has changed + // Update the widget instance and option if the data has changed. if ( $widget_instances[ $widget_key ]['title'] !== $widget_options['title'] || $widget_instances[ $widget_key ]['address'] !== $widget_options['address'] ) { $widget_instances[ $widget_key ] = array_merge( $widget_instances[ $widget_key ], $widget_options ); - // Store updated widget instances and return Error when not successful + // Store updated widget instances and return Error when not successful. if ( ! ( update_option( 'widget_' . $widget_id, $widget_instances ) ) ) { return new WP_Error( 'widget_update_failed', 'Failed to update widget.', 400 ); - }; - }; + } + } return true; } @@ -766,10 +781,10 @@ class Jetpack_Widgets { * Retrieve the first active sidebar. * * @return string|WP_Error First active sidebar, error if none exists. - */ - static function get_first_sidebar() { + */ + public static function get_first_sidebar() { $active_sidebars = get_option( 'sidebars_widgets', array() ); - unset( $active_sidebars[ 'wp_inactive_widgets' ], $active_sidebars[ 'array_version' ] ); + unset( $active_sidebars['wp_inactive_widgets'], $active_sidebars['array_version'] ); if ( empty( $active_sidebars ) ) { return false; |