diff options
Diffstat (limited to 'plugins/jetpack/_inc/lib')
54 files changed, 0 insertions, 21259 deletions
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 deleted file mode 100644 index 0c50f380..00000000 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php +++ /dev/null @@ -1,352 +0,0 @@ -<?php - -// 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 ); - - // Create a menu item for the page and returns the hook - abstract function get_page_hook(); - - // Enqueue and localize page specific scripts - abstract function page_admin_scripts(); - - // Render page specific HTML - abstract function page_render(); - - /** - * Should we block the page rendering because the site is in IDC? - * @var bool - */ - static $block_page_rendering_for_idc; - - /** - * Function called after admin_styles to load any additional needed styles. - * - * @since 4.3.0 - */ - function additional_styles() {} - - function __construct() { - $this->jetpack = Jetpack::init(); - self::$block_page_rendering_for_idc = ( - Jetpack::validate_sync_error_idc_option() && ! Jetpack_Options::get_option( 'safe_mode_confirmed' ) - ); - } - - function add_actions() { - global $pagenow; - - // If user is not an admin and site is in Dev Mode, don't do anything - if ( ! current_user_can( 'manage_options' ) && Jetpack::is_development_mode() ) { - return; - } - - // Don't add in the modules page unless modules are available! - if ( $this->dont_show_if_not_active && ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) { - return; - } - - // 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 - add_action( "load-$hook", array( $this, 'admin_help' ) ); - add_action( "load-$hook", array( $this, 'admin_page_load' ) ); - add_action( "admin_head-$hook", array( $this, 'admin_head' ) ); - - add_action( "admin_print_styles-$hook", array( $this, 'admin_styles' ) ); - add_action( "admin_print_scripts-$hook", array( $this, 'admin_scripts' ) ); - - if ( ! self::$block_page_rendering_for_idc ) { - add_action( "admin_print_styles-$hook", array( $this, 'additional_styles' ) ); - } - // If someone just activated Jetpack, let's show them a fullscreen connection banner. - if ( - ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) - && ! Jetpack::is_active() - && current_user_can( 'jetpack_connect' ) - && ! Jetpack::is_development_mode() - ) { - add_action( 'admin_enqueue_scripts', array( 'Jetpack_Connection_Banner', 'enqueue_banner_scripts' ) ); - add_action( 'admin_print_styles', array( Jetpack::init(), 'admin_banner_styles' ) ); - add_action( 'admin_notices', array( 'Jetpack_Connection_Banner', 'render_connect_prompt_full_screen' ) ); - delete_transient( 'activated_jetpack' ); - } - - // Check if the site plan changed and deactivate modules accordingly. - add_action( 'current_screen', array( $this, 'check_plan_deactivate_modules' ) ); - - // Attach page specific actions in addition to the above - $this->add_page_actions( $hook ); - } - - function admin_head() { - if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) { - /** - * Fires in the <head> of a particular Jetpack configuration page. - * - * The dynamic portion of the hook name, `$_GET['configure']`, - * refers to the slug of module, such as 'stats', 'sso', etc. - * A complete hook for the latter would be - * 'jetpack_module_configuration_head_sso'. - * - * @since 3.0.0 - */ - do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] ); - } - } - - // Render the page with a common top and bottom part, and page specific content - 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'] && - empty( $_GET['configure'] ) - ) - { - $this->page_render(); - return; - } - Jetpack_Admin_Page::wrap_ui( array( $this, 'page_render' ) ); - } - - function admin_help() { - $this->jetpack->admin_help(); - } - - function admin_page_load() { - // This is big. For the moment, just call the existing one. - $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_action( 'admin_footer', array( $this->jetpack, 'do_stats' ) ); - } - - // Enqueue the Jetpack admin stylesheet - 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' ); - wp_style_add_data( 'jetpack-admin', 'rtl', 'replace' ); - wp_style_add_data( 'jetpack-admin', 'suffix', $min ); - } - - /** - * Checks if REST API is enabled. - * - * @since 4.4.2 - * - * @return bool - */ - 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 */ - apply_filters( 'rest_jsonp_enabled', true ) && - /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ - apply_filters( 'rest_authentication_errors', true ); - } - - /** - * Checks the site plan and deactivates modules that were active but are no longer included in the plan. - * - * @since 4.4.0 - * - * @param $page - * - * @return array - */ - function check_plan_deactivate_modules( $page ) { - if ( - Jetpack::is_development_mode() - || ! in_array( - $page->base, - array( - 'toplevel_page_jetpack', - 'admin_page_jetpack_modules', - 'jetpack_page_vaultpress', - 'jetpack_page_stats', - 'jetpack_page_akismet-key-config' - ) - ) - ) { - return false; - } - - $current = Jetpack_Plan::get(); - - $to_deactivate = array(); - if ( isset( $current['product_slug'] ) ) { - $active = Jetpack::get_active_modules(); - switch ( $current['product_slug'] ) { - case 'jetpack_free': - $to_deactivate = array( 'seo-tools', 'videopress', 'google-analytics', 'wordads', 'search' ); - break; - case 'jetpack_personal': - case 'jetpack_personal_monthly': - $to_deactivate = array( 'seo-tools', 'videopress', 'google-analytics', 'wordads', 'search' ); - break; - case 'jetpack_premium': - case 'jetpack_premium_monthly': - $to_deactivate = array( 'seo-tools', 'google-analytics', 'search' ); - break; - } - $to_deactivate = array_intersect( $active, $to_deactivate ); - - $to_leave_enabled = array(); - foreach ( $to_deactivate as $feature ) { - if ( Jetpack_Plan::supports( $feature ) ) { - $to_leave_enabled []= $feature; - } - } - $to_deactivate = array_diff( $to_deactivate, $to_leave_enabled ); - - if ( ! empty( $to_deactivate ) ) { - Jetpack::update_active_modules( array_filter( array_diff( $active, $to_deactivate ) ) ); - } - } - return array( - 'current' => $current, - 'deactivate' => $to_deactivate - ); - } - - static function load_wrapper_styles( ) { - $rtl = is_rtl() ? '.rtl' : ''; - wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin.dops-style{$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(), JETPACK__VERSION ); - $custom_css = ' - #wpcontent { - padding-left: 0 !important; - } - #wpbody-content { - background-color: #f6f6f6; - } - - #jp-plugin-container .wrap { - margin: 0 auto; - max-width:45rem; - padding: 0 1.5rem; - } - #jp-plugin-container.is-wide .wrap { - max-width: 1040px; - } - #jp-plugin-container .wrap .jetpack-wrap-container { - margin-top: 1em; - } - .wp-admin #dolly { - float: none; - position: relative; - right: 0; - left: 0; - top: 0; - padding: .625rem; - text-align: right; - background: #fff; - font-size: .75rem; - font-style: italic; - color: #87a6bc; - border-bottom: 1px #e9eff3 solid; - } - '; - wp_add_inline_style( 'dops-css', $custom_css ); - } - - static function wrap_ui( $callback, $args = array() ) { - $defaults = array( - 'is-wide' => false, - ); - $args = wp_parse_args( $args, $defaults ); - $jetpack_admin_url = admin_url( 'admin.php?page=jetpack' ); - - ?> - <div id="jp-plugin-container" class="<?php if ( $args['is-wide'] ) { echo "is-wide"; } ?>"> - - <div class="jp-masthead"> - <div class="jp-masthead__inside-container"> - <div class="jp-masthead__logo-container"> - <a class="jp-masthead__logo-link" href="<?php echo esc_url( $jetpack_admin_url ); ?>"> - <svg class="jetpack-logo__masthead" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" height="32" viewBox="0 0 118 32"><path fill="#00BE28" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M15,19H7l8-16V19z M17,29V13h8L17,29z"></path><path d="M41.3,26.6c-0.5-0.7-0.9-1.4-1.3-2.1c2.3-1.4,3-2.5,3-4.6V8h-3V6h6v13.4C46,22.8,45,24.8,41.3,26.6z"></path><path d="M65,18.4c0,1.1,0.8,1.3,1.4,1.3c0.5,0,2-0.2,2.6-0.4v2.1c-0.9,0.3-2.5,0.5-3.7,0.5c-1.5,0-3.2-0.5-3.2-3.1V12H60v-2h2.1V7.1 H65V10h4v2h-4V18.4z"></path><path d="M71,10h3v1.3c1.1-0.8,1.9-1.3,3.3-1.3c2.5,0,4.5,1.8,4.5,5.6s-2.2,6.3-5.8,6.3c-0.9,0-1.3-0.1-2-0.3V28h-3V10z M76.5,12.3 c-0.8,0-1.6,0.4-2.5,1.2v5.9c0.6,0.1,0.9,0.2,1.8,0.2c2,0,3.2-1.3,3.2-3.9C79,13.4,78.1,12.3,76.5,12.3z"></path><path d="M93,22h-3v-1.5c-0.9,0.7-1.9,1.5-3.5,1.5c-1.5,0-3.1-1.1-3.1-3.2c0-2.9,2.5-3.4,4.2-3.7l2.4-0.3v-0.3c0-1.5-0.5-2.3-2-2.3 c-0.7,0-2.3,0.5-3.7,1.1L84,11c1.2-0.4,3-1,4.4-1c2.7,0,4.6,1.4,4.6,4.7L93,22z M90,16.4l-2.2,0.4c-0.7,0.1-1.4,0.5-1.4,1.6 c0,0.9,0.5,1.4,1.3,1.4s1.5-0.5,2.3-1V16.4z"></path><path d="M104.5,21.3c-1.1,0.4-2.2,0.6-3.5,0.6c-4.2,0-5.9-2.4-5.9-5.9c0-3.7,2.3-6,6.1-6c1.4,0,2.3,0.2,3.2,0.5V13 c-0.8-0.3-2-0.6-3.2-0.6c-1.7,0-3.2,0.9-3.2,3.6c0,2.9,1.5,3.8,3.3,3.8c0.9,0,1.9-0.2,3.2-0.7V21.3z"></path><path d="M110,15.2c0.2-0.3,0.2-0.8,3.8-5.2h3.7l-4.6,5.7l5,6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z"></path><path d="M58.5,21.3c-1.5,0.5-2.7,0.6-4.2,0.6c-3.6,0-5.8-1.8-5.8-6c0-3.1,1.9-5.9,5.5-5.9s4.9,2.5,4.9,4.9c0,0.8,0,1.5-0.1,2h-7.3 c0.1,2.5,1.5,2.8,3.6,2.8c1.1,0,2.2-0.3,3.4-0.7C58.5,19,58.5,21.3,58.5,21.3z M56,15c0-1.4-0.5-2.9-2-2.9c-1.4,0-2.3,1.3-2.4,2.9 C51.6,15,56,15,56,15z"></path></svg> - </a> - </div> - <div class="jp-masthead__nav"> - <?php if ( is_network_admin() ) { - $current_screen = get_current_screen(); - - $highlight_current_sites = ( 'toplevel_page_jetpack-network' === $current_screen->id ? 'is-primary' : '' ); - $highlight_current_settings = ( 'jetpack_page_jetpack-settings-network' === $current_screen->id ? 'is-primary' : '' ); - ?> - <span class="dops-button-group"> - <?php - if ( current_user_can( 'jetpack_network_sites_page' ) ) { - ?><a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack' ) ); ?>" type="button" class="<?php echo esc_attr( $highlight_current_sites ); ?> dops-button is-compact" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>"><?php echo esc_html_x( 'Sites', 'Navigation item', 'jetpack' ); ?></a><?php - } if ( current_user_can( 'jetpack_network_settings_page' ) ) { - ?><a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack-settings' ) ); ?>" type="button" class="<?php echo esc_attr( $highlight_current_settings ); ?> dops-button is-compact" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>"><?php echo esc_html_x( 'Network Settings', 'Navigation item', 'jetpack' ); ?></a><?php - } ?> - </span> - <?php } else { ?> - <span class="dops-button-group"> - <a href="<?php echo esc_url( $jetpack_admin_url ); ?>" type="button" class="dops-button is-compact"><?php esc_html_e( 'Dashboard', 'jetpack' ); ?></a><?php - if ( current_user_can( 'jetpack_manage_modules' ) ) { - ?><a href="<?php echo esc_url( $jetpack_admin_url . '#/settings' ); ?>" type="button" class="dops-button is-compact"><?php esc_html_e( 'Settings', 'jetpack' ); ?></a><?php - } ?> - </span> - <?php } ?> - </div> - </div> - </div> - <div class="wrap"><div id="jp-admin-notices" aria-live="polite"></div></div> - <!-- START OF CALLBACK --> - <?php - ob_start(); - call_user_func( $callback ); - $callback_ui = ob_get_contents(); - ob_end_clean(); - echo $callback_ui; - ?> - <!-- END OF CALLBACK --> - <div class="jp-footer"> - <div class="jp-footer__a8c-attr-container"><a href="https://automattic.com" target="_blank" rel="noopener noreferrer"><svg role="img" class="jp-footer__a8c-attr" x="0" y="0" viewBox="0 0 935 38.2" enable-background="new 0 0 935 38.2" aria-labelledby="a8c-svg-title"><title id="a8c-svg-title">An Automattic Airline</title><path d="M317.1 38.2c-12.6 0-20.7-9.1-20.7-18.5v-1.2c0-9.6 8.2-18.5 20.7-18.5 12.6 0 20.8 8.9 20.8 18.5v1.2C337.9 29.1 329.7 38.2 317.1 38.2zM331.2 18.6c0-6.9-5-13-14.1-13s-14 6.1-14 13v0.9c0 6.9 5 13.1 14 13.1s14.1-6.2 14.1-13.1V18.6zM175 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7L157 1.3h5.5L182 36.8H175zM159.7 8.2L152 23.1h15.7L159.7 8.2zM212.4 38.2c-12.7 0-18.7-6.9-18.7-16.2V1.3h6.6v20.9c0 6.6 4.3 10.5 12.5 10.5 8.4 0 11.9-3.9 11.9-10.5V1.3h6.7V22C231.4 30.8 225.8 38.2 212.4 38.2zM268.6 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H268.6zM397.3 36.8V8.7l-1.8 3.1 -14.9 25h-3.3l-14.7-25 -1.8-3.1v28.1h-6.5V1.3h9.2l14 24.4 1.7 3 1.7-3 13.9-24.4h9.1v35.5H397.3zM454.4 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7l19.2-35.5h5.5l19.5 35.5H454.4zM439.1 8.2l-7.7 14.9h15.7L439.1 8.2zM488.4 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H488.4zM537.3 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H537.3zM569.3 36.8V4.6c2.7 0 3.7-1.4 3.7-3.4h2.8v35.5L569.3 36.8 569.3 36.8zM628 11.3c-3.2-2.9-7.9-5.7-14.2-5.7 -9.5 0-14.8 6.5-14.8 13.3v0.7c0 6.7 5.4 13 15.3 13 5.9 0 10.8-2.8 13.9-5.7l4 4.2c-3.9 3.8-10.5 7.1-18.3 7.1 -13.4 0-21.6-8.7-21.6-18.3v-1.2c0-9.6 8.9-18.7 21.9-18.7 7.5 0 14.3 3.1 18 7.1L628 11.3zM321.5 12.4c1.2 0.8 1.5 2.4 0.8 3.6l-6.1 9.4c-0.8 1.2-2.4 1.6-3.6 0.8l0 0c-1.2-0.8-1.5-2.4-0.8-3.6l6.1-9.4C318.7 11.9 320.3 11.6 321.5 12.4L321.5 12.4z"></path><path d="M37.5 36.7l-4.7-8.9H11.7l-4.6 8.9H0L19.4 0.8H25l19.7 35.9H37.5zM22 7.8l-7.8 15.1h15.9L22 7.8zM82.8 36.7l-23.3-24 -2.3-2.5v26.6h-6.7v-36H57l22.6 24 2.3 2.6V0.8h6.7v35.9H82.8z"></path><path d="M719.9 37l-4.8-8.9H694l-4.6 8.9h-7.1l19.5-36h5.6l19.8 36H719.9zM704.4 8l-7.8 15.1h15.9L704.4 8zM733 37V1h6.8v36H733zM781 37c-1.8 0-2.6-2.5-2.9-5.8l-0.2-3.7c-0.2-3.6-1.7-5.1-8.4-5.1h-12.8V37H750V1h19.6c10.8 0 15.7 4.3 15.7 9.9 0 3.9-2 7.7-9 9 7 0.5 8.5 3.7 8.6 7.9l0.1 3c0.1 2.5 0.5 4.3 2.2 6.1V37H781zM778.5 11.8c0-2.6-2.1-5.1-7.9-5.1h-13.8v10.8h14.4c5 0 7.3-2.4 7.3-5.2V11.8zM794.8 37V1h6.8v30.4h28.2V37H794.8zM836.7 37V1h6.8v36H836.7zM886.2 37l-23.4-24.1 -2.3-2.5V37h-6.8V1h6.5l22.7 24.1 2.3 2.6V1h6.8v36H886.2zM902.3 37V1H935v5.6h-26v9.2h20v5.5h-20v10.1h26V37H902.3z"></path></svg></a></div> - <ul class="jp-footer__links"> - <li class="jp-footer__link-item"> - <a href="https://jetpack.com" target="_blank" rel="noopener noreferrer" class="jp-footer__link" title="<?php esc_html_e( 'Jetpack version', 'jetpack' ); ?>">Jetpack <?php echo JETPACK__VERSION; ?></a> - </li> - <li class="jp-footer__link-item"> - <a href="https://wordpress.com/tos/" target="_blank" rel="noopener noreferrer" title="<?php esc_html__( 'WordPress.com Terms of Service', 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Terms', 'Navigation item', 'jetpack' ); ?></a> - </li> - <li class="jp-footer__link-item"> - <a href="<?php echo esc_url( $jetpack_admin_url . '#/privacy' ); ?>" rel="noopener noreferrer" title="<?php esc_html_e( "Automattic's Privacy Policy", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Privacy', 'Navigation item', 'jetpack' ); ?></a> - </li> - <?php if ( is_multisite() && current_user_can( 'jetpack_network_sites_page' ) ) { ?> - <li class="jp-footer__link-item"> - <a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack' ) ); ?>" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Network Sites', 'Navigation item', 'jetpack' ); ?></a> - </li> - <?php } ?> - <?php if ( is_multisite() && current_user_can( 'jetpack_network_settings_page' ) ) { ?> - <li class="jp-footer__link-item"> - <a href="<?php echo esc_url( network_admin_url( 'admin.php?page=jetpack-settings' ) ); ?>" title="<?php esc_html_e( "Manage your network's Jetpack Sites.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Network Settings', 'Navigation item', 'jetpack' ); ?></a> - </li> - <?php } ?> - <?php if ( current_user_can( 'manage_options' ) ) { ?> - <li class="jp-footer__link-item"> - <a href="<?php echo esc_url( admin_url() . 'admin.php?page=jetpack-debugger' ); ?>" title="<?php esc_html_e( "Test your site's compatibility with Jetpack.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Debug', 'Navigation item', 'jetpack' ); ?></a> - </li> - <?php } ?> - </ul> - </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 deleted file mode 100644 index 5c06c284..00000000 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-landing-page.php +++ /dev/null @@ -1,3 +0,0 @@ -<?php -// This is intentionally left empty as a stub because some sites were caching the require() -// @see https://github.com/Automattic/jetpack/issues/5091 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 deleted file mode 100644 index f84b62c3..00000000 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php +++ /dev/null @@ -1,422 +0,0 @@ -<?php -include_once( 'class.jetpack-admin-page.php' ); - -// Builds the landing page and its menu -class Jetpack_React_Page extends Jetpack_Admin_Page { - - protected $dont_show_if_not_active = false; - - protected $is_redirecting = false; - - function get_page_hook() { - // Add the main admin Jetpack menu - return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div' ); - } - - function add_page_actions( $hook ) { - /** This action is documented in class.jetpack.php */ - do_action( 'jetpack_admin_menu', $hook ); - - // Place the Jetpack menu item on top and others in the order they appear - add_filter( 'custom_menu_order', '__return_true' ); - add_filter( 'menu_order', array( $this, 'jetpack_menu_order' ) ); - - if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] || ! empty( $_GET['configure'] ) ) { - 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 - 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 - add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) ); - - // Adding a redirect tag wrapped in browser conditional comments - add_action( 'admin_head', array( $this, 'add_legacy_browsers_head_script' ) ); - } - - /** - * Add Jetpack Dashboard sub-link and point it to AAG if the user can view stats, manage modules or if Protect is active. - * - * Works in Dev Mode or when user is connected. - * - * @since 4.3.0 - */ - function jetpack_add_dashboard_sub_nav_item() { - if ( Jetpack::is_development_mode() || Jetpack::is_active() ) { - global $submenu; - if ( current_user_can( 'jetpack_admin_page' ) ) { - $submenu['jetpack'][] = array( __( 'Dashboard', 'jetpack' ), 'jetpack_admin_page', 'admin.php?page=jetpack#/dashboard' ); - } - } - } - - /** - * If user is allowed to see the Jetpack Admin, add Settings sub-link. - * - * @since 4.3.0 - */ - function jetpack_add_settings_sub_nav_item() { - if ( ( Jetpack::is_development_mode() || Jetpack::is_active() ) && current_user_can( 'jetpack_admin_page' ) && current_user_can( 'edit_posts' ) ) { - global $submenu; - $submenu['jetpack'][] = array( __( 'Settings', 'jetpack' ), 'jetpack_admin_page', 'admin.php?page=jetpack#/settings' ); - } - } - - function add_fallback_head_meta() { - echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">'; - } - - function add_noscript_head_meta() { - echo '<noscript>'; - $this->add_fallback_head_meta(); - echo '</noscript>'; - } - - function add_legacy_browsers_head_script() { - echo - "<script type=\"text/javascript\">\n" - . "/*@cc_on\n" - . "if ( @_jscript_version <= 10) {\n" - . "window.location.href = '?page=jetpack_modules';\n" - . "}\n" - . "@*/\n" - . "</script>"; - } - - function jetpack_menu_order( $menu_order ) { - $jp_menu_order = array(); - - foreach ( $menu_order as $index => $item ) { - if ( $item != 'jetpack' ) - $jp_menu_order[] = $item; - - if ( $index == 0 ) - $jp_menu_order[] = 'jetpack'; - } - - return $jp_menu_order; - } - - // Render the configuration page for the module if it exists and an error - // screen if the module is not configurable - // @todo remove when real settings are in place - function render_nojs_configurable( $module_name ) { - $module_name = preg_replace( '/[^\da-z\-]+/', '', $_GET['configure'] ); - - echo '<div class="wrap configure-module">'; - - if ( Jetpack::is_module( $module_name ) && current_user_can( 'jetpack_configure_modules' ) ) { - Jetpack::admin_screen_configure_module( $module_name ); - } else { - echo '<h2>' . esc_html__( 'Error, bad module.', 'jetpack' ) . '</h2>'; - } - - echo '</div><!-- /wrap -->'; - } - - function page_render() { - // Handle redirects to configuration pages - if ( ! empty( $_GET['configure'] ) ) { - return $this->render_nojs_configurable( $_GET['configure'] ); - } - - /** This action is already documented in views/admin/admin-page.php */ - do_action( 'jetpack_notices' ); - - // Try fetching by patch - $static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' ); - - if ( false === $static_html ) { - - // If we still have nothing, display an error - echo '<p>'; - esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' ); - echo '<code>yarn distclean && yarn build</code>'; - echo '</p>'; - } else { - - // We got the static.html so let's display it - echo $static_html; - } - } - - /** - * Gets array of any Jetpack notices that have been dismissed. - * - * @since 4.0.1 - * @return mixed|void - */ - function get_dismissed_jetpack_notices() { - $jetpack_dismissed_notices = get_option( 'jetpack_dismissed_notices', array() ); - /** - * Array of notices that have been dismissed. - * - * @since 4.0.1 - * - * @param array $jetpack_dismissed_notices If empty, will not show any Jetpack notices. - */ - $dismissed_notices = apply_filters( 'jetpack_dismissed_notices', $jetpack_dismissed_notices ); - return $dismissed_notices; - } - - function additional_styles() { - Jetpack_Admin_Page::load_wrapper_styles(); - } - - function page_admin_scripts() { - if ( $this->is_redirecting || isset( $_GET['configure'] ) ) { - return; // No need for scripts on a fallback page. - } - - wp_enqueue_script( - 'react-plugin', - plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ), - array( 'wp-i18n' ), - JETPACK__VERSION, - true - ); - - wp_set_script_translations( 'react-plugin', 'jetpack', JETPACK__PLUGIN_DIR . 'languages/json' ); - - if ( ! Jetpack::is_development_mode() && Jetpack::is_active() ) { - // Required for Analytics. - wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true ); - } - - // Add objects to be passed to the initial state of the app. - wp_localize_script( 'react-plugin', 'Initial_State', $this->get_initial_state() ); - } - - function get_initial_state() { - // Load API endpoint base classes and endpoints for getting the module list fed into the JS Admin Page - require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php'; - require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php'; - $moduleListEndpoint = new Jetpack_Core_API_Module_List_Endpoint(); - $modules = $moduleListEndpoint->get_modules(); - - // Preparing translated fields for JSON encoding by transforming all HTML entities to - // respective characters. - foreach( $modules as $slug => $data ) { - $modules[ $slug ]['name'] = html_entity_decode( $data['name'] ); - $modules[ $slug ]['description'] = html_entity_decode( $data['description'] ); - $modules[ $slug ]['short_description'] = html_entity_decode( $data['short_description'] ); - $modules[ $slug ]['long_description'] = html_entity_decode( $data['long_description'] ); - } - - // Collecting roles that can view site stats. - $stats_roles = array(); - $enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' ); - - if ( ! function_exists( 'get_editable_roles' ) ) { - require_once ABSPATH . 'wp-admin/includes/user.php'; - } - foreach ( get_editable_roles() as $slug => $role ) { - $stats_roles[ $slug ] = array( - 'name' => translate_user_role( $role['name'] ), - 'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false, - ); - } - - // Get information about current theme. - $current_theme = wp_get_theme(); - - // Get all themes that Infinite Scroll provides support for natively. - $inf_scr_support_themes = array(); - foreach ( Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules/infinite-scroll/themes' ) as $path ) { - if ( is_readable( $path ) ) { - $inf_scr_support_themes[] = basename( $path, '.php' ); - } - } - - // Get last post, to build the link to Customizer in the Related Posts module. - $last_post = get_posts( array( 'posts_per_page' => 1 ) ); - $last_post = isset( $last_post[0] ) && $last_post[0] instanceof WP_Post - ? get_permalink( $last_post[0]->ID ) - : get_home_url(); - - // Ensure that class to get the affiliate code is loaded - if ( ! class_exists( 'Jetpack_Affiliate' ) ) { - require_once JETPACK__PLUGIN_DIR . 'class.jetpack-affiliate.php'; - } - - return array( - 'WP_API_root' => esc_url_raw( rest_url() ), - 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ), - 'pluginBaseUrl' => plugins_url( '', JETPACK__PLUGIN_FILE ), - 'connectionStatus' => array( - 'isActive' => Jetpack::is_active(), - 'isStaging' => Jetpack::is_staging_site(), - 'devMode' => array( - 'isActive' => Jetpack::is_development_mode(), - 'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG, - 'url' => site_url() && false === strpos( site_url(), '.' ), - 'filter' => apply_filters( 'jetpack_development_mode', false ), - ), - 'isPublic' => '1' == get_option( 'blog_public' ), - 'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(), - 'sandboxDomain' => JETPACK__SANDBOX_DOMAIN, - ), - 'connectUrl' => Jetpack::init()->build_connect_url( true, false, false ), - 'dismissedNotices' => $this->get_dismissed_jetpack_notices(), - 'isDevVersion' => Jetpack::is_development_version(), - 'currentVersion' => JETPACK__VERSION, - 'is_gutenberg_available' => true, - 'getModules' => $modules, - 'showJumpstart' => jetpack_show_jumpstart(), - 'rawUrl' => Jetpack::build_raw_urls( get_home_url() ), - 'adminUrl' => esc_url( admin_url() ), - 'stats' => array( - // data is populated asynchronously on page load - 'data' => array( - 'general' => false, - 'day' => false, - 'week' => false, - 'month' => false, - ), - 'roles' => $stats_roles, - ), - 'aff' => Jetpack_Affiliate::init()->get_affiliate_code(), - 'settings' => $this->get_flattened_settings( $modules ), - 'userData' => array( -// 'othersLinked' => Jetpack::get_other_linked_admins(), - 'currentUser' => jetpack_current_user_data(), - ), - 'siteData' => array( - 'icon' => has_site_icon() - ? apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) ) - : '', - 'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), - /** - * Whether promotions are visible or not. - * - * @since 4.8.0 - * - * @param bool $are_promotions_active Status of promotions visibility. True by default. - */ - 'showPromotions' => apply_filters( 'jetpack_show_promotions', true ), - 'isAtomicSite' => jetpack_is_atomic_site(), - 'plan' => Jetpack_Plan::get(), - 'showBackups' => Jetpack::show_backups_ui(), - ), - 'themeData' => array( - 'name' => $current_theme->get( 'Name' ), - '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 ), - ), - ), - 'locale' => Jetpack::get_i18n_data_json(), - 'localeSlug' => join( '-', explode( '_', get_user_locale() ) ), - 'jetpackStateNotices' => array( - 'messageCode' => Jetpack::state( 'message' ), - 'errorCode' => Jetpack::state( 'error' ), - 'errorDescription' => Jetpack::state( 'error_description' ), - ), - 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(), - 'currentIp' => function_exists( 'jetpack_protect_get_ip' ) ? jetpack_protect_get_ip() : false, - 'lastPostUrl' => esc_url( $last_post ), - 'externalServicesConnectUrls' => $this->get_external_services_connect_urls() - ); - } - - function get_external_services_connect_urls() { - $connect_urls = array(); - jetpack_require_lib( 'class.jetpack-keyring-service-helper' ); - foreach ( Jetpack_Keyring_Service_Helper::$SERVICES as $service_name => $service_info ) { - $connect_urls[ $service_name ] = Jetpack_Keyring_Service_Helper::connect_url( $service_name, $service_info[ 'for' ] ); - } - return $connect_urls; - } - - /** - * Returns an array of modules and settings both as first class members of the object. - * - * @param array $modules the result of an API request to get all modules. - * - * @return array flattened settings with modules. - */ - function get_flattened_settings( $modules ) { - $core_api_endpoint = new Jetpack_Core_API_Data(); - $settings = $core_api_endpoint->get_all_options(); - return $settings->data; - } -} - -/* - * Only show Jump Start on first activation. - * Any option 'jumpstart' other than 'new connection' will hide it. - * - * The option can be of 4 things, and will be stored as such: - * new_connection : Brand new connection - Show - * jumpstart_activated : Jump Start has been activated - dismiss - * jetpack_action_taken: Manual activation of a module already happened - dismiss - * jumpstart_dismissed : Manual dismissal of Jump Start - dismiss - * - * @todo move to functions.global.php when available - * @since 3.6 - * @return bool | show or hide - */ -function jetpack_show_jumpstart() { - if ( ! Jetpack::is_active() ) { - return false; - } - $jumpstart_option = Jetpack_Options::get_option( 'jumpstart' ); - - $hide_options = array( - 'jumpstart_activated', - 'jetpack_action_taken', - 'jumpstart_dismissed' - ); - - if ( ! $jumpstart_option || in_array( $jumpstart_option, $hide_options ) ) { - return false; - } - - return true; -} - -/** - * Gather data about the current user. - * - * @since 4.1.0 - * - * @return array - */ -function jetpack_current_user_data() { - $current_user = wp_get_current_user(); - $is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' ); - $dotcom_data = Jetpack::get_connected_user_data(); - // Add connected user gravatar to the returned dotcom_data. - $dotcom_data['avatar'] = get_avatar_url( $dotcom_data['email'], array( 'size' => 64, 'default' => 'mysteryman' ) ); - - $current_user_data = array( - 'isConnected' => Jetpack::is_user_connected( $current_user->ID ), - 'isMaster' => $is_master_user, - 'username' => $current_user->user_login, - 'id' => $current_user->ID, - 'wpcomUser' => $dotcom_data, - 'gravatar' => get_avatar( $current_user->ID, 40, 'mm', '', array( 'force_display' => true ) ), - 'permissions' => array( - 'admin_page' => current_user_can( 'jetpack_admin_page' ), - 'connect' => current_user_can( 'jetpack_connect' ), - 'disconnect' => current_user_can( 'jetpack_disconnect' ), - 'manage_modules' => current_user_can( 'jetpack_manage_modules' ), - 'network_admin' => current_user_can( 'jetpack_network_admin_page' ), - 'network_sites_page' => current_user_can( 'jetpack_network_sites_page' ), - 'edit_posts' => current_user_can( 'edit_posts' ), - 'publish_posts' => current_user_can( 'publish_posts' ), - 'manage_options' => current_user_can( 'manage_options' ), - 'view_stats' => current_user_can( 'view_stats' ), - 'manage_plugins' => current_user_can( 'install_plugins' ) - && current_user_can( 'activate_plugins' ) - && current_user_can( 'update_plugins' ) - && current_user_can( 'delete_plugins' ), - ), - ); - - return $current_user_data; -} 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 deleted file mode 100644 index 35369ada..00000000 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php -include_once( 'class.jetpack-admin-page.php' ); -include_once( JETPACK__PLUGIN_DIR . 'class.jetpack-modules-list-table.php' ); - -// 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 - protected $dont_show_if_not_active = true; - - function add_page_actions( $hook ) {} - - // Adds the Settings sub menu - function get_page_hook() { - return add_submenu_page( - null, - __( 'Jetpack Settings', 'jetpack' ), - __( 'Settings', 'jetpack' ), - 'jetpack_manage_modules', - 'jetpack_modules', - array( $this, 'render' ) - ); - } - - // 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; - - // 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' ); - $ie_notice = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static-ie-notice.html' ); - - $noscript_notice = str_replace( - '#HEADER_TEXT#', - esc_html__( 'You have JavaScript disabled', 'jetpack' ), - $noscript_notice - ); - $noscript_notice = str_replace( - '#TEXT#', - esc_html__( "Turn on JavaScript to unlock Jetpack's full potential!", 'jetpack' ), - $noscript_notice - ); - - $rest_api_notice = str_replace( - '#HEADER_TEXT#', - esc_html( __( 'WordPress REST API is disabled', 'jetpack' ) ), - $rest_api_notice - ); - $rest_api_notice = str_replace( - '#TEXT#', - esc_html( __( "Enable WordPress REST API to unlock Jetpack's full potential!", 'jetpack' ) ), - $rest_api_notice - ); - - $ie_notice = str_replace( - '#HEADER_TEXT#', - esc_html__( 'You are using an unsupported browser version.', 'jetpack' ), - $ie_notice - ); - $ie_notice = str_replace( - '#TEXT#', - esc_html__( "Update your browser to unlock Jetpack's full potential!", 'jetpack' ), - $ie_notice - ); - - if ( ! $this->is_rest_api_enabled() ) { - echo $rest_api_notice; - } - echo $noscript_notice; - echo $ie_notice; - ?> - - <div class="page-content configure"> - <div class="frame top hide-if-no-js"> - <div class="wrap"> - <div class="manage-left"> - <table class="table table-bordered fixed-top"> - <thead> - <tr> - <th class="check-column"><input type="checkbox" class="checkall"></th> - <th colspan="2"> - <?php $list_table->unprotected_display_tablenav( 'top' ); ?> - <span class="filter-search"> - <button type="button" class="button">Filter</button> - </span> - </th> - </tr> - </thead> - </table> - </div> - </div><!-- /.wrap --> - </div><!-- /.frame --> - <div class="frame bottom"> - <div class="wrap"> - <div class="manage-right" style="display: none;"> - <div class="bumper"> - <form class="navbar-form" role="search"> - <input type="hidden" name="page" value="jetpack_modules" /> - <?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> - </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> - </div> - <p><?php esc_html_e( 'Show:', 'jetpack' ); ?></p> - <?php $list_table->views(); ?> - </form> - </div> - </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() ); ?>"> - <tbody id="the-list"> - <?php $list_table->display_rows_or_placeholder(); ?> - </tbody> - </table> - </form> - </div> - </div><!-- /.wrap --> - </div><!-- /.frame --> - </div><!-- /.content --> - <?php - - JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_settings' ) ); - } - - /** - * Load styles for static page. - * - * @since 4.3.0 - */ - function additional_styles() { - Jetpack_Admin_Page::load_wrapper_styles(); - } - - // Javascript logic specific to the list table - function page_admin_scripts() { - wp_enqueue_script( - 'jetpack-admin-js', - Jetpack::get_file_url_for_environment( '_inc/build/jetpack-admin.min.js', '_inc/jetpack-admin.js' ), - array( 'jquery' ), - JETPACK__VERSION - ); - } -} diff --git a/plugins/jetpack/_inc/lib/class.color.php b/plugins/jetpack/_inc/lib/class.color.php deleted file mode 100644 index a57f2009..00000000 --- a/plugins/jetpack/_inc/lib/class.color.php +++ /dev/null @@ -1,755 +0,0 @@ -<?php -/** - * Color utility and conversion - * - * Represents a color value, and converts between RGB/HSV/XYZ/Lab/HSL - * - * Example: - * $color = new Jetpack_Color(0xFFFFFF); - * - * @author Harold Asbridge <hasbridge@gmail.com> - * @author Matt Wiebe <wiebe@automattic.com> - * @license http://www.opensource.org/licenses/MIT - */ - -class Jetpack_Color { - /** - * @var int - */ - 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 - */ - public function __construct( $color = null, $type = 'hex' ) { - if ( $color ) { - switch ( $type ) { - case 'hex': - $this->fromHex( $color ); - break; - case 'rgb': - 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 ) { - list( $h, $s, $l ) = array_values( $color ); - $this->fromHsl( $h, $s, $l ); - } - break; - case 'int': - $this->fromInt( $color ); - break; - default: - // there is no default. - break; - } - } - } - - /** - * 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" ); - } - - $this->color = $intValue; - - return $this; - } - - /** - * Init color from integer RGB values - * - * @param int $red - * @param int $green - * @param int $blue - * - * @return Jetpack_Color - */ - 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 ( $blue < 0 || $blue > 255 ) - throw new RangeException( "Blue value " . $blue . " out of valid color code range" ); - - $this->color = (int)(($red << 16) + ($green << 8) + $blue); - - return $this; - } - - /** - * Init color from hex RGB values - * - * @param string $red - * @param string $green - * @param string $blue - * - * @return Jetpack_Color - */ - 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 http://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] - */ - public function fromHsl( $h, $s, $l ) { - $h /= 360; $s /= 100; $l /= 100; - - if ( $s == 0 ) { - $r = $g = $b = $l; // achromatic - } - else { - $q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s; - $p = 2 * $l - $q; - $r = $this->hue2rgb( $p, $q, $h + 1/3 ); - $g = $this->hue2rgb( $p, $q, $h ); - $b = $this->hue2rgb( $p, $q, $h - 1/3 ); - } - - return $this->fromRgbInt( $r * 255, $g * 255, $b * 255 ); - } - - /** - * Helper function for Jetpack_Color::fromHsl() - */ - 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; - return $p; - } - - /** - * Init color from integer value - * - * @param int $intValue - * - * @return Jetpack_Color - */ - public function fromInt($intValue) - { - if ( $intValue < 0 || $intValue > 16777215 ) - throw new RangeException( $intValue . " out of valid color code range" ); - - $this->color = $intValue; - - return $this; - } - - /** - * Convert color to hex - * - * @return string - */ - public function toHex() - { - return str_pad(dechex($this->color), 6, '0', STR_PAD_LEFT); - } - - /** - * Convert color to RGB array (integer values) - * - * @return array - */ - public function toRgbInt() - { - return array( - 'red' => (int)(255 & ($this->color >> 16)), - 'green' => (int)(255 & ($this->color >> 8)), - 'blue' => (int)(255 & ($this->color)) - ); - } - - /** - * Convert color to RGB array (hex values) - * - * @return array - */ - public function toRgbHex() - { - $r = array(); - foreach ($this->toRgbInt() as $item) { - $r[] = dechex($item); - } - return $r; - } - - /** - * Get Hue/Saturation/Value for the current color - * (float values, slow but accurate) - * - * @return array - */ - public function toHsvFloat() - { - $rgb = $this->toRgbInt(); - - $rgbMin = min($rgb); - $rgbMax = max($rgb); - - $hsv = array( - 'hue' => 0, - 'sat' => 0, - 'val' => $rgbMax - ); - - // If v is 0, color is black - if ($hsv['val'] == 0) { - return $hsv; - } - - // Normalize RGB values to 1 - $rgb['red'] /= $hsv['val']; - $rgb['green'] /= $hsv['val']; - $rgb['blue'] /= $hsv['val']; - $rgbMin = min($rgb); - $rgbMax = max($rgb); - - // Calculate saturation - $hsv['sat'] = $rgbMax - $rgbMin; - if ($hsv['sat'] == 0) { - $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) { - $hsv['hue'] += 360; - } - } else if ($rgbMax == $rgb['green']) { - $hsv['hue'] = 120 + (60 * ($rgb['blue'] - $rgb['red'])); - } else { - $hsv['hue'] = 240 + (60 * ($rgb['red'] - $rgb['green'])); - } - - return $hsv; - } - - /** - * Get HSV values for color - * (integer values from 0-255, fast but less accurate) - * - * @return int - */ - public function toHsvInt() - { - $rgb = $this->toRgbInt(); - - $rgbMin = min($rgb); - $rgbMax = max($rgb); - - $hsv = array( - 'hue' => 0, - 'sat' => 0, - 'val' => $rgbMax - ); - - // If value is 0, color is black - if ($hsv['val'] == 0) { - return $hsv; - } - - // Calculate saturation - $hsv['sat'] = round(255 * ($rgbMax - $rgbMin) / $hsv['val']); - if ($hsv['sat'] == 0) { - $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)); - } else { - $hsv['hue'] = round(171 + 43 * ($rgb['red'] - $rgb['green']) / ($rgbMax - $rgbMin)); - } - if ($hsv['hue'] < 0) { - $hsv['hue'] += 255; - } - - return $hsv; - } - - /** - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * 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 - */ - 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 { - $d = $max - $min; - $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min ); - switch ( $max ) { - case $r: - $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 ); - break; - case $g: - $h = ( $b - $r ) / $d + 2; - break; - case $b: - $h = ( $r - $g ) / $d + 4; - break; - } - $h /= 6; - } - $h = (int) round( $h * 360 ); - $s = (int) round( $s * 100 ); - $l = (int) round( $l * 100 ); - return compact( 'h', 's', 'l' ); - } - - 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 { - 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 { - return "hsl( {$h}, {$s}, {$l} )"; - } - break; - default: - return $this->toString(); - break; - } - } - - /** - * Get current color in XYZ format - * - * @return array - */ - public function toXyz() - { - $rgb = $this->toRgbInt(); - - // Normalize RGB values to 1 - - $rgb_new = array(); - 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); - } else { - $item = $item / 12.92; - } - $rgb_new[] = $item * 100; - } - $rgb = $rgb_new; - - // 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) - ); - - return $xyz; - } - - /** - * Get color CIE-Lab values - * - * @return array - */ - public function toLabCie() - { - $xyz = $this->toXyz(); - - //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); - } else { - $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']) - ); - - return $lab; - } - - /** - * Convert color to integer - * - * @return int - */ - public function toInt() - { - return $this->color; - } - - /** - * Alias of toString() - * - * @return string - */ - public function __toString() - { - return $this->toString(); - } - - /** - * Get color as string - * - * @return string - */ - public function toString() - { - $str = $this->toHex(); - return strtoupper("#{$str}"); - } - - /** - * Get the distance between this color and the given color - * - * @param Jetpack_Color $color - * - * @return int - */ - 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']); - - // Sum of RGB differences - $diff = $rDiff + $gDiff + $bDiff; - return $diff; - } - - /** - * Get distance from the given color using the Delta E method - * - * @param Jetpack_Color $color - * - * @return float - */ - 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']); - - $delta = sqrt($lDiff + $aDiff + $bDiff); - - return $delta; - } - - public function toLuminosity() { - $lum = array(); - 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']; - } - - /** - * Get distance between colors using luminance. - * Should be more than 5 for readable contrast - * - * @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 ); - } - } - - 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'; - return $color->fromHex( $hex ); - } - - public function getGrayscaleContrastingColor( $contrast = false ) { - if ( ! $contrast ) { - return $this->getMaxContrastColor(); - } - // don't allow less than 5 - $target_contrast = ( $contrast < 5 ) ? 5 : $contrast; - $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 ) { - return $color; - } - - $incr = ( '#000000' === $color->toString() ) ? 1 : -1; - while ( $contrast > $target_contrast ) { - $color = $color->incrementLightness( $incr ); - $contrast = $color->getDistanceLuminosityFrom( $this ); - } - - return $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 integer $min_contrast The minimum contrast to achieve, if possible. - * @return object A Color object, an increased contrast $this compared against $bg_color - */ - public function getReadableContrastingColor( $bg_color = false, $min_contrast = 5 ) { - if ( ! $bg_color || ! is_a( $bg_color, 'Jetpack_Color' ) ) { - return $this; - } - // you shouldn't use less than 5, but you might want to. - $target_contrast = $min_contrast; - // working things - $contrast = $bg_color->getDistanceLuminosityFrom( $this ); - $max_contrast_color = $bg_color->getMaxContrastColor(); - $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 - if ( $max_contrast <= $target_contrast ) { - return $max_contrast_color; - } - // or, we might already have sufficient contrast - if ( $contrast >= $target_contrast ) { - return $this; - } - - $incr = ( 0 === $max_contrast_color->toInt() ) ? -1 : 1; - while ( $contrast < $target_contrast ) { - $this->incrementLightness( $incr ); - $contrast = $bg_color->getDistanceLuminosityFrom( $this ); - // infininite loop prevention: you never know. - if ( $this->color === 0 || $this->color === 16777215 ) { - break; - } - } - - return $this; - } - - /** - * Detect if color is grayscale - * - * @param int @threshold - * - * @return bool - */ - 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; - - return $diff < $threshold; - } - - /** - * Get the closest matching color from the given array of colors - * - * @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); - } - $dist = $this->getDistanceLabFrom($c); - if ($dist < $matchDist) { - $matchDist = $dist; - $matchKey = $key; - } - } - - return $matchKey; - } - - /* TRANSFORMS */ - - public function darken( $amount = 5 ) { - return $this->incrementLightness( - $amount ); - } - - public function lighten( $amount = 5 ) { - return $this->incrementLightness( $amount ); - } - - public function incrementLightness( $amount ) { - $hsl = $this->toHsl(); - extract( $hsl ); - $l += $amount; - if ( $l < 0 ) $l = 0; - if ( $l > 100 ) $l = 100; - return $this->fromHsl( $h, $s, $l ); - } - - public function saturate( $amount = 15 ) { - return $this->incrementSaturation( $amount ); - } - - public function desaturate( $amount = 15 ) { - return $this->incrementSaturation( - $amount ); - } - - public function incrementSaturation( $amount ) { - $hsl = $this->toHsl(); - extract( $hsl ); - $s += $amount; - if ( $s < 0 ) $s = 0; - if ( $s > 100 ) $s = 100; - return $this->fromHsl( $h, $s, $l ); - } - - public function toGrayscale() { - $hsl = $this->toHsl(); - extract( $hsl ); - $s = 0; - return $this->fromHsl( $h, $s, $l ); - } - - public function getComplement() { - return $this->incrementHue( 180 ); - } - - public function getSplitComplement( $step = 1 ) { - $incr = 180 + ( $step * 30 ); - return $this->incrementHue( $incr ); - } - - public function getAnalog( $step = 1 ) { - $incr = $step * 30; - return $this->incrementHue( $incr ); - } - - public function getTetrad( $step = 1 ) { - $incr = $step * 60; - return $this->incrementHue( $incr ); - } - - public function getTriad( $step = 1 ) { - $incr = $step * 120; - return $this->incrementHue( $incr ); - } - - public function incrementHue( $amount ) { - $hsl = $this->toHsl(); - extract( $hsl ); - $h = ( $h + $amount ) % 360; - if ( $h < 0 ) $h = 360 - $h; - 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 deleted file mode 100644 index ba57e923..00000000 --- a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php +++ /dev/null @@ -1,3327 +0,0 @@ -<?php -/** - * Register WP REST API endpoints for Jetpack. - * - * @author Automattic - */ - -/** - * Disable direct access. - */ -if ( ! defined( 'ABSPATH' ) ) { - exit; -} - -// Load WP_Error for error messages. -require_once ABSPATH . '/wp-includes/class-wp-error.php'; - -// Register endpoints when WP REST API is initialized. -add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) ); -// Load API endpoints that are synced with WP.com -// 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'; - -/** - * 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. - */ - public static $user_permissions_error_msg; - - /** - * @var array Roles that can access Stats once they're granted access. - */ - public static $stats_roles; - - /** - * Declare the Jetpack REST API endpoints. - * - * @since 4.3.0 - */ - public static function register_endpoints() { - - // 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 - 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 = esc_html__( - 'You do not have the correct user permissions to perform this action. - Please contact your site admin if you think this is a mistake.', - 'jetpack' - ); - - self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); - - Jetpack::load_xml_rpc_client(); - $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); - $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); - $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); - $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); - $module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() ); - $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); - $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); - - register_rest_route( 'jetpack/v4', 'plans', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plans', - 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', - - ) ); - - register_rest_route( 'jetpack/v4', '/jitm', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_jitm_message', - ) ); - - register_rest_route( 'jetpack/v4', '/jitm', array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => __CLASS__ . '::delete_jitm_message' - ) ); - - // Register a site - register_rest_route( 'jetpack/v4', '/verify_registration', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::verify_registration', - ) ); - - // Authorize a remote user - register_rest_route( 'jetpack/v4', '/remote_authorize', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::remote_authorize', - ) ); - - // Get current connection status of Jetpack - register_rest_route( 'jetpack/v4', '/connection', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jetpack_connection_status', - ) ); - - // Test current connection status of Jetpack - register_rest_route( 'jetpack/v4', '/connection/test', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jetpack_connection_test', - 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', - ) ); - - // Endpoint specific for privileged servers to request detailed debug information. - register_rest_route( 'jetpack/v4', '/connection/test-wpcom/', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jetpack_connection_test_for_external', - 'permission_callback' => __CLASS__ . '::view_jetpack_connection_test_check', - ) ); - - register_rest_route( 'jetpack/v4', '/rewind', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_rewind_data', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); - - // Fetches a fresh connect URL - register_rest_route( 'jetpack/v4', '/connection/url', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::build_connect_url', - 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', - ) ); - - // Get current user connection data - register_rest_route( 'jetpack/v4', '/connection/data', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_user_connection_data', - 'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback', - ) ); - - // Set the connection owner - register_rest_route( 'jetpack/v4', '/connection/owner', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::set_connection_owner', - 'permission_callback' => __CLASS__ . '::set_connection_owner_permission_callback', - ) ); - - // Current user: get or set tracking settings. - register_rest_route( 'jetpack/v4', '/tracking/settings', array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_user_tracking_settings', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_user_tracking_settings', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - 'args' => array( - 'tracks_opt_out' => array( 'type' => 'boolean' ), - ), - ), - ) ); - - // Disconnect site from WordPress.com servers - register_rest_route( 'jetpack/v4', '/connection', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::disconnect_site', - 'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback', - ) ); - - // Disconnect/unlink user from WordPress.com servers - register_rest_route( 'jetpack/v4', '/connection/user', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::unlink_user', - 'permission_callback' => __CLASS__ . '::unlink_user_permission_callback', - ) ); - - // Get current site data - register_rest_route( 'jetpack/v4', '/site', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_site_data', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); - - // Get current site data - register_rest_route( 'jetpack/v4', '/site/features', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $site_endpoint, 'get_features' ), - 'permission_callback' => array( $site_endpoint , 'can_request' ), - ) ); - - // Confirm that a site in identity crisis should be in staging mode - register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::confirm_safe_mode', - 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check', - ) ); - - // IDC resolve: create an entirely new shadow site for this URL. - register_rest_route( 'jetpack/v4', '/identity-crisis/start-fresh', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::start_fresh_connection', - 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check', - ) ); - - // Handles the request to migrate stats and subscribers during an identity crisis. - register_rest_route( 'jetpack/v4', 'identity-crisis/migrate', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::migrate_stats_and_subscribers', - 'permissison_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check', - ) ); - - // Return all modules - register_rest_route( 'jetpack/v4', '/module/all', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $module_list_endpoint, 'process' ), - 'permission_callback' => array( $module_list_endpoint, 'can_request' ), - ) ); - - // Activate many modules - register_rest_route( 'jetpack/v4', '/module/all/active', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $module_list_endpoint, 'process' ), - 'permission_callback' => array( $module_list_endpoint, 'can_request' ), - 'args' => array( - 'modules' => array( - 'default' => '', - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - 'required' => true, - 'validate_callback' => __CLASS__ . '::validate_module_list', - ), - 'active' => array( - 'default' => true, - 'type' => 'boolean', - 'required' => false, - 'validate_callback' => __CLASS__ . '::validate_boolean', - ), - ) - ) ); - - // Return a single module and update it when needed - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - ) ); - - // Activate and deactivate a module - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/active', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $module_toggle_endpoint, 'process' ), - 'permission_callback' => array( $module_toggle_endpoint, 'can_request' ), - 'args' => array( - 'active' => array( - 'default' => true, - 'type' => 'boolean', - 'required' => true, - 'validate_callback' => __CLASS__ . '::validate_boolean', - ), - ) - ) ); - - // Update a module - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - 'args' => self::get_updateable_parameters( 'any' ) - ) ); - - // Get data for a specific module, i.e. Protect block count, WPCOM stats, - // Akismet spam count, etc. - register_rest_route( 'jetpack/v4', '/module/(?P<slug>[a-z\-]+)/data', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $module_data_endpoint, 'process' ), - 'permission_callback' => array( $module_data_endpoint, 'can_request' ), - 'args' => array( - 'range' => array( - 'default' => 'day', - 'type' => 'string', - 'required' => false, - 'validate_callback' => __CLASS__ . '::validate_string', - ), - ) - ) ); - - // Check if the API key for a specific service is valid or not - register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $module_data_endpoint, 'key_check' ), - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'sanitize_callback' => 'sanitize_text_field', - ) ); - - register_rest_route( 'jetpack/v4', '/module/(?P<service>[a-z\-]+)/key/check', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $module_data_endpoint, 'key_check' ), - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'sanitize_callback' => 'sanitize_text_field', - 'args' => array( - 'api_key' => array( - 'default' => '', - 'type' => 'string', - 'validate_callback' => __CLASS__ . '::validate_alphanum', - ), - ) - ) ); - - // Update any Jetpack module option or setting - register_rest_route( 'jetpack/v4', '/settings', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - 'args' => self::get_updateable_parameters( 'any' ) - ) ); - - // Update a module - register_rest_route( 'jetpack/v4', '/settings/(?P<slug>[a-z\-]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - 'args' => self::get_updateable_parameters() - ) ); - - // Return all module settings - register_rest_route( 'jetpack/v4', '/settings/', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $core_api_endpoint, 'process' ), - 'permission_callback' => array( $core_api_endpoint, 'can_request' ), - ) ); - - // Reset all Jetpack options - register_rest_route( 'jetpack/v4', '/options/(?P<options>[a-z\-]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::reset_jetpack_options', - 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', - ) ); - - // Return current Jumpstart status - register_rest_route( 'jetpack/v4', '/jumpstart', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::jumpstart_status', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - ) ); - - // Update Jumpstart - register_rest_route( 'jetpack/v4', '/jumpstart', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::jumpstart_toggle', - 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', - 'args' => array( - 'active' => array( - 'required' => true, - 'validate_callback' => __CLASS__ . '::validate_boolean', - ), - ), - ) ); - - // Updates: get number of plugin updates available - register_rest_route( 'jetpack/v4', '/updates/plugins', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plugin_update_count', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); - - // Dismiss Jetpack Notices - register_rest_route( 'jetpack/v4', '/notice/(?P<notice>[a-z\-_]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::dismiss_notice', - 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', - ) ); - - // Plugins: get list of all plugins. - register_rest_route( 'jetpack/v4', '/plugins', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plugins', - 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', - ) ); - - // Plugins: check if the plugin is active. - register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_plugin', - 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', - ) ); - - // Widgets: get information about a widget that supports it. - register_rest_route( 'jetpack/v4', '/widgets/(?P<id>[0-9a-z\-_]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $widget_endpoint, 'process' ), - 'permission_callback' => array( $widget_endpoint, 'can_request' ), - ) ); - - // Site Verify: check if the site is verified, and a get verification token if not - register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::is_site_verified_and_token', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - ) ); - - register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::is_site_verified_and_token', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - ) ); - - // Site Verify: tell a service to verify the site - register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::verify_site', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'args' => array( - 'keyring_id' => array( - 'required' => true, - 'type' => 'integer', - 'validate_callback' => __CLASS__ . '::validate_posint', - ), - ) - ) ); - - // Get and set API keys. - // Note: permission_callback intentionally omitted from the GET method. - // Map block requires open access to API keys on the front end. - register_rest_route( - 'jetpack/v4', - '/service-api-keys/(?P<service>[a-z\-_]+)', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_service_api_key', - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_service_api_key', - 'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ), - 'args' => array( - 'service_api_key' => array( - 'required' => true, - 'type' => 'text', - ), - ), - ), - array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => __CLASS__ . '::delete_service_api_key', - 'permission_callback' => array( 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys','edit_others_posts_check' ), - ), - ) - ); - } - - public static function get_plans( $request ) { - $request = Jetpack_Client::wpcom_json_api_request_as_user( - '/plans?_locale=' . get_user_locale(), - '2', - array( - 'method' => 'GET', - 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), - ), - ) - ); - - $body = wp_remote_retrieve_body( $request ); - if ( 200 === wp_remote_retrieve_response_code( $request ) ) { - $data = $body; - } else { - // something went wrong so we'll just return the response without caching - return $body; - } - - return $data; - } - - /** - * Asks for a jitm, unless they've been disabled, in which case it returns an empty array - * - * @param $request WP_REST_Request - * - * @return array An array of jitms - */ - public static function get_jitm_message( $request ) { - require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' ); - - $jitm = Jetpack_JITM::init(); - - if ( ! $jitm ) { - return array(); - } - - return $jitm->get_messages( $request['message_path'], urldecode_deep( $request['query'] ) ); - } - - /** - * Dismisses a jitm - * @param $request WP_REST_Request The request - * - * @return bool Always True - */ - public static function delete_jitm_message( $request ) { - require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' ); - - $jitm = Jetpack_JITM::init(); - - if ( ! $jitm ) { - return true; - } - - return $jitm->dismiss( $request['id'], $request['feature_class'] ); - } - - /** - * Handles verification that a site is registered - * - * @since 5.4.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return array|wp-error - */ - public static function verify_registration( $request ) { - require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php'; - $xmlrpc_server = new Jetpack_XMLRPC_Server(); - $result = $xmlrpc_server->verify_registration( array( $request['secret_1'], $request['state'] ) ); - - if ( is_a( $result, 'IXR_Error' ) ) { - $result = new WP_Error( $result->code, $result->message ); - } - - return $result; - } - - - /** - * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic - * keyring to use to get the token if it is not - * - * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification - * - * @since 6.6.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return array|wp-error - */ - public static function is_site_verified_and_token( $request ) { - /** - * Return an error if the site uses a Maintenance / Coming Soon plugin - * and if the plugin is configured to make the site private. - * - * We currently handle the following plugins: - * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost) - * - https://wordpress.org/plugins/mojo-under-construction - * - https://wordpress.org/plugins/under-construction-page - * - https://wordpress.org/plugins/ultimate-under-construction - * - https://wordpress.org/plugins/coming-soon - * - * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter. - * If the filter returns true, we will consider the site as under construction. - */ - $mm_coming_soon = get_option( 'mm_coming_soon', null ); - $under_construction_activation_status = get_option( 'underConstructionActivationStatus', null ); - $ucp_options = get_option( 'ucp_options', array() ); - $uuc_settings = get_option( 'uuc_settings', array() ); - $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. - /** - * Allow plugins to mark a site as "under construction". - * - * @since 6.7.0 - * - * @param false bool Is the site under construction? Default to false. - */ - || true === apply_filters( 'jetpack_is_under_construction_plugin', false ) - ) { - return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) ); - } - - Jetpack::load_xml_rpc_client(); - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); - - $args = array( - 'user_id' => get_current_user_id(), - 'service' => $request[ 'service' ], - ); - - if ( isset( $request[ 'keyring_id' ] ) ) { - $args[ 'keyring_id' ] = $request[ 'keyring_id' ]; - } - - $xml->query( 'jetpack.isSiteVerified', $args ); - - if ( $xml->isError() ) { - return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) ); - } else { - return $xml->getResponse(); - } - } - - - - public static function verify_site( $request ) { - Jetpack::load_xml_rpc_client(); - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); - - $params = $request->get_json_params(); - - $xml->query( 'jetpack.verifySite', array( - 'user_id' => get_current_user_id(), - 'service' => $request[ 'service' ], - 'keyring_id' => $params[ 'keyring_id' ], - ) - ); - - if ( $xml->isError() ) { - return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) ); - } else { - $response = $xml->getResponse(); - - if ( ! empty( $response['errors'] ) ) { - $error = new WP_Error; - $error->errors = $response['errors']; - return $error; - } - - return $response; - } - } - - /** - * Handles verification that a site is registered - * - * @since 5.4.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return array|wp-error - */ - public static function remote_authorize( $request ) { - require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php'; - $xmlrpc_server = new Jetpack_XMLRPC_Server(); - $result = $xmlrpc_server->remote_authorize( $request ); - - if ( is_a( $result, 'IXR_Error' ) ) { - $result = new WP_Error( $result->code, $result->message ); - } - - return $result; - } - - /** - * Handles dismissing of Jetpack Notices - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return array|wp-error - */ - public static function dismiss_notice( $request ) { - $notice = $request['notice']; - - if ( ! isset( $request['dismissed'] ) || $request['dismissed'] !== true ) { - return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "dismissed".', 'jetpack' ), array( 'status' => 404 ) ); - } - - if ( isset( $notice ) && ! empty( $notice ) ) { - switch( $notice ) { - case 'feedback_dash_request': - case 'welcome': - $notices = get_option( 'jetpack_dismissed_notices', array() ); - $notices[ $notice ] = true; - update_option( 'jetpack_dismissed_notices', $notices ); - return rest_ensure_response( get_option( 'jetpack_dismissed_notices', array() ) ); - - default: - return new WP_Error( 'invalid_param', esc_html__( 'Invalid parameter "notice".', 'jetpack' ), array( 'status' => 404 ) ); - } - } - - return new WP_Error( 'required_param', esc_html__( 'Missing parameter "notice".', 'jetpack' ), array( 'status' => 404 ) ); - } - - /** - * Verify that the user can disconnect the site. - * - * @since 4.3.0 - * - * @return bool|WP_Error True if user is able to disconnect the site. - */ - public static function disconnect_site_permission_callback() { - if ( current_user_can( 'jetpack_disconnect' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - - } - - /** - * Verify that the user can get a connect/link URL - * - * @since 4.3.0 - * - * @return bool|WP_Error True if user is able to disconnect the site. - */ - public static function connect_url_permission_callback() { - if ( current_user_can( 'jetpack_connect_user' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - - } - - /** - * Verify that a user can get the data about the current user. - * Only those who can connect. - * - * @since 4.3.0 - * - * @uses Jetpack::is_user_connected(); - * - * @return bool|WP_Error True if user is able to unlink. - */ - public static function get_user_connection_data_permission_callback() { - if ( current_user_can( 'jetpack_connect_user' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_user_connection_data', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Check that user has permission to change the master user. - * - * @since 6.2.0 - * - * @return bool|WP_Error True if user is able to change master user. - */ - public static function set_connection_owner_permission_callback() { - if ( get_current_user_id() === Jetpack_Options::get_option( 'master_user' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_set_connection_owner', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked. - * - * @since 4.3.0 - * - * @uses Jetpack::is_user_connected(); - * - * @return bool|WP_Error True if user is able to unlink. - */ - public static function unlink_user_permission_callback() { - if ( current_user_can( 'jetpack_connect_user' ) && Jetpack::is_user_connected( get_current_user_id() ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_unlink_user', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can manage Jetpack modules. - * - * @since 4.3.0 - * - * @return bool Whether user has the capability 'jetpack_manage_modules'. - */ - public static function manage_modules_permission_check() { - if ( current_user_can( 'jetpack_manage_modules' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_manage_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can update Jetpack modules. - * - * @since 4.3.0 - * - * @return bool Whether user has the capability 'jetpack_configure_modules'. - */ - public static function configure_modules_permission_check() { - if ( current_user_can( 'jetpack_configure_modules' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_configure_modules', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can view Jetpack admin page. - * - * @since 4.3.0 - * - * @return bool Whether user has the capability 'jetpack_admin_page'. - */ - public static function view_admin_page_permission_check() { - if ( current_user_can( 'jetpack_admin_page' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_view_admin', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can mitigate an identity crisis. - * - * @since 4.4.0 - * - * @return bool Whether user has capability 'jetpack_disconnect'. - */ - public static function identity_crisis_mitigation_permission_check() { - if ( current_user_can( 'jetpack_disconnect' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_identity_crisis', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can update Jetpack general settings. - * - * @since 4.3.0 - * - * @return bool Whether user has the capability 'update_settings_permission_check'. - */ - public static function update_settings_permission_check() { - if ( current_user_can( 'jetpack_configure_modules' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_manage_settings', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can view Jetpack admin page and can activate plugins. - * - * @since 4.3.0 - * - * @return bool Whether user has the capability 'jetpack_admin_page' and 'activate_plugins'. - */ - public static function activate_plugins_permission_check() { - if ( current_user_can( 'jetpack_admin_page' ) && current_user_can( 'activate_plugins' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_activate_plugins', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Verify that user can edit other's posts (Editors and Administrators). - * - * @return bool Whether user has the capability 'edit_others_posts'. - */ - public static function edit_others_posts_check() { - if ( current_user_can( 'edit_others_posts' ) ) { - return true; - } - - return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); - } - - /** - * Contextual HTTP error code for authorization failure. - * - * Taken from rest_authorization_required_code() in WP-API plugin until is added to core. - * @see https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6 - * - * @since 4.3.0 - * - * @return int - */ - public static function rest_authorization_required_code() { - return is_user_logged_in() ? 403 : 401; - } - - /** - * Get connection status for this Jetpack site. - * - * @since 4.3.0 - * - * @return bool True if site is connected - */ - public static function jetpack_connection_status() { - return rest_ensure_response( array( - 'isActive' => Jetpack::is_active(), - 'isStaging' => Jetpack::is_staging_site(), - 'devMode' => array( - 'isActive' => Jetpack::is_development_mode(), - 'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG, - 'url' => site_url() && false === strpos( site_url(), '.' ), - 'filter' => apply_filters( 'jetpack_development_mode', false ), - ), - ) - ); - } - - /** - * Test connection status for this Jetpack site. - * - * @since 6.8.0 - * - * @return array|WP_Error WP_Error returned if connection test does not succeed. - */ - public static function jetpack_connection_test() { - jetpack_require_lib( 'debugger' ); - $cxntests = new Jetpack_Cxn_Tests(); - - if ( $cxntests->pass() ) { - return rest_ensure_response( - array( - 'code' => 'success', - 'message' => __( 'All connection tests passed.', 'jetpack' ), - ) - ); - } else { - return $cxntests->output_fails_as_wp_error(); - } - } - - /** - * Test connection permission check method. - * - * @since 7.1.0 - * - * @return bool - */ - public static function view_jetpack_connection_test_check() { - if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'] ) ) { - return false; - } - $signature = base64_decode( $_GET['signature'] ); - - $signature_data = wp_json_encode( - array( - 'rest_route' => $_GET['rest_route'], - 'timestamp' => intval( $_GET['timestamp'] ), - 'url' => wp_unslash( $_GET['url'] ), - ) - ); - - if ( - ! function_exists( 'openssl_verify' ) - || ! openssl_verify( - $signature_data, - $signature, - JETPACK__DEBUGGER_PUBLIC_KEY - ) - ) { - return false; - } - - // signature timestamp must be within 5min of current time - if ( abs( time() - intval( $_GET['timestamp'] ) ) > 300 ) { - return false; - } - - return true; - } - - /** - * Test connection status for this Jetpack site, encrypt the results for decryption by a third-party. - * - * @since 7.1.0 - * - * @return array|mixed|object|WP_Error - */ - public static function jetpack_connection_test_for_external() { - // Since we are running this test for inclusion in the WP.com testing suite, let's not try to run them as part of these results. - add_filter( 'jetpack_debugger_run_self_test', '__return_false' ); - jetpack_require_lib( 'debugger' ); - $cxntests = new Jetpack_Cxn_Tests(); - - if ( $cxntests->pass() ) { - $result = array( - 'code' => 'success', - 'message' => __( 'All connection tests passed.', 'jetpack' ), - ); - } else { - $error = $cxntests->output_fails_as_wp_error(); // Using this so the output is similar both ways. - $errors = array(); - - // Borrowed from WP_REST_Server::error_to_response(). - foreach ( (array) $error->errors as $code => $messages ) { - foreach ( (array) $messages as $message ) { - $errors[] = array( - 'code' => $code, - 'message' => $message, - 'data' => $error->get_error_data( $code ), - ); - } - } - - $result = $errors[0]; - if ( count( $errors ) > 1 ) { - // Remove the primary error. - array_shift( $errors ); - $result['additional_errors'] = $errors; - } - } - - $result = wp_json_encode( $result ); - - $encrypted = $cxntests->encrypt_string_for_wpcom( $result ); - - if ( ! $encrypted || ! is_array( $encrypted ) ) { - return rest_ensure_response( - array( - 'code' => 'action_required', - 'message' => 'Please request results from the in-plugin debugger', - ) - ); - } - - return rest_ensure_response( - array( - 'code' => 'response', - 'debug' => array( - 'data' => $encrypted['data'], - 'key' => $encrypted['key'], - ), - ) - ); - } - - public static function rewind_data() { - $site_id = Jetpack_Options::get_option( 'id' ); - - if ( ! $site_id ) { - return new WP_Error( 'site_id_missing' ); - } - - $response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' ); - - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { - return new WP_Error( 'rewind_data_fetch_failed' ); - } - - $body = wp_remote_retrieve_body( $response ); - - return json_decode( $body ); - } - - /** - * Get rewind data - * - * @since 5.7.0 - * - * @return array Array of rewind properties. - */ - public static function get_rewind_data() { - $rewind_data = self::rewind_data(); - - if ( ! is_wp_error( $rewind_data ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Rewind data correctly received.', 'jetpack' ), - 'data' => wp_json_encode( $rewind_data ), - ) - ); - } - - if ( $rewind_data->get_error_code() === 'rewind_data_fetch_failed' ) { - return new WP_Error( 'rewind_data_fetch_failed', esc_html__( 'Failed fetching rewind data. Try again later.', 'jetpack' ), array( 'status' => 400 ) ); - } - - if ( $rewind_data->get_error_code() === 'site_id_missing' ) { - return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) ); - } - - return new WP_Error( - 'error_get_rewind_data', - esc_html__( 'Could not retrieve Rewind data.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - /** - * Disconnects Jetpack from the WordPress.com Servers - * - * @uses Jetpack::disconnect(); - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if Jetpack successfully disconnected. - */ - public static function disconnect_site( $request ) { - - if ( ! isset( $request['isActive'] ) || $request['isActive'] !== false ) { - return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); - } - - if ( Jetpack::is_active() ) { - Jetpack::disconnect(); - return rest_ensure_response( array( 'code' => 'success' ) ); - } - - return new WP_Error( 'disconnect_failed', esc_html__( 'Was not able to disconnect the site. Please try again.', 'jetpack' ), array( 'status' => 400 ) ); - } - - /** - * Gets a new connect raw URL with fresh nonce. - * - * @uses Jetpack::disconnect(); - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return string|WP_Error A raw URL if the connection URL could be built; error message otherwise. - */ - public static function build_connect_url() { - $url = Jetpack::init()->build_connect_url( true, false, false ); - if ( $url ) { - return rest_ensure_response( $url ); - } - - return new WP_Error( 'build_connect_url_failed', esc_html__( 'Unable to build the connect URL. Please reload the page and try again.', 'jetpack' ), array( 'status' => 400 ) ); - } - - /** - * Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack". - * Information about the master/primary user. - * Information about the current user. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return object - */ - public static function get_user_connection_data() { - require_once( JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php' ); - - $response = array( -// 'othersLinked' => Jetpack::get_other_linked_admins(), - 'currentUser' => jetpack_current_user_data(), - ); - return rest_ensure_response( $response ); - } - - /** - * Change the master user. - * - * @since 6.2.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if owner successfully changed. - */ - public static function set_connection_owner( $request ) { - if ( ! isset( $request['owner'] ) ) { - return new WP_Error( - 'invalid_param', - esc_html__( 'Invalid Parameter', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - $new_owner_id = $request['owner']; - if ( ! user_can( $new_owner_id, 'administrator' ) ) { - return new WP_Error( - 'new_owner_not_admin', - esc_html__( 'New owner is not admin', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - if ( $new_owner_id === get_current_user_id() ) { - return new WP_Error( - 'new_owner_is_current_user', - esc_html__( 'New owner is same as current user', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - if ( ! Jetpack::is_user_connected( $new_owner_id ) ) { - return new WP_Error( - 'new_owner_not_connected', - esc_html__( 'New owner is not connected', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - // Update the master user in Jetpack - $updated = Jetpack_Options::update_option( 'master_user', $new_owner_id ); - - // Notify WPCOM about the master user change - Jetpack::load_xml_rpc_client(); - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); - $xml->query( 'jetpack.switchBlogOwner', array( - 'new_blog_owner' => $new_owner_id, - ) ); - - if ( $updated && ! $xml->isError() ) { - return rest_ensure_response( - array( - 'code' => 'success', - ) - ); - } - return new WP_Error( - 'error_setting_new_owner', - esc_html__( 'Could not confirm new owner.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - /** - * Unlinks current user from the WordPress.com Servers. - * - * @since 4.3.0 - * @uses Jetpack::unlink_user - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if user successfully unlinked. - */ - public static function unlink_user( $request ) { - - if ( ! isset( $request['linked'] ) || $request['linked'] !== false ) { - return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); - } - - if ( Jetpack::unlink_user() ) { - return rest_ensure_response( - array( - 'code' => 'success' - ) - ); - } - - return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user. Please try again.', 'jetpack' ), array( 'status' => 400 ) ); - } - - /** - * Gets current user's tracking settings. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return WP_REST_Response|WP_Error Response, else error. - */ - public static function get_user_tracking_settings( $request ) { - if ( ! Jetpack::is_user_connected() ) { - $response = array( - 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. - ); - } else { - $response = Jetpack_Client::wpcom_json_api_request_as_user( - '/jetpack-user-tracking', - 'v2', - array( - 'method' => 'GET', - 'headers' => array( - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), - ), - ) - ); - if ( ! is_wp_error( $response ) ) { - $response = json_decode( wp_remote_retrieve_body( $response ), true ); - } - } - - return rest_ensure_response( $response ); - } - - /** - * Updates current user's tracking settings. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return WP_REST_Response|WP_Error Response, else error. - */ - public static function update_user_tracking_settings( $request ) { - if ( ! Jetpack::is_user_connected() ) { - $response = array( - 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. - ); - } else { - $response = Jetpack_Client::wpcom_json_api_request_as_user( - '/jetpack-user-tracking', - 'v2', - array( - 'method' => 'PUT', - 'headers' => array( - 'Content-Type' => 'application/json', - 'X-Forwarded-For' => Jetpack::current_user_ip( true ), - ), - ), - wp_json_encode( $request->get_params() ) - ); - if ( ! is_wp_error( $response ) ) { - $response = json_decode( wp_remote_retrieve_body( $response ), true ); - } - } - - return rest_ensure_response( $response ); - } - - /** - * Fetch site data from .com including the site's current plan. - * - * @since 5.5.0 - * - * @return array Array of site properties. - */ - public static function site_data() { - $site_id = Jetpack_Options::get_option( 'id' ); - - if ( ! $site_id ) { - new WP_Error( 'site_id_missing' ); - } - - $response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1' ); - - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { - return new WP_Error( 'site_data_fetch_failed' ); - } - - Jetpack_Plan::update_from_sites_response( $response ); - - $body = wp_remote_retrieve_body( $response ); - - return json_decode( $body ); - } - /** - * Get site data, including for example, the site's current plan. - * - * @since 4.3.0 - * - * @return array Array of site properties. - */ - public static function get_site_data() { - $site_data = self::site_data(); - - if ( ! is_wp_error( $site_data ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Site data correctly received.', 'jetpack' ), - 'data' => json_encode( $site_data ), - ) - ); - } - if ( $site_data->get_error_code() === 'site_data_fetch_failed' ) { - return new WP_Error( 'site_data_fetch_failed', esc_html__( 'Failed fetching site data. Try again later.', 'jetpack' ), array( 'status' => 400 ) ); - } - - if ( $site_data->get_error_code() === 'site_id_missing' ) { - return new WP_Error( 'site_id_missing', esc_html__( 'The ID of this site does not exist.', 'jetpack' ), array( 'status' => 404 ) ); - } - } - - /** - * Handles identity crisis mitigation, confirming safe mode for this site. - * - * @since 4.4.0 - * - * @return bool | WP_Error True if option is properly set. - */ - public static function confirm_safe_mode() { - $updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true ); - if ( $updated ) { - return rest_ensure_response( - array( - 'code' => 'success' - ) - ); - } - return new WP_Error( - 'error_setting_jetpack_safe_mode', - esc_html__( 'Could not confirm safe mode.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - /** - * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url. - * - * @since 4.4.0 - * - * @return bool | WP_Error True if option is properly set. - */ - public static function migrate_stats_and_subscribers() { - if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) { - return new WP_Error( - 'error_deleting_sync_error_idc', - esc_html__( 'Could not delete sync error option.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) { - return rest_ensure_response( - array( - 'code' => 'success' - ) - ); - } - return new WP_Error( - 'error_setting_jetpack_migrate', - esc_html__( 'Could not confirm migration.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - /** - * This IDC resolution will disconnect the site and re-connect to a completely new - * and separate shadow site than the original. - * - * It will first will disconnect the site without phoning home as to not disturb the production site. - * It then builds a fresh connection URL and sends it back along with the response. - * - * @since 4.4.0 - * @return bool|WP_Error - */ - public static function start_fresh_connection() { - // First clear the options / disconnect. - Jetpack::disconnect(); - return self::build_connect_url(); - } - - /** - * Reset Jetpack options - * - * @since 4.3.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $options Available options to reset are options|modules - * } - * - * @return bool|WP_Error True if options were reset. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function reset_jetpack_options( $request ) { - - if ( ! isset( $request['reset'] ) || $request['reset'] !== true ) { - return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); - } - - if ( isset( $request['options'] ) ) { - $data = $request['options']; - - switch( $data ) { - case ( 'options' ) : - $options_to_reset = Jetpack::get_jetpack_options_for_reset(); - - // Reset the Jetpack options - foreach ( $options_to_reset['jp_options'] as $option_to_reset ) { - Jetpack_Options::delete_option( $option_to_reset ); - } - - foreach ( $options_to_reset['wp_options'] as $option_to_reset ) { - delete_option( $option_to_reset ); - } - - // Reset to default modules - $default_modules = Jetpack::get_default_modules(); - Jetpack::update_active_modules( $default_modules ); - - // Jumpstart option is special - Jetpack_Options::update_option( 'jumpstart', 'new_connection' ); - 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; - - default: - return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); - } - } - - return new WP_Error( 'required_param', esc_html__( 'Missing parameter "type".', 'jetpack' ), array( 'status' => 404 ) ); - } - - /** - * Retrieves the current status of Jumpstart. - * - * @since 4.5.0 - * - * @return bool - */ - public static function jumpstart_status() { - return array( - 'status' => Jetpack_Options::get_option( 'jumpstart' ) - ); - } - - /** - * Toggles activation or deactivation of the JumpStart - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if toggling Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function jumpstart_toggle( $request ) { - - if ( $request[ 'active' ] ) { - return self::jumpstart_activate( $request ); - } else { - return self::jumpstart_deactivate( $request ); - } - } - - /** - * Activates a series of valid Jetpack modules and initializes some options. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if Jumpstart succeeded. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function jumpstart_activate( $request ) { - $modules = Jetpack::get_available_modules(); - $activate_modules = array(); - foreach ( $modules as $module ) { - $module_info = Jetpack::get_module( $module ); - if ( isset( $module_info['feature'] ) && is_array( $module_info['feature'] ) && in_array( 'Jumpstart', $module_info['feature'] ) ) { - $activate_modules[] = $module; - } - } - - // Collect success/error messages like modules that are properly activated. - $result = array( - 'activated_modules' => array(), - 'failed_modules' => array(), - ); - - // Update the jumpstart option - if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) { - $result['jumpstart_activated'] = Jetpack_Options::update_option( 'jumpstart', 'jumpstart_activated' ); - } - - // Check for possible conflicting plugins - $module_slugs_filtered = Jetpack::init()->filter_default_modules( $activate_modules ); - - foreach ( $module_slugs_filtered as $module_slug ) { - Jetpack::log( 'activate', $module_slug ); - if ( Jetpack::activate_module( $module_slug, false, false ) ) { - $result['activated_modules'][] = $module_slug; - } else { - $result['failed_modules'][] = $module_slug; - } - } - - // Set the default sharing buttons and set to display on posts if none have been set. - $sharing_services = get_option( 'sharing-services' ); - $sharing_options = get_option( 'sharing-options' ); - if ( empty( $sharing_services['visible'] ) ) { - // Default buttons to set - $visible = array( - 'twitter', - 'facebook', - ); - $hidden = array(); - - // Set some sharing settings - if ( class_exists( 'Sharing_Service' ) ) { - $sharing = new Sharing_Service(); - $sharing_options['global'] = array( - 'button_style' => 'icon', - 'sharing_label' => $sharing->default_sharing_label, - 'open_links' => 'same', - 'show' => array( 'post' ), - 'custom' => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array() - ); - - $result['sharing_options'] = update_option( 'sharing-options', $sharing_options ); - $result['sharing_services'] = update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) ); - } - } - - // If all Jumpstart modules were activated - if ( empty( $result['failed_modules'] ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Jumpstart done.', 'jetpack' ), - 'data' => $result, - ) ); - } - - return new WP_Error( 'jumpstart_failed', esc_html( sprintf( _n( 'Jumpstart failed activating this module: %s.', 'Jumpstart failed activating these modules: %s.', count( $result['failed_modules'] ), 'jetpack' ), join( ', ', $result['failed_modules'] ) ) ), array( 'status' => 400 ) ); - } - - /** - * Dismisses Jumpstart so user is not prompted to go through it again. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool|WP_Error True if Jumpstart was disabled or was nothing to dismiss. Otherwise, a WP_Error instance with a message. - */ - public static function jumpstart_deactivate( $request ) { - - // If dismissed, flag the jumpstart option as such. - if ( 'new_connection' === Jetpack_Options::get_option( 'jumpstart' ) ) { - if ( Jetpack_Options::update_option( 'jumpstart', 'jumpstart_dismissed' ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Jumpstart dismissed.', 'jetpack' ), - ) ); - } else { - return new WP_Error( 'jumpstart_failed_dismiss', esc_html__( 'Jumpstart could not be dismissed.', 'jetpack' ), array( 'status' => 400 ) ); - } - } - - // If this was not a new connection and there was nothing to dismiss, don't fail. - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Nothing to dismiss. This was not a new connection.', 'jetpack' ), - ) ); - } - - /** - * Get the query parameters to update module options or general settings. - * - * @since 4.3.0 - * @since 4.4.0 Accepts a $selector parameter. - * - * @param string $selector Selects a set of options to update, Can be empty, a module slug or 'any'. - * - * @return array - */ - public static function get_updateable_parameters( $selector = '' ) { - $parameters = array( - 'context' => array( - 'default' => 'edit', - ), - ); - - return array_merge( $parameters, self::get_updateable_data_list( $selector ) ); - } - - /** - * Returns a list of module options or general settings that can be updated. - * - * @since 4.3.0 - * @since 4.4.0 Accepts 'any' as a parameter which will make it return the entire list. - * - * @param string|array $selector Module slug, 'any', or an array of parameters. - * If empty, it's assumed we're updating a module and we'll try to get its slug. - * If 'any' the full list is returned. - * If it's an array of parameters, includes the elements by matching keys. - * - * @return array - */ - public static function get_updateable_data_list( $selector = '' ) { - - $options = array( - - // Carousel - 'carousel_background_color' => array( - 'description' => esc_html__( 'Color scheme.', 'jetpack' ), - 'type' => 'string', - 'default' => 'black', - 'enum' => array( - 'black', - 'white', - ), - 'enum_labels' => array( - 'black' => esc_html__( 'Black', 'jetpack' ), - 'white' => esc_html__( 'White', 'jetpack' ), - ), - 'validate_callback' => __CLASS__ . '::validate_list_item', - 'jp_group' => 'carousel', - ), - 'carousel_display_exif' => array( - 'description' => wp_kses( sprintf( __( 'Show photo metadata (<a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ), array( 'a' => array( 'href' => true, 'target' => true ) ) ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'carousel', - ), - - // Comments - 'highlander_comment_form_prompt' => array( - 'description' => esc_html__( 'Greeting Text', 'jetpack' ), - 'type' => 'string', - 'default' => esc_html__( 'Leave a Reply', 'jetpack' ), - 'sanitize_callback' => 'sanitize_text_field', - 'jp_group' => 'comments', - ), - 'jetpack_comment_form_color_scheme' => array( - 'description' => esc_html__( "Color scheme", 'jetpack' ), - 'type' => 'string', - 'default' => 'light', - 'enum' => array( - 'light', - 'dark', - 'transparent', - ), - 'enum_labels' => array( - 'light' => esc_html__( 'Light', 'jetpack' ), - 'dark' => esc_html__( 'Dark', 'jetpack' ), - 'transparent' => esc_html__( 'Transparent', 'jetpack' ), - ), - 'validate_callback' => __CLASS__ . '::validate_list_item', - 'jp_group' => 'comments', - ), - - // Custom Content Types - 'jetpack_portfolio' => array( - 'description' => esc_html__( 'Enable or disable Jetpack portfolio post type.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'custom-content-types', - ), - 'jetpack_portfolio_posts_per_page' => array( - 'description' => esc_html__( 'Number of entries to show at most in Portfolio pages.', 'jetpack' ), - 'type' => 'integer', - 'default' => 10, - 'validate_callback' => __CLASS__ . '::validate_posint', - 'jp_group' => 'custom-content-types', - ), - 'jetpack_testimonial' => array( - 'description' => esc_html__( 'Enable or disable Jetpack testimonial post type.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'custom-content-types', - ), - 'jetpack_testimonial_posts_per_page' => array( - 'description' => esc_html__( 'Number of entries to show at most in Testimonial pages.', 'jetpack' ), - 'type' => 'integer', - 'default' => 10, - 'validate_callback' => __CLASS__ . '::validate_posint', - 'jp_group' => 'custom-content-types', - ), - - // Galleries - 'tiled_galleries' => array( - 'description' => esc_html__( 'Display all your gallery pictures in a cool mosaic.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'tiled-gallery', - ), - - 'gravatar_disable_hovercards' => array( - 'description' => esc_html__( "View people's profiles when you mouse over their Gravatars", 'jetpack' ), - 'type' => 'string', - 'default' => 'enabled', - // Not visible. This is used as the checkbox value. - 'enum' => array( - 'enabled', - 'disabled', - ), - 'enum_labels' => array( - 'enabled' => esc_html__( 'Enabled', 'jetpack' ), - 'disabled' => esc_html__( 'Disabled', 'jetpack' ), - ), - 'validate_callback' => __CLASS__ . '::validate_list_item', - 'jp_group' => 'gravatar-hovercards', - ), - - // Infinite Scroll - 'infinite_scroll' => array( - 'description' => esc_html__( 'To infinity and beyond', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'infinite-scroll', - ), - 'infinite_scroll_google_analytics' => array( - 'description' => esc_html__( 'Use Google Analytics with Infinite Scroll', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'infinite-scroll', - ), - - // Likes - 'wpl_default' => array( - 'description' => esc_html__( 'WordPress.com Likes are', 'jetpack' ), - 'type' => 'string', - 'default' => 'on', - 'enum' => array( - 'on', - 'off', - ), - 'enum_labels' => array( - 'on' => esc_html__( 'On for all posts', 'jetpack' ), - 'off' => esc_html__( 'Turned on per post', 'jetpack' ), - ), - 'validate_callback' => __CLASS__ . '::validate_list_item', - 'jp_group' => 'likes', - ), - 'social_notifications_like' => array( - 'description' => esc_html__( 'Send email notification when someone likes a post', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'likes', - ), - - // Markdown - 'wpcom_publish_comments_with_markdown' => array( - 'description' => esc_html__( 'Use Markdown for comments.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'markdown', - ), - 'wpcom_publish_posts_with_markdown' => array( - 'description' => esc_html__( 'Use Markdown for posts.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'markdown', - ), - - // Mobile Theme - 'wp_mobile_excerpt' => array( - 'description' => esc_html__( 'Excerpts', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'minileven', - ), - 'wp_mobile_featured_images' => array( - 'description' => esc_html__( 'Featured Images', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'minileven', - ), - 'wp_mobile_app_promos' => array( - 'description' => esc_html__( 'Show a promo for the WordPress mobile apps in the footer of the mobile theme.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'minileven', - ), - - // Monitor - 'monitor_receive_notifications' => array( - 'description' => esc_html__( 'Receive Monitor Email Notifications.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'monitor', - ), - - // Post by Email - 'post_by_email_address' => array( - 'description' => esc_html__( 'Email Address', 'jetpack' ), - 'type' => 'string', - 'default' => 'noop', - 'enum' => array( - 'noop', - 'create', - 'regenerate', - 'delete', - ), - 'enum_labels' => array( - 'noop' => '', - 'create' => esc_html__( 'Create Post by Email address', 'jetpack' ), - 'regenerate' => esc_html__( 'Regenerate Post by Email address', 'jetpack' ), - 'delete' => esc_html__( 'Delete Post by Email address', 'jetpack' ), - ), - 'validate_callback' => __CLASS__ . '::validate_list_item', - 'jp_group' => 'post-by-email', - ), - - // Protect - 'jetpack_protect_key' => array( - 'description' => esc_html__( 'Protect API key', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_alphanum', - 'jp_group' => 'protect', - ), - 'jetpack_protect_global_whitelist' => array( - 'description' => esc_html__( 'Protect global whitelist', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_string', - 'sanitize_callback' => 'esc_textarea', - 'jp_group' => 'protect', - ), - - // Sharing - 'sharing_services' => array( - 'description' => esc_html__( 'Enabled Services and those hidden behind a button', 'jetpack' ), - 'type' => 'object', - 'default' => array( - 'visible' => array( 'twitter', 'facebook', 'google-plus-1' ), - 'hidden' => array(), - ), - 'validate_callback' => __CLASS__ . '::validate_services', - 'jp_group' => 'sharedaddy', - ), - 'button_style' => array( - 'description' => esc_html__( 'Button Style', 'jetpack' ), - 'type' => 'string', - 'default' => 'icon', - 'enum' => array( - 'icon-text', - 'icon', - 'text', - 'official', - ), - 'enum_labels' => array( - 'icon-text' => esc_html__( 'Icon + text', 'jetpack' ), - 'icon' => esc_html__( 'Icon only', 'jetpack' ), - 'text' => esc_html__( 'Text only', 'jetpack' ), - 'official' => esc_html__( 'Official buttons', 'jetpack' ), - ), - 'validate_callback' => __CLASS__ . '::validate_list_item', - 'jp_group' => 'sharedaddy', - ), - 'sharing_label' => array( - 'description' => esc_html__( 'Sharing Label', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_string', - 'sanitize_callback' => 'esc_html', - 'jp_group' => 'sharedaddy', - ), - 'show' => array( - 'description' => esc_html__( 'Views where buttons are shown', 'jetpack' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string' - ), - 'default' => array( 'post' ), - 'validate_callback' => __CLASS__ . '::validate_sharing_show', - 'jp_group' => 'sharedaddy', - ), - 'jetpack-twitter-cards-site-tag' => array( - 'description' => esc_html__( "The Twitter username of the owner of this site's domain.", 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_twitter_username', - 'sanitize_callback' => 'esc_html', - 'jp_group' => 'sharedaddy', - ), - 'sharedaddy_disable_resources' => array( - 'description' => esc_html__( 'Disable CSS and JS', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'sharedaddy', - ), - 'custom' => array( - 'description' => esc_html__( 'Custom sharing services added by user.', 'jetpack' ), - 'type' => 'object', - 'default' => array( - 'sharing_name' => '', - 'sharing_url' => '', - 'sharing_icon' => '', - ), - 'validate_callback' => __CLASS__ . '::validate_custom_service', - 'jp_group' => 'sharedaddy', - ), - // Not an option, but an action that can be perfomed on the list of custom services passing the service ID. - 'sharing_delete_service' => array( - 'description' => esc_html__( 'Delete custom sharing service.', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_custom_service_id', - 'jp_group' => 'sharedaddy', - ), - - // SSO - 'jetpack_sso_require_two_step' => array( - 'description' => esc_html__( 'Require Two-Step Authentication', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'sso', - ), - 'jetpack_sso_match_by_email' => array( - 'description' => esc_html__( 'Match by Email', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'sso', - ), - - // Subscriptions - 'stb_enabled' => array( - 'description' => esc_html__( "Show a <em>'follow blog'</em> option in the comment form", 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'subscriptions', - ), - 'stc_enabled' => array( - 'description' => esc_html__( "Show a <em>'follow comments'</em> option in the comment form", 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'subscriptions', - ), - - // Related Posts - 'show_headline' => array( - 'description' => esc_html__( 'Highlight related content with a heading', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'related-posts', - ), - 'show_thumbnails' => array( - 'description' => esc_html__( 'Show a thumbnail image where available', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'related-posts', - ), - - // Spelling and Grammar - After the Deadline - 'onpublish' => array( - 'description' => esc_html__( 'Proofread when a post or page is first published.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'onupdate' => array( - 'description' => esc_html__( 'Proofread when a post or page is updated.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Bias Language' => array( - 'description' => esc_html__( 'Bias Language', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Cliches' => array( - 'description' => esc_html__( 'Clichés', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Complex Expression' => array( - 'description' => esc_html__( 'Complex Phrases', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Diacritical Marks' => array( - 'description' => esc_html__( 'Diacritical Marks', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Double Negative' => array( - 'description' => esc_html__( 'Double Negatives', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Hidden Verbs' => array( - 'description' => esc_html__( 'Hidden Verbs', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Jargon Language' => array( - 'description' => esc_html__( 'Jargon', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Passive voice' => array( - 'description' => esc_html__( 'Passive Voice', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Phrases to Avoid' => array( - 'description' => esc_html__( 'Phrases to Avoid', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'Redundant Expression' => array( - 'description' => esc_html__( 'Redundant Phrases', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'guess_lang' => array( - 'description' => esc_html__( 'Use automatically detected language to proofread posts and pages', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'after-the-deadline', - ), - 'ignored_phrases' => array( - 'description' => esc_html__( 'Add Phrase to be ignored', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'sanitize_callback' => 'esc_html', - 'jp_group' => 'after-the-deadline', - ), - 'unignore_phrase' => array( - 'description' => esc_html__( 'Remove Phrase from being ignored', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'sanitize_callback' => 'esc_html', - 'jp_group' => 'after-the-deadline', - ), - - // Verification Tools - 'google' => array( - 'description' => esc_html__( 'Google Search Console', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_verification_service', - 'jp_group' => 'verification-tools', - ), - 'bing' => array( - 'description' => esc_html__( 'Bing Webmaster Center', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_verification_service', - 'jp_group' => 'verification-tools', - ), - 'pinterest' => array( - 'description' => esc_html__( 'Pinterest Site Verification', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_verification_service', - 'jp_group' => 'verification-tools', - ), - 'yandex' => array( - 'description' => esc_html__( 'Yandex Site Verification', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_verification_service', - 'jp_group' => 'verification-tools', - ), - 'enable_header_ad' => array( - 'description' => esc_html__( 'Display an ad unit at the top of each page.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_approved' => array( - 'description' => esc_html__( 'Is site approved for WordAds?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_second_belowpost' => array( - 'description' => esc_html__( 'Display second ad below post?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_front_page' => array( - 'description' => esc_html__( 'Display ads on the front page?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_post' => array( - 'description' => esc_html__( 'Display ads on posts?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_page' => array( - 'description' => esc_html__( 'Display ads on pages?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_display_archive' => array( - 'description' => esc_html__( 'Display ads on archive pages?', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'wordads', - ), - 'wordads_custom_adstxt' => array( - 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_string', - 'sanitize_callback' => 'sanitize_textarea_field', - 'jp_group' => 'wordads', - ), - - // Google Analytics - 'google_analytics_tracking_id' => array( - 'description' => esc_html__( 'Google Analytics', 'jetpack' ), - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_alphanum', - 'jp_group' => 'google-analytics', - ), - - // Stats - 'admin_bar' => array( - 'description' => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'stats', - ), - 'roles' => array( - 'description' => esc_html__( 'Select the roles that will be able to view stats reports.', 'jetpack' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string' - ), - 'default' => array( 'administrator' ), - 'validate_callback' => __CLASS__ . '::validate_stats_roles', - 'sanitize_callback' => __CLASS__ . '::sanitize_stats_allowed_roles', - 'jp_group' => 'stats', - ), - 'count_roles' => array( - 'description' => esc_html__( 'Count the page views of registered users who are logged in.', 'jetpack' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string' - ), - 'default' => array( 'administrator' ), - 'validate_callback' => __CLASS__ . '::validate_stats_roles', - 'jp_group' => 'stats', - ), - 'blog_id' => array( - 'description' => esc_html__( 'Blog ID.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'stats', - ), - 'do_not_track' => array( - 'description' => esc_html__( 'Do not track.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'stats', - ), - 'hide_smile' => array( - 'description' => esc_html__( 'Hide the stats smiley face image.', 'jetpack' ), - 'type' => 'boolean', - 'default' => 1, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'stats', - ), - 'version' => array( - 'description' => esc_html__( 'Version.', 'jetpack' ), - 'type' => 'integer', - 'default' => 9, - 'validate_callback' => __CLASS__ . '::validate_posint', - 'jp_group' => 'stats', - ), - - // Akismet - Not a module, but a plugin. The options can be passed and handled differently. - 'akismet_show_user_comments_approved' => array( - 'description' => '', - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'settings', - ), - - 'wordpress_api_key' => array( - 'description' => '', - 'type' => 'string', - 'default' => '', - 'validate_callback' => __CLASS__ . '::validate_alphanum', - 'jp_group' => 'settings', - ), - - // Apps card on dashboard - 'dismiss_dash_app_card' => array( - 'description' => '', - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'settings', - ), - - // Empty stats card dismiss - 'dismiss_empty_stats_card' => array( - 'description' => '', - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'settings', - ), - - 'lang_id' => array( - 'description' => esc_html__( 'Primary language for the site.', 'jetpack' ), - 'type' => 'string', - 'default' => 'en_US', - 'jp_group' => 'settings', - ), - - 'onboarding' => array( - 'description' => '', - 'type' => 'object', - 'default' => array( - 'siteTitle' => '', - 'siteDescription' => '', - 'siteType' => 'personal', - 'homepageFormat' => 'posts', - 'addContactForm' => 0, - 'businessAddress' => array( - 'name' => '', - 'street' => '', - 'city' => '', - 'state' => '', - 'zip' => '', - ), - 'installWooCommerce' => false, - ), - 'validate_callback' => __CLASS__ . '::validate_onboarding', - 'jp_group' => 'settings', - ), - - // Show welcome for newly purchased plan - 'show_welcome_for_new_plan' => array( - 'description' => '', - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'settings', - ), - - ); - - // Add modules to list so they can be toggled - $modules = Jetpack::get_available_modules(); - if ( is_array( $modules ) && ! empty( $modules ) ) { - $module_args = array( - 'description' => '', - 'type' => 'boolean', - 'default' => 0, - 'validate_callback' => __CLASS__ . '::validate_boolean', - 'jp_group' => 'modules', - ); - foreach( $modules as $module ) { - $options[ $module ] = $module_args; - } - } - - if ( is_array( $selector ) ) { - - // 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 - return $options; - } - - // We're updating the options for a single module. - if ( empty( $selector ) ) { - $selector = self::get_module_requested(); - } - $selected = array(); - foreach ( $options as $option => $attributes ) { - - // Not adding an isset( $attributes['jp_group'] ) because if it's not set, it must be fixed, otherwise options will fail. - if ( $selector === $attributes['jp_group'] ) { - $selected[ $option ] = $attributes; - } - } - return $selected; - } - - /** - * Validates that the parameters are proper values that can be set during Jetpack onboarding. - * - * @since 5.4.0 - * - * @param array $onboarding_data Values 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_onboarding( $onboarding_data, $request, $param ) { - if ( ! is_array( $onboarding_data ) ) { - return new WP_Error( 'invalid_param', esc_html__( 'Not valid onboarding data.', 'jetpack' ) ); - } - foreach ( $onboarding_data as $value ) { - if ( is_string( $value ) ) { - $onboarding_choice = self::validate_string( $value, $request, $param ); - } elseif ( is_array( $value ) ) { - $onboarding_choice = self::validate_onboarding( $value, $request, $param ); - } else { - $onboarding_choice = self::validate_boolean( $value, $request, $param ); - } - if ( is_wp_error( $onboarding_choice ) ) { - return $onboarding_choice; - } - } - return true; - } - - /** - * Validates that the parameter is either a pure boolean or a numeric string that can be mapped to a boolean. - * - * @since 4.3.0 - * - * @param string|bool $value Value to check. - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - public static function validate_boolean( $value, $request, $param ) { - if ( ! is_bool( $value ) && ! ( ( ctype_digit( $value ) || is_numeric( $value ) ) && in_array( $value, array( 0, 1 ) ) ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be true, false, 0 or 1.', 'jetpack' ), $param ) ); - } - return true; - } - - /** - * Validates that the parameter is a positive integer. - * - * @since 4.3.0 - * - * @param int $value Value to check. - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - public static function validate_posint( $value = 0, $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 true; - } - - /** - * Validates that the parameter belongs to a list of admitted values. - * - * @since 4.3.0 - * - * @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_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 ) ); - } - $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 ) ) { - return new WP_Error( 'invalid_param_value', sprintf( - /* Translators: first variable is the parameter passed to endpoint that holds the list item, the second is a list of admitted values. */ - esc_html__( '%1$s must be one of %2$s', 'jetpack' ), $param, implode( ', ', $enum ) - ) ); - } - } - return true; - } - - /** - * Validates that the parameter belongs to a list of admitted values. - * - * @since 4.3.0 - * - * @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_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 ) ); - } - - $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 ) ); - } - - return true; - } - - /** - * Validates that the parameter is an alphanumeric or empty string (to be able to clear the field). - * - * @since 4.3.0 - * - * @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_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 true; - } - - /** - * Validates that the parameter is a tag or id for a verification service, or an empty string (to be able to clear the field). - * - * @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. - * - * @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 true; - } - - /** - * Validates that the parameter is among the roles allowed for Stats. - * - * @since 4.3.0 - * - * @param string|bool $value Value to check. - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - public static function validate_stats_roles( $value, $request, $param ) { - if ( ! empty( $value ) && ! array_intersect( self::$stats_roles, $value ) ) { - return new WP_Error( 'invalid_param', sprintf( - /* 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 ) - ) ); - } - return true; - } - - /** - * Validates that the parameter is among the views where the Sharing can be displayed. - * - * @since 4.3.0 - * - * @param string|bool $value Value to check. - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - 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 ) ); - } - 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 */ - esc_html__( '%1$s must be %2$s.', 'jetpack' ), $param, join( ', ', $views ) - ) ); - } - return true; - } - - /** - * Validates that the parameter is among the views where the Sharing can be displayed. - * - * @since 4.3.0 - * - * @param string|bool $value { - * Value to check received by request. - * - * @type array $visible List of slug of services to share to that are displayed directly in the page. - * @type array $hidden List of slug of services to share to that are concealed in a folding menu. - * } - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - 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 ) ); - } - - // Allow to clear everything. - if ( empty( $value['visible'] ) && empty( $value['hidden'] ) ) { - return true; - } - - 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() ); - - if ( - ( ! empty( $value['visible'] ) && ! array_intersect( $value['visible'], $services ) ) - || - ( ! empty( $value['hidden'] ) && ! array_intersect( $value['hidden'], $services ) ) ) - { - return new WP_Error( 'invalid_param', sprintf( - /* Translators: placeholder 1 is a parameter holding the services passed to endpoint, placeholder 2 is a list of all Jetpack Sharing services */ - esc_html__( '%1$s visible and hidden items must be a list of %2$s.', 'jetpack' ), $param, join( ', ', $services ) - ) ); - } - return true; - } - - /** - * Validates that the parameter has enough information to build a custom sharing button. - * - * @since 4.3.0 - * - * @param string|bool $value Value to check. - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - 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 ) ); - } - - // Allow to clear everything. - if ( empty( $value['sharing_name'] ) && empty( $value['sharing_url'] ) && empty( $value['sharing_icon'] ) ) { - return true; - } - - 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' ) ); - } - - 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 true; - } - - /** - * Validates that the parameter is a custom sharing service ID like 'custom-1461976264'. - * - * @since 4.3.0 - * - * @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_custom_service_id( $value = '', $request, $param ) { - if ( ! empty( $value ) && ( ! is_string( $value ) || ! preg_match( '/custom\-[0-1]+/i', $value ) ) ) { - return new WP_Error( 'invalid_param', sprintf( esc_html__( "%s must be a string prefixed with 'custom-' and followed by a numeric ID.", 'jetpack' ), $param ) ); - } - - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { - 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() ); - - 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 ) ); - } - - return true; - } - - /** - * Validates that the parameter is a Twitter username or empty string (to be able to clear the field). - * - * @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. - * - * @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 true; - } - - /** - * Validates that the parameter is a string. - * - * @since 4.3.0 - * - * @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_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 true; - } - - /** - * If for some reason the roles allowed to see Stats are empty (for example, user tampering with checkboxes), - * return an array with only 'administrator' as the allowed role and save it for 'roles' option. - * - * @since 4.3.0 - * - * @param string|bool $value Value to check. - * - * @return bool|array - */ - public static function sanitize_stats_allowed_roles( $value ) { - if ( empty( $value ) ) { - return array( 'administrator' ); - } - return $value; - } - - /** - * Get the currently accessed route and return the module slug in it. - * - * @since 4.3.0 - * - * @param string $route Regular expression for the endpoint with the module slug to return. - * - * @return array|string - */ - public static function get_module_requested( $route = '/module/(?P<slug>[a-z\-]+)' ) { - - if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { - return ''; - } - - preg_match( "#$route#", $GLOBALS['wp']->query_vars['rest_route'], $module ); - - if ( empty( $module['slug'] ) ) { - return ''; - } - - return $module['slug']; - } - - /** - * Adds extra information for modules. - * - * @since 4.3.0 - * - * @param string|array $modules Can be a single module or a list of modules. - * @param null|string $slug Slug of the module in the first parameter. - * - * @return array|string - */ - public static function prepare_modules_for_response( $modules = '', $slug = null ) { - global $wp_rewrite; - - /** This filter is documented in modules/sitemaps/sitemaps.php */ - $location = apply_filters( 'jetpack_sitemap_location', '' ); - - if ( $wp_rewrite->using_index_permalinks() ) { - $sitemap_url = home_url( '/index.php' . $location . '/sitemap.xml' ); - $news_sitemap_url = home_url( '/index.php' . $location . '/news-sitemap.xml' ); - } else if ( $wp_rewrite->using_permalinks() ) { - $sitemap_url = home_url( $location . '/sitemap.xml' ); - $news_sitemap_url = home_url( $location . '/news-sitemap.xml' ); - } else { - $sitemap_url = home_url( $location . '/?jetpack-sitemap=sitemap.xml' ); - $news_sitemap_url = home_url( $location . '/?jetpack-sitemap=news-sitemap.xml' ); - } - - if ( is_null( $slug ) && isset( $modules['sitemaps'] ) ) { - // Is a list of modules - $modules['sitemaps']['extra']['sitemap_url'] = $sitemap_url; - $modules['sitemaps']['extra']['news_sitemap_url'] = $news_sitemap_url; - } elseif ( 'sitemaps' == $slug ) { - // It's a single module - $modules['extra']['sitemap_url'] = $sitemap_url; - $modules['extra']['news_sitemap_url'] = $news_sitemap_url; - } - return $modules; - } - - /** - * Remove 'validate_callback' item from options available for module. - * Fetch current option value and add to array of module options. - * Prepare values of module options that need special handling, like those saved in wpcom. - * - * @since 4.3.0 - * - * @param string $module Module slug. - * @return array - */ - public static function prepare_options_for_response( $module = '' ) { - $options = self::get_updateable_data_list( $module ); - - if ( ! is_array( $options ) || empty( $options ) ) { - return $options; - } - - // Some modules need special treatment. - switch ( $module ) { - - case 'monitor': - // 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 - $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 - $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' ); - } - $options['jetpack_protect_global_whitelist']['current_value'] = jetpack_protect_format_whitelist(); - break; - - case 'related-posts': - // It's local, but it must be broken apart since it's saved as an array. - $options = self::split_options( $options, Jetpack_Options::get_option( 'relatedposts' ) ); - break; - - case 'verification-tools': - // It's local, but it must be broken apart since it's saved as an array. - $options = self::split_options( $options, get_option( 'verification_services_codes' ) ); - break; - - case 'google-analytics': - $wga = get_option( 'jetpack_wga' ); - $code = ''; - if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) { - $code = $wga[ 'code' ]; - } - $options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code; - break; - - case 'sharedaddy': - // It's local, but it must be broken apart since it's saved as an array. - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { - break; - } - $sharer = new Sharing_Service(); - $options = self::split_options( $options, $sharer->get_global_options() ); - $options['sharing_services']['current_value'] = $sharer->get_blog_services(); - $other_sharedaddy_options = array( 'jetpack-twitter-cards-site-tag', 'sharedaddy_disable_resources', 'sharing_delete_service' ); - foreach ( $other_sharedaddy_options as $key ) { - $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; - $current_value = get_option( $key, $default_value ); - $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] ); - } - break; - - case 'after-the-deadline': - if ( ! function_exists( 'AtD_get_options' ) ) { - include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' ); - } - $atd_options = array_merge( AtD_get_options( get_current_user_id(), 'AtD_options' ), AtD_get_options( get_current_user_id(), 'AtD_check_when' ) ); - unset( $atd_options['name'] ); - foreach ( $atd_options as $key => $value ) { - $options[ $key ]['current_value'] = self::cast_value( $value, $options[ $key ] ); - } - $atd_options = AtD_get_options( get_current_user_id(), 'AtD_guess_lang' ); - $options['guess_lang']['current_value'] = self::cast_value( isset( $atd_options['true'] ), $options[ 'guess_lang' ] ); - $options['ignored_phrases']['current_value'] = AtD_get_setting( get_current_user_id(), 'AtD_ignored_phrases' ); - unset( $options['unignore_phrase'] ); - break; - - case 'stats': - // It's local, but it must be broken apart since it's saved as an array. - if ( ! function_exists( 'stats_get_options' ) ) { - include_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' ); - } - $options = self::split_options( $options, stats_get_options() ); - break; - default: - // These option are just stored as plain WordPress options. - foreach ( $options as $key => $value ) { - $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; - $current_value = get_option( $key, $default_value ); - $options[ $key ]['current_value'] = self::cast_value( $current_value, $options[ $key ] ); - } - } - // 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 - if ( isset( $options[ $key ]['validate_callback'] ) ) { - unset( $options[ $key ]['validate_callback'] ); - } - $default_value = isset( $options[ $key ]['default'] ) ? $options[ $key ]['default'] : ''; - if ( ! array_key_exists( 'current_value', $options[ $key ] ) ) { - $options[ $key ]['current_value'] = self::cast_value( $default_value, $options[ $key ] ); - } - } - return $options; - } - - /** - * Splits module options saved as arrays like relatedposts or verification_services_codes into separate options to be returned in the response. - * - * @since 4.3.0 - * - * @param array $separate_options Array of options admitted by the module. - * @param array $grouped_options Option saved as array to be splitted. - * @param string $prefix Optional prefix for the separate option keys. - * - * @return array - */ - public static function split_options( $separate_options, $grouped_options, $prefix = '' ) { - if ( is_array( $grouped_options ) ) { - foreach ( $grouped_options as $key => $value ) { - $option_key = $prefix . $key; - if ( isset( $separate_options[ $option_key ] ) ) { - $separate_options[ $option_key ]['current_value'] = self::cast_value( $grouped_options[ $key ], $separate_options[ $option_key ] ); - } - } - } - return $separate_options; - } - - /** - * Perform a casting to the value specified in the option definition. - * - * @since 4.3.0 - * - * @param mixed $value Value to cast to the proper type. - * @param array $definition Type to cast the value to. - * - * @return bool|float|int|string - */ - public static function cast_value( $value, $definition ) { - if ( $value === 'NULL' ) { - return null; - } - - if ( isset( $definition['type'] ) ) { - switch ( $definition['type'] ) { - case 'boolean': - if ( 'true' === $value ) { - return true; - } elseif ( 'false' === $value ) { - return false; - } - return (bool) $value; - break; - - case 'integer': - return (int) $value; - break; - - case 'float': - return (float) $value; - break; - - case 'string': - return (string) $value; - break; - } - } - return $value; - } - - /** - * Get a value not saved locally. - * - * @since 4.3.0 - * - * @param string $module Module slug. - * @param string $option Option name. - * - * @return bool Whether user is receiving notifications or not. - */ - public static function get_remote_value( $module, $option ) { - - if ( in_array( $module, array( 'post-by-email' ), true ) ) { - $option .= get_current_user_id(); - } - - // If option doesn't exist, 'does_not_exist' will be returned. - $value = get_option( $option, 'does_not_exist' ); - - // If option exists, just return it. - if ( 'does_not_exist' !== $value ) { - return $value; - } - - // Only check a remote option if Jetpack is connected. - if ( ! Jetpack::is_active() ) { - return false; - } - - // Do what is necessary for each module. - switch ( $module ) { - case 'monitor': - // Load the class to use the method. If class can't be found, do nothing. - if ( ! class_exists( 'Jetpack_Monitor' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) { - return false; - } - $value = Jetpack_Monitor::user_receives_notifications( false ); - break; - - case 'post-by-email': - // Load the class to use the method. If class can't be found, do nothing. - if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) { - return false; - } - $post_by_email = new Jetpack_Post_By_Email(); - $value = $post_by_email->get_post_by_email_address(); - if ( $value === null ) { - $value = 'NULL'; // sentinel value so it actually gets set - } - break; - } - - // Normalize value to boolean. - if ( is_wp_error( $value ) || is_null( $value ) ) { - $value = false; - } - - // Save option to use it next time. - update_option( $option, $value ); - - return $value; - } - - /** - * Get number of plugin updates available. - * - * @since 4.3.0 - * - * @return mixed|WP_Error Number of plugin updates available. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function get_plugin_update_count() { - $updates = wp_get_update_data(); - if ( isset( $updates['counts'] ) && isset( $updates['counts']['plugins'] ) ) { - $count = $updates['counts']['plugins']; - if ( 0 == $count ) { - $response = array( - 'code' => 'success', - 'message' => esc_html__( 'All plugins are up-to-date. Keep up the good work!', 'jetpack' ), - 'count' => 0, - ); - } else { - $response = array( - 'code' => 'updates-available', - 'message' => esc_html( sprintf( _n( '%s plugin need updating.', '%s plugins need updating.', $count, 'jetpack' ), $count ) ), - 'count' => $count, - ); - } - return rest_ensure_response( $response ); - } - - return new WP_Error( 'not_found', esc_html__( 'Could not check updates for plugins on this site.', 'jetpack' ), array( 'status' => 404 ) ); - } - - - /** - * Returns a list of all plugins in the site. - * - * @since 4.2.0 - * @uses get_plugins() - * - * @return array - */ - private static function core_get_plugins() { - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ - $plugins = apply_filters( 'all_plugins', get_plugins() ); - - if ( is_array( $plugins ) && ! empty( $plugins ) ) { - foreach ( $plugins as $plugin_slug => $plugin_data ) { - $plugins[ $plugin_slug ]['active'] = self::core_is_plugin_active( $plugin_slug ); - } - return $plugins; - } - - return array(); - } - - /** - * Deprecated - Get third party plugin API keys. - * @deprecated - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function get_service_api_key( $request ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::get_service_api_key( $request ); - } - - /** - * Deprecated - Update third party plugin API keys. - * @deprecated - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function update_service_api_key( $request ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::update_service_api_key( $request ) ; - } - - /** - * Deprecated - Delete a third party plugin API key. - * @deprecated - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function delete_service_api_key( $request ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::delete_service_api_key( $request ); - } - - /** - * Deprecated - Validate the service provided in /service-api-keys/ endpoints. - * To add a service to these endpoints, add the service name to $valid_services - * and add '{service name}_api_key' to the non-compact return array in get_option_names(), - * in class-jetpack-options.php - * @deprecated - * - * @param string $service The service the API key is for. - * @return string Returns the service name if valid, null if invalid. - */ - public static function validate_service_api_service( $service = null ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_service( $service ); - } - - /** - * Error response for invalid service API key requests with an invalid service. - */ - public static function service_api_invalid_service_response() { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::service_api_invalid_service_response(); - } - - /** - * Deprecated - Validate API Key - * @deprecated - * - * @param string $key The API key to be validated. - * @param string $service The service the API key is for. - * - */ - public static function validate_service_api_key( $key = null, $service = null ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key( $key , $service ); - } - - /** - * Deprecated - Validate Mapbox API key - * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php - * @deprecated - * - * @param string $key The API key to be validated. - */ - public static function validate_service_api_key_mapbox( $key ) { - _deprecated_function( __METHOD__, 'jetpack-6.9.0', 'WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key' ); - return WPCOM_REST_API_V2_Endpoint_Service_API_Keys::validate_service_api_key_mapbox( $key ); - - } - - /** - * Checks if the queried plugin is active. - * - * @since 4.2.0 - * @uses is_plugin_active() - * - * @return bool - */ - private static function core_is_plugin_active( $plugin ) { - if ( ! function_exists( 'is_plugin_active' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - return is_plugin_active( $plugin ); - } - - /** - * Get plugins data in site. - * - * @since 4.2.0 - * - * @return WP_REST_Response|WP_Error List of plugins in the site. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function get_plugins() { - $plugins = self::core_get_plugins(); - - if ( ! empty( $plugins ) ) { - return rest_ensure_response( $plugins ); - } - - return new WP_Error( 'not_found', esc_html__( 'Unable to list plugins.', 'jetpack' ), array( 'status' => 404 ) ); - } - - /** - * Get data about the queried plugin. Currently it only returns whether the plugin is active or not. - * - * @since 4.2.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - * - * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function get_plugin( $request ) { - - $plugins = self::core_get_plugins(); - - if ( empty( $plugins ) ) { - return new WP_Error( 'no_plugins_found', esc_html__( 'This site has no plugins.', 'jetpack' ), array( 'status' => 404 ) ); - } - - $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 ) ); - } - - $plugin_data = $plugins[ $plugin ]; - - $plugin_data['active'] = self::core_is_plugin_active( $plugin ); - - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Plugin found.', 'jetpack' ), - 'data' => $plugin_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 deleted file mode 100644 index 228c6b2c..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php +++ /dev/null @@ -1,111 +0,0 @@ -<?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. - */ -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 - */ - public function feedback( $data ) { - - $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 ) { - $args = func_get_args(); - $args = array_splice( $args, 1 ); - 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; - } -} diff --git a/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php b/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php deleted file mode 100644 index 4445cb65..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-iframe-embed.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php -/** - * Tweak the preview when rendered in an iframe - */ - -class Jetpack_Iframe_Embed { - static function init() { - if ( ! self::is_embedding_in_iframe() ) { - return; - } - - // Disable the admin bar - if ( ! defined( 'IFRAME_REQUEST' ) ) { - define( 'IFRAME_REQUEST', true ); - } - - // Prevent canonical redirects - remove_filter( 'template_redirect', 'redirect_canonical' ); - - add_action( 'wp_head', array( 'Jetpack_Iframe_Embed', 'noindex' ), 1 ); - add_action( 'wp_head', array( 'Jetpack_Iframe_Embed', 'base_target_blank' ), 1 ); - - add_filter( 'shortcode_atts_video', array( 'Jetpack_Iframe_Embed', 'disable_autoplay' ) ); - add_filter( 'shortcode_atts_audio', array( 'Jetpack_Iframe_Embed', 'disable_autoplay' ) ); - - if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { - wp_enqueue_script( 'jetpack-iframe-embed', WPMU_PLUGIN_URL . '/jetpack-iframe-embed/jetpack-iframe-embed.js', array( 'jquery' ) ); - } 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_localize_script( 'jetpack-iframe-embed', '_previewSite', array( 'siteURL' => get_site_url() ) ); - } - - static function is_embedding_in_iframe() { - return ( - self::has_iframe_get_param() && ( - self::has_preview_get_param() || - self::has_preview_theme_preview_param() - ) - ); - } - - 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` - * - * @param array $atts The output array of shortcode attributes. - * - * @return array The output array of shortcode attributes. - */ - static function disable_autoplay( $atts ) { - return array_merge( $atts, array( 'autoplay' => false ) ); - } - - /** - * We don't want search engines to index iframe previews - * Added via `wp_head` action in `init` - */ - static function noindex() { - echo '<meta name="robots" content="noindex,nofollow" />'; - } - - /** - * Make sure all links and forms open in a new window by default - * (unless overridden on client-side by JS) - * Added via `wp_head` action in `init` - */ - 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 deleted file mode 100644 index c8005ea1..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php +++ /dev/null @@ -1,204 +0,0 @@ -<?php - -class Jetpack_Keyring_Service_Helper { - /** - * @var Jetpack_Keyring_Service_Helper - **/ - private static $instance = null; - - static function init() { - if ( is_null( self::$instance ) ) { - self::$instance = new Jetpack_Keyring_Service_Helper; - } - - return self::$instance; - } - - public static $SERVICES = array( - 'facebook' => array( - 'for' => 'publicize' - ), - 'twitter' => array( - 'for' => 'publicize' - ), - 'linkedin' => array( - 'for' => 'publicize' - ), - 'tumblr' => array( - 'for' => 'publicize' - ), - 'path' => array( - 'for' => 'publicize' - ), - 'google_plus' => array( - 'for' => 'publicize' - ), - 'google_site_verification' => array( - 'for' => 'other' - ) - ); - - private function __construct() { - add_action( 'load-settings_page_sharing', array( __CLASS__, 'admin_page_load' ), 9 ); - } - - function get_services( $filter = 'all' ) { - $services = array( - - ); - - if ( 'all' == $filter ) { - return $services; - } else { - $connected_services = array(); - foreach ( $services as $service => $empty ) { - $connections = $this->get_connections( $service ); - if ( $connections ) { - $connected_services[ $service ] = $connections; - } - } - return $connected_services; - } - } - - /** - * Gets a URL to the public-api actions. Works like WP's admin_url - * - * @param string $service Shortname of a specific service. - * - * @return URL to specific public-api process - */ - // on WordPress.com this is/calls Keyring::admin_url - static function api_url( $service = false, $params = array() ) { - /** - * Filters the API URL used to interact with WordPress.com. - * - * @since 2.0.0 - * - * @param string https://public-api.wordpress.com/connect/?jetpack=publicize Default Publicize API URL. - */ - $url = apply_filters( 'publicize_api_url', 'https://public-api.wordpress.com/connect/?jetpack=publicize' ); - - if ( $service ) { - $url = add_query_arg( array( 'service' => $service ), $url ); - } - - if ( count( $params ) ) { - $url = add_query_arg( $params, $url ); - } - - return $url; - } - - static function connect_url( $service_name, $for ) { - return add_query_arg( array( - 'action' => 'request', - 'service' => $service_name, - 'kr_nonce' => wp_create_nonce( 'keyring-request' ), - 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), - 'for' => $for, - ), menu_page_url( 'sharing', false ) ); - } - - static function refresh_url( $service_name, $for ) { - return add_query_arg( array( - 'action' => 'request', - 'service' => $service_name, - 'kr_nonce' => wp_create_nonce( 'keyring-request' ), - 'refresh' => 1, - 'for' => $for, - 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), - ), admin_url( 'options-general.php?page=sharing' ) ); - } - - static function disconnect_url( $service_name, $id ) { - return add_query_arg( array( - 'action' => 'delete', - 'service' => $service_name, - 'id' => $id, - 'kr_nonce' => wp_create_nonce( 'keyring-request' ), - 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), - ), menu_page_url( 'sharing', false ) ); - } - - static function admin_page_load() { - if ( isset( $_GET['action'] ) ) { - if ( isset( $_GET['service'] ) ) { - $service_name = $_GET['service']; - } - - switch ( $_GET['action'] ) { - - case 'request': - check_admin_referer( 'keyring-request', 'kr_nonce' ); - check_admin_referer( "keyring-request-$service_name", 'nonce' ); - - $verification = Jetpack::generate_secrets( '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 ) ); - - } - $stats_options = get_option( 'stats_options' ); - $wpcom_blog_id = Jetpack_Options::get_option( 'id' ); - $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 ); - exit; - break; - - case 'completed': - Jetpack::load_xml_rpc_client(); - $xml = new Jetpack_IXR_Client(); - $xml->query( 'jetpack.fetchPublicizeConnections' ); - - if ( ! $xml->isError() ) { - $response = $xml->getResponse(); - Jetpack_Options::update_option( 'publicize_connections', $response ); - } - - break; - - case 'delete': - $id = $_GET['id']; - - check_admin_referer( 'keyring-request', 'kr_nonce' ); - check_admin_referer( "keyring-request-$service_name", 'nonce' ); - - Jetpack_Keyring_Service_Helper::disconnect( $service_name, $id ); - - do_action( 'connection_disconnected', $service_name ); - break; - } - } - } - - /** - * Remove a Publicize connection - */ - static function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) { - Jetpack::load_xml_rpc_client(); - $xml = new Jetpack_IXR_Client(); - $xml->query( 'jetpack.deletePublicizeConnection', $connection_id ); - - if ( ! $xml->isError() ) { - Jetpack_Options::update_option( 'publicize_connections', $xml->getResponse() ); - } else { - return false; - } - } - -} diff --git a/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php b/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php deleted file mode 100644 index 14de6053..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php +++ /dev/null @@ -1,1288 +0,0 @@ -<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -/** - * The password strength checker. - * - * @package jetpack - */ - -/** - * Checks passwords strength. - */ -class Jetpack_Password_Checker { - - /** - * Minimum entropy bits a password should contain. 36 bits of entropy is considered - * to be a reasonable password, 28 stands for a weak one. - * - * @const Integer - */ - const MINIMUM_BITS = 28; - - /** - * Currently tested password. - * - * @var String - */ - public $password = ''; - - /** - * Test results array. - * - * @var Array - */ - public $test_results = ''; - - /** - * Current password score. - * - * @var Integer - */ - public $score = 0; - - /** - * Current multiplier affecting the score. - * - * @var Integer - */ - public $multiplier = 4; - - /** - * A common password blacklist, which on match will immediately disqualify the password. - * - * @var Array - */ - public $common_passwords = array(); - - /** - * Minimum password length setting. - * - * @var Integer - */ - public $min_password_length = 6; - - /** - * User defined strings that passwords need to be tested for a match against. - * - * @var Array - */ - private $user_strings_to_test = array(); - - /** - * The user object for whom the password is being tested. - * - * @var WP_User - */ - protected $user; - - /** - * The user identifier for whom the password is being tested, used if there's no user object. - * - * @var WP_User - */ - protected $user_id; - - /** - * Creates an instance of the password checker class for the specified user, or - * defaults to the currently logged in user. - * - * @param Mixed $user can be an integer ID, or a WP_User object. - */ - public function __construct( $user = null ) { - - /** - * Filters Jetpack's password strength enforcement settings. You can supply your own passwords - * that should not be used for authenticating in addition to weak and easy to guess strings for - * each user. For example, you can add passwords from known password databases to avoid compromised - * password usage. - * - * @since 7.2.0 - * - * @param Array $restricted_passwords strings that are forbidden for use as passwords. - */ - $this->common_passwords = apply_filters( 'jetpack_password_checker_restricted_strings', array() ); - - if ( is_null( $user ) ) { - $this->user_id = get_current_user_id(); - } elseif ( is_object( $user ) && isset( $user->ID ) ) { - - // Existing user, using their ID. - $this->user_id = $user->ID; - - } elseif ( is_object( $user ) ) { - - // Newly created user, using existing data. - $this->user = $user; - $this->user_id = 'new_user'; - - } else { - $this->user_id = $user; - } - $this->min_password_length = apply_filters( 'better_password_min_length', $this->min_password_length ); - } - - /** - * Run tests against a password. - * - * @param String $password the tested string. - * @param Boolean $required_only only test against required conditions, defaults to false. - * @return Array $results an array containing failed and passed test results. - */ - public function test( $password, $required_only = false ) { - - $this->password = $password; - $results = $this->run_tests( $this->list_tests(), $required_only ); - - // If we've failed on the required tests, return now. - if ( ! empty( $results['failed'] ) ) { - return array( - 'passed' => false, - 'test_results' => $results, - ); - } - - /** - * Filters Jetpack's password strength enforcement settings. You can modify the minimum - * entropy bits requirement using this filter. - * - * @since 7.2.0 - * - * @param Array $minimum_entropy_bits minimum entropy bits requirement. - */ - $bits = apply_filters( 'jetpack_password_checker_minimum_entropy_bits', self::MINIMUM_BITS ); - $entropy_bits = $this->calculate_entropy_bits( $this->password ); - - // If we have failed the entropy bits test, run the regex tests so we can suggest improvements. - if ( $entropy_bits < $bits ) { - $results['failed']['entropy_bits'] = $entropy_bits; - $results = array_merge( - $results, - $this->run_tests( $this->list_tests( 'preg_match' ), false ) - ); - } - - return( array( - 'passed' => empty( $results['failed'] ), - 'test_results' => $results, - ) ); - } - - /** - * Run the tests using the currently set up object values. - * - * @param Array $tests tests to run. - * @param Boolean $required_only whether to run only required tests. - * @return Array test results. - */ - protected function run_tests( $tests, $required_only = false ) { - - $results = array( - 'passed' => array(), - 'failed' => array(), - ); - - foreach ( $tests as $test_type => $section_tests ) { - foreach ( $section_tests as $test_name => $test_data ) { - - // Skip non-required tests if required_only param is set. - if ( $required_only && ! $test_data['required'] ) { - continue; - } - - $test_function = 'test_' . $test_type; - - $result = call_user_func( array( $this, $test_function ), $test_data ); - - if ( $result ) { - $results['passed'][] = array( 'test_name' => $test_name ); - } else { - $results['failed'][] = array( - 'test_name' => $test_name, - 'explanation' => $test_data['error'], - ); - - if ( isset( $test_data['fail_immediately'] ) ) { - return $results; - } - } - } - } - - return $results; - } - - /** - * Returns a list of tests that need to be run on password strings. - * - * @param Array $sections only return specific sections with the passed keys, defaults to all. - * @return Array test descriptions. - */ - protected function list_tests( $sections = false ) { - // Note: these should be in order of priority. - $tests = array( - 'preg_match' => array( - 'no_backslashes' => array( - 'pattern' => '^[^\\\\]*$', - 'error' => __( 'Passwords may not contain the character "\".', 'jetpack' ), - 'required' => true, - 'fail_immediately' => true, - ), - 'minimum_length' => array( - 'pattern' => '^.{' . $this->min_password_length . ',}', - /* translators: %d is a number of characters in the password. */ - 'error' => sprintf( __( 'Password must be at least %d characters.', 'jetpack' ), $this->min_password_length ), - 'required' => true, - 'fail_immediately' => true, - ), - 'has_mixed_case' => array( - 'pattern' => '([a-z].*?[A-Z]|[A-Z].*?[a-z])', - 'error' => __( 'This password is too easy to guess: you can improve it by adding additional uppercase letters, lowercase letters, or numbers.', 'jetpack' ), - 'trim' => true, - 'required' => false, - ), - 'has_digit' => array( - 'pattern' => '\d', - 'error' => __( 'This password is too easy to guess: you can improve it by mixing both letters and numbers.', 'jetpack' ), - 'trim' => false, - 'required' => false, - ), - 'has_special_char' => array( - 'pattern' => '[^a-zA-Z\d]', - 'error' => __( 'This password is too easy to guess: you can improve it by including special characters such as !#=?*&.', 'jetpack' ), - 'required' => false, - ), - ), - 'compare_to_list' => array( - 'not_a_common_password' => array( - 'list_callback' => 'get_common_passwords', - 'compare_callback' => 'negative_in_array', - 'error' => __( 'This is a very common password. Choose something that will be harder for others to guess.', 'jetpack' ), - 'required' => true, - ), - 'not_same_as_other_user_data' => array( - 'list_callback' => 'get_other_user_data', - 'compare_callback' => 'test_not_same_as_other_user_data', - 'error' => __( 'Your password is too weak: Looks like you\'re including easy to guess information about yourself. Try something a little more unique.', 'jetpack' ), - 'required' => true, - ), - ), - ); - - /** - * Filters Jetpack's password strength enforcement settings. You can determine the tests run - * and their order based on whatever criteria you wish to specify. - * - * @since 7.2.0 - * - * @param Array $minimum_entropy_bits minimum entropy bits requirement. - */ - $tests = apply_filters( 'jetpack_password_checker_tests', $tests ); - - if ( ! $sections ) { - return $tests; - } - - $sections = (array) $sections; - return array_intersect_key( $tests, array_flip( $sections ) ); - } - - /** - * Provides the regular expression tester functionality. - * - * @param Array $test_data the current test data. - * @return Boolean does the test pass? - */ - protected function test_preg_match( $test_data ) { - $password = stripslashes( $this->password ); - - if ( isset( $test_data['trim'] ) ) { - $password = substr( $password, 1, -1 ); - } - - if ( ! preg_match( '/' . $test_data['pattern'] . '/u', $password ) ) { - return false; - } - - return true; - } - - /** - * Provides the comparison tester functionality. - * - * @param Array $test_data the current test data. - * @return Boolean does the test pass? - */ - protected function test_compare_to_list( $test_data ) { - $list_callback = $test_data['list_callback']; - $compare_callback = $test_data['compare_callback']; - - if ( - ! is_callable( array( $this, $list_callback ) ) - || ! is_callable( array( $this, $compare_callback ) ) - ) { - return false; - } - - $list = call_user_func( array( $this, $list_callback ) ); - if ( empty( $list ) ) { - return true; - } - - return call_user_func( array( $this, $compare_callback ), $this->password, $list ); - } - - /** - * Getter for the common password list. - * - * @return Array common passwords. - */ - protected function get_common_passwords() { - return $this->common_passwords; - } - - /** - * Returns the widely known user data that can not be used in the password to avoid - * predictable strings. - * - * @return Array user data. - */ - protected function get_other_user_data() { - - if ( ! isset( $this->user ) ) { - $user_data = get_userdata( $this->user_id ); - - $first_name = get_user_meta( $user_data->ID, 'first_name', true ); - $last_name = get_user_meta( $user_data->ID, 'last_name', true ); - $nickname = get_user_meta( $user_data->ID, 'nickname', true ); - - $this->add_user_strings_to_test( $nickname ); - $this->add_user_strings_to_test( $user_data->user_nicename ); - $this->add_user_strings_to_test( $user_data->display_name ); - } else { - $user_data = $this->user; - - $first_name = $user_data->first_name; - $last_name = $user_data->last_name; - } - $email_username = substr( $user_data->user_email, 0, strpos( $user_data->user_email, '@' ) ); - - $this->add_user_strings_to_test( $user_data->user_email ); - $this->add_user_strings_to_test( $email_username, '.' ); - $this->add_user_strings_to_test( $first_name ); - $this->add_user_strings_to_test( $last_name ); - - return $this->user_strings_to_test; - } - - /** - * Compare the password for matches with known user data. - * - * @param String $password the string to be tested. - * @param Array $strings_to_test known user data. - * @return Boolean does the test pass? - */ - protected function test_not_same_as_other_user_data( $password, $strings_to_test ) { - $password_lowercase = strtolower( $password ); - foreach ( array_unique( $strings_to_test ) as $string ) { - if ( empty( $string ) ) { - continue; - } - - $string = strtolower( $string ); - $string_reversed = strrev( $string ); - - if ( $password_lowercase === $string || $password_lowercase === $string_reversed ) { - return false; - } - - // Also check for the string or reversed string with any numbers just stuck to the end to catch things like bob123 as passwords. - if ( - preg_match( '/^' . preg_quote( $string, '/' ) . '\d+$/', $password_lowercase ) - || preg_match( '/^' . preg_quote( $string_reversed, '/' ) . '\d+$/', $password_lowercase ) - ) { - return false; - } - } - return true; - } - - /** - * A shorthand for the not in array construct. - * - * @param Mixed $needle the needle. - * @param Array $haystack the haystack. - * @return is the needle not in the haystack? - */ - protected function negative_in_array( $needle, $haystack ) { - if ( in_array( $needle, $haystack, true ) ) { - return false; - } - - return true; - } - - /** - * A helper function used to break a single string into its constituents so - * that both the full string and its constituents and any variants thereof - * can be tested against the password. - * - * @param String $string the string to be broken down. - * @param String $explode_delimiter delimiter. - * @return NULL|Array array of fragments, or NULL on empty string. - */ - protected function add_user_strings_to_test( $string, $explode_delimiter = ' ' ) { - - // Don't check against empty strings. - if ( empty( $string ) ) { - return; - } - - $strings = explode( $explode_delimiter, $string ); - - // Remove any non alpha numeric characters from the strings to check against. - foreach ( $strings as $key => $_string ) { - $strings[ $key ] = preg_replace( '/[^a-zA-Z0-9]/', '', $_string ); - } - - // Check the original too. - $strings[] = $string; - - // Check the original minus non alpha numeric characters. - $strings[] = preg_replace( '/[^a-zA-Z0-9]/', '', $string ); - - // Remove any empty strings. - $strings = array_filter( $strings ); - $this->user_strings_to_test = array_merge( $this->user_strings_to_test, $strings ); - } - - /** - * Return a character set size that is used in the string. - * - * @param String $password the password. - * @return Integer number of different character sets in use. - */ - protected function get_charset_size( $password ) { - $size = 0; - - // Lowercase a-z. - if ( preg_match( '/[a-z]/', $password ) ) { - $size += 26; - } - - // Uppercase A-Z. - if ( preg_match( '/[A-Z]/', substr( $password, 1, -1 ) ) ) { - $size += 26; - } - - // Digits. - if ( preg_match( '/\d/', substr( $password, 1, -1 ) ) ) { - $size += 10; - } - - // Over digits symbols. - if ( preg_match( '/[!|@|#|$|%|^|&|*|(|)]/', $password ) ) { - $size += 10; - } - - // Other symbols. - if ( preg_match( '#[`|~|-|_|=|+|\[|{|\]|}|\\|\|;:\'",<\.>/\?]#', $password ) ) { - $size += 20; - } - - // Spaces. - if ( strpos( $password, ' ' ) ) { - $size++; - } - - return $size; - } - - /** - * Shorthand for getting a character index. - * - * @param String $char character. - * @return Integer the character code. - */ - protected function get_char_index( $char ) { - $char = strtolower( $char[0] ); - if ( $char < 'a' || $char > 'z' ) { - return 0; - } else { - return ord( $char[0] ) - ord( 'a' ) + 1; - } - } - - /** - * This is the password strength calculation algorithm, based on the formula H = L(logN/log2). - * - * H = Entropy - * L = String length (the for iterator) - * N = Our charset size, via get_charset_size() - * - * @see http://en.wikipedia.org/wiki/Password_strength#Random_passwords - * - * On top of the base formula, we're also multiplying the bits of entropy for every char - * by 1 - (the probabily of it following the previous char) - * i.e.: the probablity of U following Q is ~0.84. If our password contains this pair of characters, - * the u char will only add ( 0.16^2 * charset_score ) to our total of entropy bits. - * - * @param String $password the password. - */ - protected function calculate_entropy_bits( $password ) { - $bits = 0; - $charset_score = log( $this->get_charset_size( $password ) ) / log( 2 ); - - $aidx = $this->get_char_index( $password[0] ); - $length = strlen( $password ); - - for ( $b = 1; $b < $length; $b++ ) { - $bidx = $this->get_char_index( $password[ $b ] ); - - // 27 = number of chars in the index (a-z,' '). - $c = 1.0 - $this->frequency_table[ $aidx * 27 + $bidx ]; - $bits += $charset_score * $c * $c; - - // Move on to next pair. - $aidx = $bidx; - } - - return $bits; - } - - /** - * A frequency table of character pairs, starting with ' ' then ' a', ' b' [...] , 'a ', 'aa' etc. - * - * @see http://rumkin.com/tools/password/passchk.php - * @var Array - */ - public $frequency_table = array( - 0.23653710453418866, - 0.04577693541332556, - 0.03449832337075375, - 0.042918209651552706, - 0.037390873305146524, - 0.028509112115468728, - 0.02350896632162123, - 0.022188657238664526, - 0.028429800262428927, - 0.04357019973757107, - 0.00913602565971716, - 0.03223093745443942, - 0.02235311269864412, - 0.04438081352966905, - 0.04512377897652719, - 0.020055401662049863, - 0.055903192885260244, - 0.0024388394809739026, - 0.035207464644991984, - 0.07355941099285611, - 0.036905671380667734, - 0.026134421927394666, - 0.023787724158040528, - 0.011352092141711621, - 0.0032354570637119114, - 0.005986878553725033, - 0.008861933226417843, - 0.11511532293337222, - 0.027556203528211108, - 0.024331243621519172, - 0.039266365359381834, - 0.031599941682461, - 0.014403265782183991, - 0.015480973902901297, - 0.027770812071730572, - 0.00942761335471643, - 0.039872867764980315, - 0.0078122175244204695, - 0.02808456043154979, - 0.08429100451960927, - 0.04688963405744277, - 0.13831170724595424, - 0.002540311998833649, - 0.025211838460416972, - 0.001543082081936142, - 0.09519638431258201, - 0.061845750109345385, - 0.08907071001603732, - 0.02137571074500656, - 0.027093162268552268, - 0.005521504592506197, - 0.003023181221752442, - 0.007086747339262283, - 0.010262720513194342, - 0.08785070710016038, - 0.14617757690625455, - 0.03417291150313457, - 0.0059635515381250915, - 0.006146668610584633, - 0.195202799241872, - 0.002774748505613063, - 0.004715556203528212, - 0.0044776206444088066, - 0.11205481848665985, - 0.005654468581425864, - 0.0028820527773727946, - 0.07383000437381543, - 0.005516839189386207, - 0.006496573844583759, - 0.09843067502551392, - 0.0027140982650532145, - 0.0006893133109782768, - 0.08425368129464937, - 0.021325557661466685, - 0.006493074792243767, - 0.07023414491908442, - 0.002077270739174807, - 0.0024633328473538415, - 0.0007744569179180639, - 0.015413325557661468, - 0.0011990086018370024, - 0.13162851727657093, - 0.10115993585070711, - 0.0026989357049132527, - 0.03319317684793702, - 0.002946202070272634, - 0.0783216212275842, - 0.0018358361277154103, - 0.00258813238081353, - 0.2141688292754046, - 0.09853681294649366, - 0.0032482869222918796, - 0.04359352675317102, - 0.01993526753171016, - 0.0036880011663507797, - 0.008011663507799971, - 0.12014696019827964, - 0.0029846916460125384, - 0.0017553579238956116, - 0.029470185158186325, - 0.010413179763813967, - 0.030699518880303252, - 0.03508499781309229, - 0.002021285901734947, - 0.0010613792097973467, - 0.0005295232541186761, - 0.009677212421635807, - 0.010585799679253535, - 0.17101734946785244, - 0.07968625164018078, - 0.007839043592360402, - 0.005438693687126403, - 0.0183606939787141, - 0.2732701559994168, - 0.004953491762647616, - 0.007259367254701851, - 0.008104971570199739, - 0.13274588132380813, - 0.004210526315789474, - 0.004997813092287506, - 0.017006560723137484, - 0.007442484327161393, - 0.016789619478058026, - 0.08477737279486806, - 0.005106283714827234, - 0.0005026971861787433, - 0.04040355736987899, - 0.037535500801866156, - 0.00885960052485785, - 0.0336410555474559, - 0.007066919376002332, - 0.005344219273946639, - 0.0006333284735384167, - 0.010684939495553289, - 0.0063064586674442345, - 0.15386849394955532, - 0.015049424114302375, - 0.012162705933809595, - 0.020425134859308938, - 0.037366379938766583, - 0.02157165767604607, - 0.009373961218836564, - 0.0173214754337367, - 0.009616562181075958, - 0.029522670943286193, - 0.010154249890654615, - 0.018600962239393497, - 0.06362210234728094, - 0.03157078291296107, - 0.151603440734801, - 0.0062329785683044175, - 0.014775331681003062, - 0.0020854351946347867, - 0.1826342032366234, - 0.0878017203674005, - 0.054190989940224525, - 0.010329202507654177, - 0.012763376585508092, - 0.0064872430383437815, - 0.006381105117364048, - 0.005388540603586529, - 0.0090800408222773, - 0.09611196967487973, - 0.09940691062837148, - 0.01033969966467415, - 0.004034407348009914, - 0.008826942703017933, - 0.11474675608689314, - 0.07132584924916169, - 0.012388977985129028, - 0.005435194634786413, - 0.1417174515235457, - 0.0037066627788307337, - 0.0045802595130485495, - 0.060800699810468, - 0.005341886572386646, - 0.005683627350925791, - 0.12434932205860913, - 0.004596588423968508, - 0.0007534626038781163, - 0.07107041842834232, - 0.022361277154104096, - 0.04784720804782038, - 0.06277533168100306, - 0.003441901151771395, - 0.005828254847645429, - 0.0009669047966175828, - 0.009470768333576322, - 0.002077270739174807, - 0.12797667298440007, - 0.08797783933518005, - 0.005388540603586529, - 0.0024913252660737715, - 0.007550954949701123, - 0.2786866890217233, - 0.002509986878553725, - 0.029002478495407494, - 0.0303204548768042, - 0.07576614666861058, - 0.00246799825047383, - 0.00592389561160519, - 0.039574281965301064, - 0.00706808572678233, - 0.03304505029887739, - 0.05474150750838315, - 0.0028633911648928414, - 0.0005073625892987316, - 0.07293541332555767, - 0.053528502697186175, - 0.022566554891383584, - 0.038151334013704616, - 0.002716430966613209, - 0.005049132526607377, - 0.0009902318122175246, - 0.008997229916897508, - 0.0011861787432570347, - 0.1666377022889634, - 0.14414462749671964, - 0.003374252806531564, - 0.005169266656947077, - 0.008468873013558828, - 0.16337541915731155, - 0.002873888321912815, - 0.004305000728969237, - 0.0031141565825922144, - 0.1241172182533897, - 0.0052800699810468, - 0.008969237498177577, - 0.024094474413179766, - 0.017029887738737422, - 0.01722700102055693, - 0.10618457501093455, - 0.006147834961364631, - 0.0008269427030179326, - 0.03303571949263741, - 0.024188948826359528, - 0.05213937891820965, - 0.04505846333284735, - 0.0035270447587111824, - 0.006799825047383001, - 0.0008199445983379502, - 0.02206735675754483, - 0.001010059775477475, - 0.11971191135734072, - 0.04656538854060359, - 0.011243621519171892, - 0.06513019390581717, - 0.032375564951159064, - 0.06347047674588133, - 0.013678961947805804, - 0.03309870243475726, - 0.006982942119842543, - 0.009726199154395685, - 0.010121592068814697, - 0.032514360693978714, - 0.04986032949409535, - 0.039734072022160664, - 0.15690683773144773, - 0.03949963551538125, - 0.014790494241143023, - 0.002722262720513194, - 0.02614375273363464, - 0.10753637556495116, - 0.06764834523983088, - 0.006221315060504448, - 0.021317393206006705, - 0.0030826651115322934, - 0.002399183554454002, - 0.0019069835252952323, - 0.015595276279341012, - 0.0925126111678087, - 0.18437906400349907, - 0.006538562472663654, - 0.008719638431258201, - 0.02116693395538708, - 0.18241376293920394, - 0.007290858725761773, - 0.005976381396705059, - 0.005629975215045925, - 0.09721300481119698, - 0.004810030616707975, - 0.024303251202799244, - 0.012954658113427612, - 0.011057005394372358, - 0.02733459688001166, - 0.10135121737862662, - 0.012016912086309959, - 0.001055547455897361, - 0.009027555037177431, - 0.07162326869806095, - 0.01007143898527482, - 0.07297623560285756, - 0.006741507508383147, - 0.0036891675171307776, - 0.0008409389123778977, - 0.011272780288671819, - 0.007020265344802449, - 0.1030389269572824, - 0.15350809155853623, - 0.004232686980609419, - 0.004353987461729115, - 0.0023385333138941536, - 0.14450386353695874, - 0.002546143752733635, - 0.0024470039364338824, - 0.01200758128006998, - 0.0981227584195947, - 0.003161976964572095, - 0.040695145064878264, - 0.03460446129173349, - 0.003908441463770229, - 0.01598483743986004, - 0.13107216795451232, - 0.003129319142732177, - 0.00032307916605919226, - 0.04050386353695874, - 0.05452689896486368, - 0.03589677795597026, - 0.07087097244496282, - 0.006143169558244642, - 0.008684647907858289, - 0.0004607085580988482, - 0.022010205569324977, - 0.0009097536083977258, - 0.07328765126111678, - 0.14751421490013122, - 0.008015162560139961, - 0.006601545414783497, - 0.025279486805656802, - 0.1682449336637994, - 0.008313748359819215, - 0.007010934538562473, - 0.005886572386645284, - 0.16889575739903775, - 0.004123050007289692, - 0.011925936725470185, - 0.10007289692374982, - 0.013380376148126549, - 0.009021723283277445, - 0.08650823735238372, - 0.007756232686980609, - 0.0007243038343781893, - 0.0026791077416533026, - 0.02797492345823006, - 0.032384895757399036, - 0.04187432570345531, - 0.00882461000145794, - 0.0032401224668318998, - 0.00033357632307916605, - 0.027878116343490307, - 0.0022277299897944304, - 0.14333518005540166, - 0.1725534334451086, - 0.02781629975215046, - 0.006909462020702727, - 0.005264907420906838, - 0.16661437527336345, - 0.004325995043009185, - 0.003334596880011664, - 0.005312727802886718, - 0.14024668318996938, - 0.0013261408368566844, - 0.003504884093891238, - 0.006375273363464061, - 0.04964922000291588, - 0.008290421344219274, - 0.09536783787724158, - 0.05394372357486515, - 0.005505175681586237, - 0.005339553870826651, - 0.01782067356757545, - 0.006710016037323225, - 0.05105933809593235, - 0.002983525295232541, - 0.002940370316372649, - 0.0004548768041988629, - 0.01208456043154979, - 0.000915585362297711, - 0.20146260387811635, - 0.067196967487972, - 0.006158332118384605, - 0.025438110511736407, - 0.07753783350342616, - 0.1273876658405015, - 0.009337804344656656, - 0.07683452398308792, - 0.0070412596588423975, - 0.08747164309666132, - 0.0038827817466102928, - 0.018116926665694706, - 0.005017641055547455, - 0.004567429654468581, - 0.028277008310249308, - 0.05271555620352821, - 0.004394809739029013, - 0.0013343052923166642, - 0.00411605190260971, - 0.059621519171890944, - 0.09073859163143316, - 0.01446858142586383, - 0.006770666277883074, - 0.003425572240851436, - 0.0004455459979588861, - 0.010401516256013998, - 0.005825922146085436, - 0.10833882490158916, - 0.007584779122321038, - 0.016903921854497742, - 0.02719580113719201, - 0.0304814112844438, - 0.02206385770520484, - 0.013064295086747339, - 0.02696369733197259, - 0.009581571657676046, - 0.026761918647033093, - 0.006510570053943724, - 0.021941390873305145, - 0.07042659279778393, - 0.05437410701268406, - 0.1425175681586237, - 0.027802303542790494, - 0.037690625455605774, - 0.0019606356611750987, - 0.1095623268698061, - 0.06157748942994606, - 0.044618749088788455, - 0.04955124653739612, - 0.03608689313310978, - 0.018381688292754043, - 0.003404577926811489, - 0.015036594255722409, - 0.009600233270156, - 0.10794693103951014, - 0.12447528794284882, - 0.0031981338387520046, - 0.0074716430966613205, - 0.003202799241871993, - 0.13437643971424407, - 0.006655197550663361, - 0.0036693395538708266, - 0.049338970695436656, - 0.09486863974340283, - 0.0015990669193760023, - 0.0026604461291733486, - 0.051775477474850555, - 0.0041347135150896636, - 0.005450357194926374, - 0.12030325120279925, - 0.04581309228750547, - 0.0004537104534188657, - 0.12425601399620935, - 0.025981629975215047, - 0.023926519900860182, - 0.04423385333138941, - 0.0017950138504155123, - 0.002661612479953346, - 0.0006333284735384167, - 0.008449045050298877, - 0.000653156436798367, - 0.04816678816153958, - 0.008625164018078437, - 0.0039037760606502403, - 0.005228750546726928, - 0.004531272780288672, - 0.0056672984400058316, - 0.00359585945473101, - 0.0032179618020119548, - 0.0038093016474704767, - 0.011452398308791368, - 0.002519317684793702, - 0.00280390727511299, - 0.005572824026826068, - 0.004554599795888614, - 0.004531272780288672, - 0.0035841959469310393, - 0.004400641492928998, - 0.0036670068523108326, - 0.004839189386207902, - 0.006258638285464354, - 0.004897506925207757, - 0.840776789619478, - 0.004968654322787578, - 0.002886718180492783, - 0.0019757982213150604, - 0.0018568304417553576, - 0.001691208630995772, - 0.09009243329931477, - 0.14030150167662925, - 0.013242746756086894, - 0.013746610293045632, - 0.027342761335471644, - 0.16938912377897652, - 0.006607377168683481, - 0.01661933226417845, - 0.008173786266219566, - 0.13297448607668758, - 0.0034675608689313307, - 0.016641492928998396, - 0.011722991689750693, - 0.021493512173786266, - 0.03430820819361423, - 0.10099548039072752, - 0.00873596734217816, - 0.0018323370753754193, - 0.020103222044029742, - 0.047197550663362, - 0.040833940807697915, - 0.03361189677795597, - 0.010844729552412887, - 0.005544831608106138, - 0.0007522962530981193, - 0.01525120279924187, - 0.00815512465373961, - 0.2109648636827526, - 0.058258055110074355, - 0.007181221752442048, - 0.043560868931331105, - 0.004058900714389853, - 0.10618107595859454, - 0.0062399766729844, - 0.004835690333867911, - 0.02679224376731302, - 0.08414637702288964, - 0.0030698352529523252, - 0.03637498177576906, - 0.01592885260242018, - 0.017413617145356466, - 0.008430383437818923, - 0.037231083248286924, - 0.03290275550371775, - 0.007538125091121154, - 0.004500947660008748, - 0.05932409972299169, - 0.16006764834523984, - 0.03309636973319726, - 0.007766729844000583, - 0.005225251494386936, - 0.0006321621227584196, - 0.012989648636827526, - 0.005274238227146815, - 0.1254503571949264, - 0.12852719055255868, - 0.0035433736696311416, - 0.005203090829566993, - 0.0019314768916751715, - 0.20520775623268697, - 0.002509986878553725, - 0.00343606939787141, - 0.027138649948972155, - 0.13926578218399185, - 0.004565096952908587, - 0.005614812654905963, - 0.00874413179763814, - 0.004109053797929727, - 0.008300918501239247, - 0.08270943286193323, - 0.002912377897652719, - 0.0037066627788307337, - 0.06909578655780726, - 0.03242805073625893, - 0.05237614812654906, - 0.04723487388832191, - 0.0038991106575302524, - 0.006299460562764251, - 0.00043388249015891526, - 0.020029741944889927, - 0.005311561452106721, - 0.09334072022160665, - 0.022940953491762648, - 0.024658988190698353, - 0.02901297565242747, - 0.03531593526753171, - 0.0758023035427905, - 0.013711619769645722, - 0.021597317393206007, - 0.009670214316955824, - 0.044728386062108175, - 0.010596296836273509, - 0.03264382563055839, - 0.0604822860475288, - 0.05489546581134276, - 0.11501851581863246, - 0.01837585653885406, - 0.026237060796034405, - 0.0011255285026971862, - 0.08704125965884241, - 0.10156349322058608, - 0.06660562764251349, - 0.023434319871701415, - 0.010777081207173057, - 0.005409534917626476, - 0.003123487388832191, - 0.0028762210234728096, - 0.0089995626184575, - 0.07518297127861205, - 0.2314868056568013, - 0.002226563639014434, - 0.003285610147251786, - 0.0027455897361131363, - 0.2724537104534189, - 0.0016655489138358362, - 0.0019209797346551977, - 0.0022137337804344656, - 0.17690392185449774, - 0.0014532730718763668, - 0.0024994897215337513, - 0.015302522233561744, - 0.003441901151771395, - 0.015303688584341741, - 0.09314593964134713, - 0.0017833503426155418, - 0.0005108616416387229, - 0.017828838023035427, - 0.010385187345094037, - 0.003168975069252078, - 0.01902901297565243, - 0.005525003644846187, - 0.0010088934246974776, - 0.0009272488700976819, - 0.036282840064149294, - 0.0022977110365942554, - 0.0766805656801283, - 0.22270418428342326, - 0.005283569033386791, - 0.007155562035282111, - 0.01173582154833066, - 0.1715620352821111, - 0.003925936725470185, - 0.004425134859308937, - 0.020040239101909902, - 0.14243242455168392, - 0.0016737133692958156, - 0.0066808572678232975, - 0.011980755212130047, - 0.012638577052048404, - 0.07206065024055984, - 0.08115701997375711, - 0.00710424260096224, - 0.0007278028867181805, - 0.02347630849978131, - 0.04595538708266512, - 0.01481965301064295, - 0.013925061962385188, - 0.0018125091121154687, - 0.00529173348884677, - 0.0016340574427759146, - 0.03072401224668319, - 0.0023746901880740633, - 0.25174165330223064, - 0.06673392622831317, - 0.00878378772415804, - 0.03956261845750109, - 0.010077270739174807, - 0.0844787869951888, - 0.00985216503863537, - 0.004973319725907567, - 0.01893220586091267, - 0.11200583175389998, - 0.0028715556203528212, - 0.004095057588569762, - 0.01202391019098994, - 0.01756757544831608, - 0.014825484764542934, - 0.05312961073042717, - 0.06746872721971132, - 0.003845458521650386, - 0.0210806239976673, - 0.019443067502551394, - 0.08017028721387957, - 0.01825572240851436, - 0.005365213587986587, - 0.01959702580551101, - 0.026184575010934536, - 0.02474879720075813, - 0.002171745152354571, - 0.25827321767021433, - 0.048050153083539875, - 0.01043184137629392, - 0.03930485493512174, - 0.027640180784370902, - 0.03294007872867765, - 0.006474413179763814, - 0.018314039947514214, - 0.015119405161102202, - 0.014706516984983233, - 0.005494678524566263, - 0.03309870243475726, - 0.043864120134130345, - 0.058996355153812505, - 0.06265986295378335, - 0.04633328473538417, - 0.03790756670068523, - 0.0004642076104388394, - 0.037849249161685375, - 0.08369966467415076, - 0.04999679253535501, - 0.02392768625164018, - 0.010998687855372504, - 0.009881323808135296, - 0.003867619186470331, - 0.012434465665548913, - 0.007253535500801866, - 0.11106225397288234, - 0.17624726636535937, - 0.008209943140399476, - 0.008390727511299025, - 0.012682898381688294, - 0.1825653885406036, - 0.001538416678816154, - 0.004590756670068524, - 0.008710307625018223, - 0.1299513048549351, - 0.002677941390873305, - 0.012309666132089225, - 0.014087184720804781, - 0.01199941682461, - 0.031246537396121883, - 0.07206648199445984, - 0.008254264470039366, - 0.0007033095203382417, - 0.007034261554162415, - 0.006599212713223502, - 0.013906400349905234, - 0.050098265053214755, - 0.007133401370462167, - 0.017750692520775622, - 0.0008257763522379356, - 0.03918821985712203, - 0.06015454147834961, - ); -} diff --git a/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php b/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php deleted file mode 100644 index 8dc22d19..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-photon-image-sizes.php +++ /dev/null @@ -1,182 +0,0 @@ -<?php -/** - * The Image Sizes library. - * - * @package jetpack - */ - -jetpack_require_lib( 'class.jetpack-photon-image' ); - -/** - * Class Jetpack_Photon_ImageSizes - * - * Manages image resizing via Jetpack CDN Service. - */ -class Jetpack_Photon_ImageSizes { - - /** - * @var array $data Attachment metadata. - */ - public $data; - - /** - * @var Image Image to be resized. - */ - public $image; - - /** - * @var null|array $sizes Intermediate sizes. - */ - public static $sizes = null; - - /** - * Construct new sizes meta - * - * @param int $attachment_id Attachment ID. - * @param array $data Attachment metadata. - */ - public function __construct( $attachment_id, $data ) { - $this->data = $data; - $this->image = new Jetpack_Photon_Image( $data, get_post_mime_type( $attachment_id ) ); - $this->generate_sizes(); - } - - /** - * Generate sizes for attachment. - * - * @return array Array of sizes; empty array as failure fallback. - */ - protected function generate_sizes() { - - // There is no need to generate the sizes a new for every single image. - if ( null !== self::$sizes ) { - return self::$sizes; - } - - /* - * The following logic is copied over from wp_generate_attachment_metadata - */ - $_wp_additional_image_sizes = wp_get_additional_image_sizes(); - - $sizes = array(); - - $intermediate_image_sizes = get_intermediate_image_sizes(); - - foreach ( $intermediate_image_sizes as $s ) { - $sizes[ $s ] = array( - 'width' => '', - 'height' => '', - 'crop' => false, - ); - if ( isset( $_wp_additional_image_sizes[ $s ]['width'] ) ) { - // For theme-added sizes. - $sizes[ $s ]['width'] = intval( $_wp_additional_image_sizes[ $s ]['width'] ); - } else { - // For default sizes set in options. - $sizes[ $s ]['width'] = get_option( "{$s}_size_w" ); - } - - if ( isset( $_wp_additional_image_sizes[ $s ]['height'] ) ) { - // For theme-added sizes. - $sizes[ $s ]['height'] = intval( $_wp_additional_image_sizes[ $s ]['height'] ); - } else { - // For default sizes set in options. - $sizes[ $s ]['height'] = get_option( "{$s}_size_h" ); - } - - if ( isset( $_wp_additional_image_sizes[ $s ]['crop'] ) ) { - // For theme-added sizes. - $sizes[ $s ]['crop'] = $_wp_additional_image_sizes[ $s ]['crop']; - } else { - // For default sizes set in options. - $sizes[ $s ]['crop'] = get_option( "{$s}_crop" ); - } - } - - self::$sizes = $sizes; - - return $sizes; - } - - /** - * @return array - */ - public function filtered_sizes() { - // Remove filter preventing the creation of advanced sizes. - remove_filter( - 'intermediate_image_sizes_advanced', - array( 'Jetpack_Photon', 'filter_photon_noresize_intermediate_sizes' ) - ); - - /** This filter is documented in wp-admin/includes/image.php */ - $sizes = apply_filters( 'intermediate_image_sizes_advanced', self::$sizes, $this->data ); - - // Re-add the filter removed above. - add_filter( - 'intermediate_image_sizes_advanced', - array( 'Jetpack_Photon', 'filter_photon_noresize_intermediate_sizes' ) - ); - - return (array) $sizes; - } - - /** - * Standardises and validates the size_data array. - * - * @param array $size_data Size data array - at least containing height or width key. Can contain crop as well. - * - * @return array Array with populated width, height and crop keys; empty array if no width and height are provided. - */ - public function standardize_size_data( $size_data ) { - $has_at_least_width_or_height = ( isset( $size_data['width'] ) || isset( $size_data['height'] ) ); - if ( ! $has_at_least_width_or_height ) { - return array(); - } - - $defaults = array( - 'width' => null, - 'height' => null, - 'crop' => false, - ); - - return array_merge( $defaults, $size_data ); - } - - /** - * Get sizes for attachment post meta. - * - * @return array ImageSizes for attachment postmeta. - */ - public function generate_sizes_meta() { - - $metadata = array(); - - foreach ( $this->filtered_sizes() as $size => $size_data ) { - - $size_data = $this->standardize_size_data( $size_data ); - - if ( true === empty( $size_data ) ) { - continue; - } - - $resized_image = $this->resize( $size_data ); - - if ( true === is_array( $resized_image ) ) { - $metadata[ $size ] = $resized_image; - } - } - - return $metadata; - } - - /** - * @param array $size_data - * - * @return array|\WP_Error Array for usage in $metadata['sizes']; WP_Error on failure. - */ - protected function resize( $size_data ) { - - return $this->image->get_size( $size_data ); - - } -} diff --git a/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php b/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php deleted file mode 100644 index d364f2a3..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-photon-image.php +++ /dev/null @@ -1,243 +0,0 @@ -<?php -/** - * The Image Class. - * - * @package Jetpack - */ - -/** - * Represents a resizable image, exposing properties necessary for properly generating srcset. - */ -class Jetpack_Photon_Image { - - /** - * @var string $filename Attachment's Filename. - */ - public $filename; - - /** - * @var string/WP_Erorr $mime_type Attachment's mime-type, WP_Error on failure when recalculating the dimensions. - */ - private $mime_type; - - /** - * @var int $original_width Image original width. - */ - private $original_width; - - /** - * @var int $original_width Image original height. - */ - private $original_height; - - /** - * @var int $width Current attachment's width. - */ - private $width; - - /** - * @var int $height Current attachment's height. - */ - private $height; - - /** - * @var bool $is_resized Whether the attachment has been resized yet, or not. - */ - private $is_resized = false; - - /** - * Constructs the image object. - * - * The $data array should provide at least - * file : string Image file path - * width : int Image width - * height : int Image height - * - * @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; - } - - /** - * Resizes the image to given size. - * - * @param array $size_data Array of width, height, and crop properties of a size. - * - * @return bool|\WP_Error True if resize was successful, WP_Error on failure. - */ - public function resize( $size_data ) { - - $dimensions = $this->image_resize_dimensions( $size_data['width'], $size_data['height'], $size_data['crop'] ); - - if ( true === is_wp_error( $dimensions ) ) { - return $dimensions; // Returns \WP_Error. - } - - if ( true === is_wp_error( $this->mime_type ) ) { - return $this->mime_type; // Returns \WP_Error. - } - - $this->set_width_height( $dimensions ); - - return $this->is_resized = true; - } - - /** - * Generates size data for usage in $metadata['sizes'];. - * - * @param array $size_data Array of width, height, and crop properties of a size. - * - * @return array|\WP_Error An array containing file, width, height, and mime-type keys and it's values. WP_Error on failure. - */ - public function get_size( $size_data ) { - - $is_resized = $this->resize( $size_data ); - - if ( true === is_wp_error( $is_resized ) ) { - return $is_resized; - } - - return array( - 'file' => $this->get_filename(), - 'width' => $this->get_width(), - 'height' => $this->get_height(), - 'mime-type' => $this->get_mime_type(), - ); - } - - /** - * Resets the image to it's original dimensions. - * - * @return bool True on successful reset to original dimensions. - */ - public function reset_to_original() { - $this->width = $this->original_width; - $this->height = $this->original_height; - $this->is_resized = false; - - return true; - } - - /** - * Return the basename filename. If the image has been resized, including - * the resizing params for Jetpack CDN. - * - * @return string Basename of the filename. - */ - public function get_filename() { - - if ( true === $this->is_resized() ) { - $filename = $this->get_resized_filename(); - } else { - $filename = $this->filename; - } - - return wp_basename( $filename ); - } - - /** - * Returns current image width. Either original, or after resize. - * - * @return int - */ - public function get_width() { - return (int) $this->width; - } - - /** - * Returns current image height. Either original, or after resize. - * - * @return int - */ - public function get_height() { - return (int) $this->height; - } - - /** - * Returns image mime type. - * - * @return string|WP_Error Image's mime type or WP_Error if it was not determined. - */ - public function get_mime_type() { - return $this->mime_type; - } - - /** - * Checks the resize status of the image. - * - * @return bool If the image has been resized. - */ - public function is_resized() { - return ( true === $this->is_resized ); - } - - /** - * Get filename with proper args for the Photon service. - * - * @return string Filename with query args for Photon service - */ - protected function get_resized_filename() { - $query_args = array( - 'resize' => join( - ',', - array( - $this->get_width(), - $this->get_height(), - ) - ), - ); - - return add_query_arg( $query_args, $this->filename ); - } - - /** - * Get resize dimensions used for the Jetpack CDN service. - * - * Converts the list of values returned from `image_resize_dimensions()` to - * 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 - * - * @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 array_combine( - array( - 'dst_x', - 'dst_y', - 'src_x', - 'src_y', - 'dst_w', - 'dst_h', - 'src_w', - 'src_h', - ), - $dimensions - ); - } - - /** - * Sets proper width and height from dimensions. - * - * @param Array $dimensions an array of image dimensions. - * @return void - */ - protected function set_width_height( $dimensions ) { - $this->width = (int) $dimensions['dst_w']; - $this->height = (int) $dimensions['dst_h']; - } - -} diff --git a/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php b/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php deleted file mode 100644 index f8de70ee..00000000 --- a/plugins/jetpack/_inc/lib/class.jetpack-search-performance-logger.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php - -class Jetpack_Search_Performance_Logger { - /** - * @var Jetpack_Search_Performance_Logger - **/ - private static $instance = null; - - private $current_query = null; - private $query_started = null; - private $stats = null; - - static function init() { - if ( is_null( self::$instance ) ) { - self::$instance = new Jetpack_Search_Performance_Logger; - } - - return self::$instance; - } - - private function __construct() { - $this->stats = array(); - add_action( 'pre_get_posts', array( $this, 'begin_log_query' ), 10, 1 ); - add_action( 'did_jetpack_search_query', array( $this, 'log_jetpack_search_query' ) ); - add_filter( 'found_posts', array( $this, 'log_mysql_query' ), 10, 2 ); - add_action( 'wp_footer', array( $this, 'print_stats' ) ); - } - - public function begin_log_query( $query ) { - if ( $this->should_log_query( $query ) ) { - $this->query_started = microtime( true ); - $this->current_query = $query; - } - } - - 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 - $this->record_query_time( $duration, false ); - } - $this->reset_query_state(); - } - - return $found_posts; - } - - public function log_jetpack_search_query() { - $duration = microtime( true ) - $this->query_started; - if ( $duration < 60 ) { // eliminate outliers, likely tracking errors - $this->record_query_time( $duration, true ); - } - $this->reset_query_state(); - } - - private function reset_query_state() { - $this->query_started = null; - $this->current_query = null; - } - - private function should_log_query( $query ) { - return $query->is_main_query() && $query->is_search(); - } - - private function record_query_time( $duration, $was_jetpack_search ) { - $this->stats[] = array( $was_jetpack_search, intval( $duration * 1000 ) ); - } - - public function print_stats() { - $beacons = array(); - if ( ! empty( $this->stats ) ) { - foreach( $this->stats as $stat ) { - $search_type = $stat[0] ? 'es' : 'mysql'; - $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}"; - echo '<img src="' . $url . '" width="1" height="1" style="display:none;" alt=":)"/>'; - } - } -} diff --git a/plugins/jetpack/_inc/lib/class.media-extractor.php b/plugins/jetpack/_inc/lib/class.media-extractor.php deleted file mode 100644 index 6acf34db..00000000 --- a/plugins/jetpack/_inc/lib/class.media-extractor.php +++ /dev/null @@ -1,436 +0,0 @@ -<?php -/** - * Class with methods to extract metadata from a post/page about videos, images, links, mentions embedded - * in or attached to the post/page. - * - * @todo Additionally, have some filters on number of items in each field - */ -class Jetpack_Media_Meta_Extractor { - - // Some consts for what to extract - const ALL = 255; - const LINKS = 1; - const MENTIONS = 2; - const IMAGES = 4; - const SHORTCODES = 8; // Only the keeper shortcodes below - const EMBEDS = 16; - const HASHTAGS = 32; - - // For these, we try to extract some data from the shortcode, rather than just recording its presence (which we do for all) - // There should be a function get_{shortcode}_id( $atts ) or static method SomethingShortcode::get_{shortcode}_id( $atts ) for these. - private static $KEEPER_SHORTCODES = array( - 'youtube', - 'vimeo', - 'hulu', - 'ted', - 'wpvideo', - 'videopress', - ); - - /** - * Gets the specified media and meta info from the given post. - * NOTE: If you have the post's HTML content already and don't need image data, use extract_from_content() instead. - * - * @param $blog_id The ID of the blog - * @param $post_id The ID of the post - * @param $what_to_extract (int) A mask of things to extract, e.g. Jetpack_Media_Meta_Extractor::IMAGES | Jetpack_Media_Meta_Extractor::MENTIONS - * @returns a structure containing metadata about the embedded things, or empty array if nothing found, or WP_Error on error - */ - static public function extract( $blog_id, $post_id, $what_to_extract = self::ALL ) { - - // multisite? - if ( function_exists( 'switch_to_blog') ) - switch_to_blog( $blog_id ); - - $post = get_post( $post_id ); - $content = $post->post_title . "\n\n" . $post->post_content; - $char_cnt = strlen( $content ); - - //prevent running extraction on really huge amounts of content - if ( $char_cnt > 100000 ) //about 20k English words - $content = substr( $content, 0, 100000 ); - - $extracted = array(); - - // Get images first, we need the full post for that - if ( self::IMAGES & $what_to_extract ) { - $extracted = self::get_image_fields( $post ); - - // Turn off images so we can safely call extract_from_content() below - $what_to_extract = $what_to_extract - self::IMAGES; - } - - if ( function_exists( 'switch_to_blog') ) - restore_current_blog(); - - // All of the other things besides images can be extracted from just the content - $extracted = self::extract_from_content( $content, $what_to_extract, $extracted ); - - return $extracted; - } - - /** - * Gets the specified meta info from the given post content. - * NOTE: If you want IMAGES, call extract( $blog_id, $post_id, ...) which will give you more/better image extraction - * This method will give you an error if you ask for IMAGES. - * - * @param $content The HTML post_content of a post - * @param $what_to_extract (int) A mask of things to extract, e.g. Jetpack_Media_Meta_Extractor::IMAGES | Jetpack_Media_Meta_Extractor::MENTIONS - * @param $already_extracted (array) Previously extracted things, e.g. images from extract(), which can be used for x-referencing here - * @returns a structure containing metadata about the embedded things, or empty array if nothing found, or WP_Error on error - */ - static public function extract_from_content( $content, $what_to_extract = self::ALL, $already_extracted = array() ) { - $stripped_content = self::get_stripped_content( $content ); - - // Maybe start with some previously extracted things (e.g. images from extract() - $extracted = $already_extracted; - - // Embedded media objects will have already been converted to shortcodes by pre_kses hooks on save. - - if ( self::IMAGES & $what_to_extract ) { - $images = Jetpack_Media_Meta_Extractor::extract_images_from_content( $stripped_content, array() ); - $extracted = array_merge( $extracted, $images ); - } - - // ----------------------------------- MENTIONS ------------------------------ - - if ( self::MENTIONS & $what_to_extract ) { - if ( preg_match_all( '/(^|\s)@(\w+)/u', $stripped_content, $matches ) ) { - $mentions = array_values( array_unique( $matches[2] ) ); //array_unique() retains the keys! - $mentions = array_map( 'strtolower', $mentions ); - $extracted['mention'] = array( 'name' => $mentions ); - if ( !isset( $extracted['has'] ) ) - $extracted['has'] = array(); - $extracted['has']['mention'] = count( $mentions ); - } - } - - // ----------------------------------- HASHTAGS ------------------------------ - /** Some hosts may not compile with --enable-unicode-properties and kick a warning: - * Warning: preg_match_all() [function.preg-match-all]: Compilation failed: support for \P, \p, and \X has not been compiled - * Therefore, we only run this code block on wpcom, not in Jetpack. - */ - if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) && ( self::HASHTAGS & $what_to_extract ) ) { - //This regex does not exactly match Twitter's - // if there are problems/complaints we should implement this: - // https://github.com/twitter/twitter-text/blob/master/java/src/com/twitter/Regex.java - if ( preg_match_all( '/(?:^|\s)#(\w*\p{L}+\w*)/u', $stripped_content, $matches ) ) { - $hashtags = array_values( array_unique( $matches[1] ) ); //array_unique() retains the keys! - $hashtags = array_map( 'strtolower', $hashtags ); - $extracted['hashtag'] = array( 'name' => $hashtags ); - if ( !isset( $extracted['has'] ) ) - $extracted['has'] = array(); - $extracted['has']['hashtag'] = count( $hashtags ); - } - } - - // ----------------------------------- SHORTCODES ------------------------------ - - // Always look for shortcodes. - // If we don't want them, we'll just remove them, so we don't grab them as links below - $shortcode_pattern = '/' . get_shortcode_regex() . '/s'; - if ( preg_match_all( $shortcode_pattern, $content, $matches ) ) { - - $shortcode_total_count = 0; - $shortcode_type_counts = array(); - $shortcode_types = array(); - $shortcode_details = array(); - - if ( self::SHORTCODES & $what_to_extract ) { - - foreach( $matches[2] as $key => $shortcode ) { - //Elasticsearch (and probably other things) doesn't deal well with some chars as key names - $shortcode_name = preg_replace( '/[.,*"\'\/\\\\#+ ]/', '_', $shortcode ); - - $attr = shortcode_parse_atts( $matches[3][ $key ] ); - - $shortcode_total_count++; - if ( ! isset( $shortcode_type_counts[$shortcode_name] ) ) - $shortcode_type_counts[$shortcode_name] = 0; - $shortcode_type_counts[$shortcode_name]++; - - // Store (uniquely) presence of all shortcode regardless of whether it's a keeper (for those, get ID below) - // @todo Store number of occurrences? - if ( ! in_array( $shortcode_name, $shortcode_types ) ) - $shortcode_types[] = $shortcode_name; - - // For keeper shortcodes, also store the id/url of the object (e.g. youtube video, TED talk, etc.) - if ( in_array( $shortcode, self::$KEEPER_SHORTCODES ) ) { - unset( $id ); // Clear shortcode ID data left from the last shortcode - // We'll try to get the salient ID from the function jetpack_shortcode_get_xyz_id() - // If the shortcode is a class, we'll call XyzShortcode::get_xyz_id() - $shortcode_get_id_func = "jetpack_shortcode_get_{$shortcode}_id"; - $shortcode_class_name = ucfirst( $shortcode ) . 'Shortcode'; - $shortcode_get_id_method = "get_{$shortcode}_id"; - if ( function_exists( $shortcode_get_id_func ) ) { - $id = call_user_func( $shortcode_get_id_func, $attr ); - } else if ( method_exists( $shortcode_class_name, $shortcode_get_id_method ) ) { - $id = call_user_func( array( $shortcode_class_name, $shortcode_get_id_method ), $attr ); - } - if ( ! empty( $id ) - && ( ! isset( $shortcode_details[$shortcode_name] ) || ! in_array( $id, $shortcode_details[$shortcode_name] ) ) ) - $shortcode_details[$shortcode_name][] = $id; - } - } - - if ( $shortcode_total_count > 0 ) { - // Add the shortcode info to the $extracted array - if ( !isset( $extracted['has'] ) ) - $extracted['has'] = array(); - $extracted['has']['shortcode'] = $shortcode_total_count; - $extracted['shortcode'] = array(); - foreach ( $shortcode_type_counts as $type => $count ) - $extracted['shortcode'][$type] = array( 'count' => $count ); - if ( ! empty( $shortcode_types ) ) - $extracted['shortcode_types'] = $shortcode_types; - foreach ( $shortcode_details as $type => $id ) - $extracted['shortcode'][$type]['id'] = $id; - } - } - - // Remove the shortcodes form our copy of $content, so we don't count links in them as links below. - $content = preg_replace( $shortcode_pattern, ' ', $content ); - } - - // ----------------------------------- LINKS ------------------------------ - - if ( self::LINKS & $what_to_extract ) { - - // To hold the extracted stuff we find - $links = array(); - - // @todo Get the text inside the links? - - // Grab any links, whether in <a href="..." or not, but subtract those from shortcodes and images - // (we treat embed links as just another link) - if ( preg_match_all( '#(?:^|\s|"|\')(https?://([^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))))#', $content, $matches ) ) { - - foreach ( $matches[1] as $link_raw ) { - $url = parse_url( $link_raw ); - - // Data URI links - if ( isset( $url['scheme'] ) && 'data' === $url['scheme'] ) - continue; - - // Remove large (and likely invalid) links - if ( 4096 < strlen( $link_raw ) ) - continue; - - // Build a simple form of the URL so we can compare it to ones we found in IMAGES or SHORTCODES and exclude those - $simple_url = $url['scheme'] . '://' . $url['host'] . ( ! empty( $url['path'] ) ? $url['path'] : '' ); - if ( isset( $extracted['image']['url'] ) ) { - if ( in_array( $simple_url, (array) $extracted['image']['url'] ) ) - continue; - } - - list( $proto, $link_all_but_proto ) = explode( '://', $link_raw ); - - // Build a reversed hostname - $host_parts = array_reverse( explode( '.', $url['host'] ) ); - $host_reversed = ''; - foreach ( $host_parts as $part ) { - $host_reversed .= ( ! empty( $host_reversed ) ? '.' : '' ) . $part; - } - - $link_analyzed = ''; - if ( !empty( $url['path'] ) ) { - // The whole path (no query args or fragments) - $path = substr( $url['path'], 1 ); // strip the leading '/' - $link_analyzed .= ( ! empty( $link_analyzed ) ? ' ' : '' ) . $path; - - // The path split by / - $path_split = explode( '/', $path ); - if ( count( $path_split ) > 1 ) { - $link_analyzed .= ' ' . implode( ' ', $path_split ); - } - - // The fragment - if ( ! empty( $url['fragment'] ) ) - $link_analyzed .= ( ! empty( $link_analyzed ) ? ' ' : '' ) . $url['fragment']; - } - - // @todo Check unique before adding - $links[] = array( - 'url' => $link_all_but_proto, - 'host_reversed' => $host_reversed, - 'host' => $url['host'], - ); - } - - } - - $link_count = count( $links ); - if ( $link_count ) { - $extracted[ 'link' ] = $links; - if ( !isset( $extracted['has'] ) ) - $extracted['has'] = array(); - $extracted['has']['link'] = $link_count; - } - } - - // ----------------------------------- EMBEDS ------------------------------ - - //Embeds are just individual links on their own line - if ( self::EMBEDS & $what_to_extract ) { - - if ( !function_exists( '_wp_oembed_get_object' ) ) - include( ABSPATH . WPINC . '/class-oembed.php' ); - - // get an oembed object - $oembed = _wp_oembed_get_object(); - - // Grab any links on their own lines that may be embeds - if ( preg_match_all( '|^\s*(https?://[^\s"]+)\s*$|im', $content, $matches ) ) { - - // To hold the extracted stuff we find - $embeds = array(); - - foreach ( $matches[1] as $link_raw ) { - $url = parse_url( $link_raw ); - - list( $proto, $link_all_but_proto ) = explode( '://', $link_raw ); - - // Check whether this "link" is really an embed. - foreach ( $oembed->providers as $matchmask => $data ) { - list( $providerurl, $regex ) = $data; - - // Turn the asterisk-type provider URLs into regex - if ( !$regex ) { - $matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i'; - $matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask ); - } - - if ( preg_match( $matchmask, $link_raw ) ) { - $provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML - $embeds[] = $link_all_but_proto; // @todo Check unique before adding - - // @todo Try to get ID's for the ones we care about (shortcode_keepers) - break; - } - } - } - - if ( ! empty( $embeds ) ) { - if ( !isset( $extracted['has'] ) ) - $extracted['has'] = array(); - $extracted['has']['embed'] = count( $embeds ); - $extracted['embed'] = array( 'url' => array() ); - foreach ( $embeds as $e ) - $extracted['embed']['url'][] = $e; - } - } - } - - return $extracted; - } - - /** - * @param $post A post object - * @param $args (array) Optional args, see defaults list for details - * @returns array Returns an array of all images meeting the specified criteria in $args - * - * Uses Jetpack Post Images - */ - private static function get_image_fields( $post, $args = array() ) { - - $defaults = array( - 'width' => 200, // Required minimum width (if possible to determine) - 'height' => 200, // Required minimum height (if possible to determine) - ); - - $args = wp_parse_args( $args, $defaults ); - - $image_list = array(); - $image_booleans = array(); - $image_booleans['gallery'] = 0; - - $from_featured_image = Jetpack_PostImages::from_thumbnail( $post->ID, $args['width'], $args['height'] ); - if ( !empty( $from_featured_image ) ) { - $srcs = wp_list_pluck( $from_featured_image, 'src' ); - $image_list = array_merge( $image_list, $srcs ); - } - - $from_slideshow = Jetpack_PostImages::from_slideshow( $post->ID, $args['width'], $args['height'] ); - if ( !empty( $from_slideshow ) ) { - $srcs = wp_list_pluck( $from_slideshow, 'src' ); - $image_list = array_merge( $image_list, $srcs ); - } - - $from_gallery = Jetpack_PostImages::from_gallery( $post->ID ); - if ( !empty( $from_gallery ) ) { - $srcs = wp_list_pluck( $from_gallery, 'src' ); - $image_list = array_merge( $image_list, $srcs ); - $image_booleans['gallery']++; // @todo This count isn't correct, will only every count 1 - } - - // @todo Can we check width/height of these efficiently? Could maybe use query args at least, before we strip them out - $image_list = Jetpack_Media_Meta_Extractor::get_images_from_html( $post->post_content, $image_list ); - - return Jetpack_Media_Meta_Extractor::build_image_struct( $image_list, $image_booleans ); - } - - public static function extract_images_from_content( $content, $image_list ) { - $image_list = Jetpack_Media_Meta_Extractor::get_images_from_html( $content, $image_list ); - return Jetpack_Media_Meta_Extractor::build_image_struct( $image_list, array() ); - } - - public static function build_image_struct( $image_list, $image_booleans ) { - if ( ! empty( $image_list ) ) { - $retval = array( 'image' => array() ); - $image_list = array_unique( $image_list ); - foreach ( $image_list as $img ) { - $retval['image'][] = array( 'url' => $img ); - } - $image_booleans['image'] = count( $retval['image'] ); - if ( ! empty( $image_booleans ) ) - $retval['has'] = $image_booleans; - return $retval; - } else { - return array(); - } - } - - /** - * - * @param string $html Some markup, possibly containing image tags - * @param array $images_already_extracted (just an array of image URLs without query strings, no special structure), used for de-duplication - * @return array Image URLs extracted from the HTML, stripped of query params and de-duped - */ - public static function get_images_from_html( $html, $images_already_extracted ) { - $image_list = $images_already_extracted; - $from_html = Jetpack_PostImages::from_html( $html ); - if ( !empty( $from_html ) ) { - $srcs = wp_list_pluck( $from_html, 'src' ); - foreach( $srcs as $image_url ) { - if ( ( $src = parse_url( $image_url ) ) && isset( $src['scheme'], $src['host'], $src['path'] ) ) { - // Rebuild the URL without the query string - $queryless = $src['scheme'] . '://' . $src['host'] . $src['path']; - } elseif ( $length = strpos( $image_url, '?' ) ) { - // If parse_url() didn't work, strip off the query string the old fashioned way - $queryless = substr( $image_url, 0, $length ); - } else { - // Failing that, there was no spoon! Err ... query string! - $queryless = $image_url; - } - - // Discard URLs that are longer then 4KB, these are likely data URIs or malformed HTML. - if ( 4096 < strlen( $queryless ) ) { - continue; - } - - if ( ! in_array( $queryless, $image_list ) ) { - $image_list[] = $queryless; - } - } - } - return $image_list; - } - - private static function get_stripped_content( $content ) { - $clean_content = strip_tags( $content ); - $clean_content = html_entity_decode( $clean_content ); - //completely strip shortcodes and any content they enclose - $clean_content = strip_shortcodes( $clean_content ); - return $clean_content; - } -} diff --git a/plugins/jetpack/_inc/lib/class.media-summary.php b/plugins/jetpack/_inc/lib/class.media-summary.php deleted file mode 100644 index 1cce160b..00000000 --- a/plugins/jetpack/_inc/lib/class.media-summary.php +++ /dev/null @@ -1,354 +0,0 @@ -<?php -/** - * Class Jetpack_Media_Summary - * - * embed [video] > gallery > image > text - */ -class Jetpack_Media_Summary { - - private static $cache = array(); - - static function get( $post_id, $blog_id = 0, $args = array() ) { - - $defaults = array( - 'max_words' => 16, - 'max_chars' => 256, - ); - $args = wp_parse_args( $args, $defaults ); - - $switched = false; - if ( !empty( $blog_id ) && $blog_id != get_current_blog_id() && function_exists( 'switch_to_blog' ) ) { - switch_to_blog( $blog_id ); - $switched = true; - } else { - $blog_id = get_current_blog_id(); - } - - $cache_key = "{$blog_id}_{$post_id}_{$args['max_words']}_{$args['max_chars']}"; - if ( isset( self::$cache[ $cache_key ] ) ) { - return self::$cache[ $cache_key ]; - } - - if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) { - if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { - jetpack_require_lib( 'class.wpcom-media-meta-extractor' ); - } else { - jetpack_require_lib( 'class.media-extractor' ); - } - } - - $post = get_post( $post_id ); - $permalink = get_permalink( $post_id ); - - $return = array( - 'type' => 'standard', - 'permalink' => $permalink, - 'image' => '', - 'excerpt' => '', - 'word_count' => 0, - 'secure' => array( - 'image' => '', - ), - 'count' => array( - 'image' => 0, - 'video' => 0, - 'word' => 0, - 'link' => 0, - ), - ); - - if ( empty( $post->post_password ) ) { - $return['excerpt'] = self::get_excerpt( $post->post_content, $post->post_excerpt, $args['max_words'], $args['max_chars'] , $post); - $return['count']['word'] = self::get_word_count( $post->post_content ); - $return['count']['word_remaining'] = self::get_word_remaining_count( $post->post_content, $return['excerpt'] ); - $return['count']['link'] = self::get_link_count( $post->post_content ); - } - - $extract = Jetpack_Media_Meta_Extractor::extract( $blog_id, $post_id, Jetpack_Media_Meta_Extractor::ALL ); - - if ( empty( $extract['has'] ) ) - return $return; - - // Prioritize [some] video embeds - if ( !empty( $extract['has']['shortcode'] ) ) { - foreach ( $extract['shortcode'] as $type => $data ) { - switch ( $type ) { - case 'videopress': - case 'wpvideo': - if ( 0 == $return['count']['video'] ) { - // If there is no id on the video, then let's just skip this - if ( ! isset ( $data['id'][0] ) ) { - break; - } - - $guid = $data['id'][0]; - $video_info = videopress_get_video_details( $guid ); - - // Only add the video tags if the guid returns a valid videopress object. - if ( $video_info instanceof stdClass ) { - // Continue early if we can't find a Video slug. - if ( empty( $video_info->files->std->mp4 ) ) { - break; - } - - $url = sprintf( - 'https://videos.files.wordpress.com/%1$s/%2$s', - $guid, - $video_info->files->std->mp4 - ); - - $thumbnail = $video_info->poster; - if ( ! empty( $thumbnail ) ) { - $return['image'] = $thumbnail; - $return['secure']['image'] = $thumbnail; - } - - $return['type'] = 'video'; - $return['video'] = esc_url_raw( $url ); - $return['video_type'] = 'video/mp4'; - $return['secure']['video'] = $return['video']; - } - - } - $return['count']['video']++; - break; - case 'youtube': - if ( 0 == $return['count']['video'] ) { - $return['type'] = 'video'; - $return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] ); - $return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] ); - $return['secure']['video'] = self::https( $return['video'] ); - $return['secure']['image'] = self::https( $return['image'] ); - } - $return['count']['video']++; - break; - case 'vimeo': - if ( 0 == $return['count']['video'] ) { - $return['type'] = 'video'; - $return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] ); - $return['secure']['video'] = self::https( $return['video'] ); - - $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true ); - if ( !empty( $poster_image ) ) { - $return['image'] = $poster_image; - $poster_url_parts = parse_url( $poster_image ); - $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path']; - } - } - $return['count']['video']++; - break; - } - } - - } - - if ( !empty( $extract['has']['embed'] ) ) { - foreach( $extract['embed']['url'] as $embed ) { - if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) { - if ( 0 == $return['count']['video'] ) { - $return['type'] = 'video'; - $return['video'] = 'http://' . $embed; - $return['secure']['video'] = self::https( $return['video'] ); - if ( false !== strpos( $embed, 'youtube' ) ) { - $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) ); - $return['secure']['image'] = self::https( $return['image'] ); - } else if ( false !== strpos( $embed, 'youtu.be' ) ) { - $youtube_id = jetpack_get_youtube_id( $return['video'] ); - $return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be'; - $return['secure']['video'] = self::https( $return['video'] ); - $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) ); - $return['secure']['image'] = self::https( $return['image'] ); - } else if ( false !== strpos( $embed, 'vimeo' ) ) { - $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true ); - if ( !empty( $poster_image ) ) { - $return['image'] = $poster_image; - $poster_url_parts = parse_url( $poster_image ); - $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path']; - } - } else if ( false !== strpos( $embed, 'dailymotion' ) ) { - $return['image'] = str_replace( 'dailymotion.com/video/','dailymotion.com/thumbnail/video/', $embed ); - $return['image'] = parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image']; - $return['secure']['image'] = self::https( $return['image'] ); - } - - } - $return['count']['video']++; - } - } - } - - // Do we really want to make the video the primary focus of the post? - if ( 'video' == $return['type'] ) { - $content = wpautop( strip_tags( $post->post_content ) ); - $paragraphs = explode( '</p>', $content ); - $number_of_paragraphs = 0; - - foreach ( $paragraphs as $i => $paragraph ) { - // Don't include blank lines as a paragraph - if ( '' == trim( $paragraph ) ) { - unset( $paragraphs[$i] ); - continue; - } - $number_of_paragraphs++; - } - - $number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos.. - - // More than 2 paragraph? The video is not the primary focus so we can do some more analysis - if ( $number_of_paragraphs > 2 ) - $return['type'] = 'standard'; - } - - // If we don't have any prioritized embed... - if ( 'standard' == $return['type'] ) { - if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) { - //... Then we prioritize galleries first (multiple images returned) - $return['type'] = 'gallery'; - $return['images'] = $extract['image']; - foreach ( $return['images'] as $image ) { - $return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) ); - $return['count']['image']++; - } - } else if ( ! empty( $extract['has']['image'] ) ) { - // ... Or we try and select a single image that would make sense - $content = wpautop( strip_tags( $post->post_content ) ); - $paragraphs = explode( '</p>', $content ); - $number_of_paragraphs = 0; - - foreach ( $paragraphs as $i => $paragraph ) { - // Don't include 'actual' captions as a paragraph - if ( false !== strpos( $paragraph, '[caption' ) ) { - unset( $paragraphs[$i] ); - continue; - } - // Don't include blank lines as a paragraph - if ( '' == trim( $paragraph ) ) { - unset( $paragraphs[$i] ); - continue; - } - $number_of_paragraphs++; - } - - $return['image'] = $extract['image'][0]['url']; - $return['secure']['image'] = self::ssl_img( $return['image'] ); - $return['count']['image']++; - - if ( $number_of_paragraphs <= 2 && 1 == count( $extract['image'] ) ) { - // If we have lots of text or images, let's not treat it as an image post, but return its first image - $return['type'] = 'image'; - } - } - } - - if ( $switched ) { - restore_current_blog(); - } - - /** - * Allow a theme or plugin to inspect and ultimately change the media summary. - * - * @since 4.4.0 - * - * @param array $data The calculated media summary data. - * @param int $post_id The id of the post this data applies to. - */ - $return = apply_filters( 'jetpack_media_summary_output', $return, $post_id ); - - self::$cache[ $cache_key ] = $return; - - return $return; - } - - static function https( $str ) { - return str_replace( 'http://', 'https://', $str ); - } - - static function ssl_img( $url ) { - if ( false !== strpos( $url, 'files.wordpress.com' ) ) { - return self::https( $url ); - } else { - return self::https( jetpack_photon_url( $url ) ); - } - } - - static function get_video_poster( $type, $id ) { - if ( 'videopress' == $type ) { - if ( function_exists( 'video_get_highest_resolution_image_url' ) ) { - return video_get_highest_resolution_image_url( $id ); - } else if ( class_exists( 'VideoPress_Video' ) ) { - $video = new VideoPress_Video( $id ); - return $video->poster_frame_uri; - } - } else if ( 'youtube' == $type ) { - return 'http://img.youtube.com/vi/'.$id.'/0.jpg'; - } - } - - static function clean_text( $text ) { - return trim( - preg_replace( - '/[\s]+/', - ' ', - preg_replace( - '@https?://[\S]+@', - '', - strip_shortcodes( - strip_tags( - $text - ) - ) - ) - ) - ); - } - - /** - * Retrieve an excerpt for the post summary. - * - * This function works around a suspected problem with Core. If resolved, this function should be simplified. - * @link https://github.com/Automattic/jetpack/pull/8510 - * @link https://core.trac.wordpress.org/ticket/42814 - * - * @param string $post_content The post's content. - * @param string $post_excerpt The post's excerpt. Empty if none was explicitly set. - * @param int $max_words Maximum number of words for the excerpt. Used on wp.com. Default 16. - * @param int $max_chars Maximum characters in the excerpt. Used on wp.com. Default 256. - * @param WP_Post $requested_post The post object. - * @return string Post excerpt. - **/ - static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256, $requested_post = null ) { - global $post; - $original_post = $post; // Saving the global for later use. - if ( function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) { - return self::clean_text( wpcom_enhanced_excerpt_extract_excerpt( array( - 'text' => $post_content, - 'excerpt_only' => true, - 'show_read_more' => false, - 'max_words' => $max_words, - 'max_chars' => $max_chars, - 'read_more_threshold' => 25, - ) ) ); - } elseif ( $requested_post instanceof WP_Post ) { - $post = $requested_post; // setup_postdata does not set the global. - setup_postdata( $post ); - /** This filter is documented in core/src/wp-includes/post-template.php */ - $post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt, $post ); - $post = $original_post; // wp_reset_postdata uses the $post global. - wp_reset_postdata(); - return self::clean_text( $post_excerpt ); - } - return ''; - } - - static function get_word_count( $post_content ) { - return str_word_count( self::clean_text( $post_content ) ); - } - - static function get_word_remaining_count( $post_content, $excerpt_content ) { - return str_word_count( self::clean_text( $post_content ) ) - str_word_count( self::clean_text( $excerpt_content ) ); - } - - static function get_link_count( $post_content ) { - return preg_match_all( '/\<a[\> ]/', $post_content, $matches ); - } -} diff --git a/plugins/jetpack/_inc/lib/class.media.php b/plugins/jetpack/_inc/lib/class.media.php deleted file mode 100644 index e48c4aad..00000000 --- a/plugins/jetpack/_inc/lib/class.media.php +++ /dev/null @@ -1,505 +0,0 @@ -<?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'; - - /** - * Generate a filename in function of the original filename of the media. - * The returned name has the `{basename}-{hash}-{random-number}.{ext}` shape. - * The hash is built according to the filename trying to avoid name collisions - * with other media files. - * - * @param number $media_id - media post ID - * @param string $new_filename - the new filename - * @return string A random filename. - */ - public static function generate_new_filename( $media_id, $new_filename ) { - // get the right filename extension - $new_filename_paths = pathinfo( $new_filename ); - $new_file_ext = $new_filename_paths['extension']; - - // take out filename from the original file or from the current attachment - $original_media = (array) self::get_original_media( $media_id ); - - if ( ! empty( $original_media ) ) { - $original_file_parts = pathinfo( $original_media['file'] ); - $filename_base = $original_file_parts['filename']; - } else { - $current_file = get_attached_file( $media_id ); - $current_file_parts = pathinfo( $current_file ); - $current_file_ext = $current_file_parts['filename']; - $filename_base = $current_file_parts['filename']; - } - - // add unique seed based on the filename - $filename_base .= '-' . crc32( $filename_base ) . '-'; - - $number_suffix = time() . rand( 100, 999 ); - - do { - $filename = $filename_base; - $filename .= $number_suffix; - $file_ext = $new_file_ext ? $new_file_ext : $current_file_ext; - - $new_filename = "{$filename}.{$file_ext}"; - $new_path = "{$current_file_parts['dirname']}/$new_filename"; - $number_suffix++; - } while( file_exists( $new_path ) ); - - return $new_filename; - } - - /** - * File urls use the post (image item) date to generate a folder path. - * Post dates can change, so we use the original date used in the `guid` - * url so edits can remain in the same folder. In the following function - * we capture a string in the format of `YYYY/MM` from the guid. - * - * For example with a guid of - * "http://test.files.wordpress.com/2016/10/test.png" the resulting string - * would be: "2016/10" - * - * @param number $media_id - * @return string - */ - private function get_time_string_from_guid( $media_id ) { - $time = date( "Y/m", strtotime( current_time( 'mysql' ) ) ); - - if ( $media = get_post( $media_id ) ) { - $pattern = '/\/(\d{4}\/\d{2})\//'; - preg_match( $pattern, $media->guid, $matches ); - if ( count( $matches ) > 1 ) { - $time = $matches[1]; - } - } - return $time; - } - - /** - * Return an array of allowed mime_type items used to upload a media file. - * - * @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 - ) ) ); - } - - /** - * Checks that the mime type of the file - * is among those in a filterable list of mime types. - * - * @param string $file Path to file to get its mime type. - * @return bool - */ - protected static function is_file_supported_for_sideloading( $file ) { - if ( class_exists( 'finfo' ) ) { // php 5.3+ - // phpcs:ignore PHPCompatibility.PHP.NewClasses.finfoFound - $finfo = new finfo( FILEINFO_MIME ); - $mime = explode( '; ', $finfo->file( $file ) ); - $type = $mime[0]; - - } elseif ( function_exists( 'mime_content_type' ) ) { // PHP 5.2 - $type = mime_content_type( $file ); - - } else { - return false; - } - - /** - * Filter the list of supported mime types for media sideloading. - * - * @since 4.0 - * - * @module json-api - * - * @param array $supported_mime_types Array of the supported mime types for media sideloading. - */ - $supported_mime_types = apply_filters( 'jetpack_supported_media_sideload_types', array( - 'image/png', - 'image/jpeg', - 'image/gif', - 'image/bmp', - 'video/quicktime', - 'video/mp4', - 'video/mpeg', - 'video/ogg', - 'video/3gpp', - 'video/3gpp2', - 'video/h261', - 'video/h262', - 'video/h264', - 'video/x-msvideo', - 'video/x-ms-wmv', - 'video/x-ms-asf', - ) ); - - // If the type returned was not an array as expected, then we know we don't have a match. - if ( ! is_array( $supported_mime_types ) ) { - return false; - } - - return in_array( $type, $supported_mime_types ); - } - - /** - * Try to remove the temporal file from the given file array. - * - * @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'] ) ) { - return false; - } - return @unlink( $file_array['tmp_name'] ); - } - - /** - * Save the given temporal file considering file type, - * correct location according to the original file path, etc. - * 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 - * @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 ) { - $tmp_filename = $file_array['tmp_name']; - - if ( ! file_exists( $tmp_filename ) ) { - return new WP_Error( 'invalid_input', 'No media provided in input.' ); - } - - // add additional mime_types through of the `jetpack_supported_media_sideload_types` filter - $mime_type_static_filter = array( - 'Jetpack_Media', - 'get_allowed_mime_types' - ); - - add_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter ); - if ( - ! self::is_file_supported_for_sideloading( $tmp_filename ) && - ! file_is_displayable_image( $tmp_filename ) - ) { - @unlink( $tmp_filename ); - 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' ] ); - - // start to create the parameters to move the temporal file - $overrides = array( 'test_form' => false ); - - // 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 ); - - self::remove_tmp_file( $file_array ); - - if ( isset( $file['error'] ) ) { - return new WP_Error( 'upload_error', $file['error'] ); - } - - return $file; - } - - /** - * Return an object with an snapshot of a revision item. - * - * @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 ); - - $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 ) - ); - - return (object) $snapshot; - } - - /** - * 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 - * @return bool `true` if the item has been added. Otherwise `false`. - */ - public static function register_revision( $media_item, $file, $has_original_media ) { - if ( is_wp_error( $file ) || ! $has_original_media ) { - return false; - } - - 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 - * @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 the original media data - */ - public static function get_original_media( $media_id ) { - $original = get_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA, true ); - $original = $original ? $original : array(); - return $original; - } - - 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 - touch( $pathname ); - } - - return wp_delete_file( $pathname ); - } - - /** - * 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. - */ - private static function delete_media_history_file( $media_id, $filename ) { - $attached_path = get_attached_file( $media_id ); - $attached_parts = pathinfo( $attached_path ); - $dirname = $attached_parts['dirname']; - - $pathname = $dirname . '/' . $filename; - - // remove thumbnails - $metadata = wp_generate_attachment_metadata( $media_id, $pathname ); - - if ( isset( $metadata ) && isset( $metadata['sizes'] ) ) { - foreach ( $metadata['sizes'] as $size => $properties ) { - self::delete_file( $dirname . '/' . $properties['file'] ); - } - } - - // remove primary file - self::delete_file( $pathname ); - } - - /** - * Remove specific items from the `revision history` array - * depending on the given criteria: array( - * 'from' => (int) <from>, - * 'to' => (int) <to>, - * ) - * - * 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 - * @return array `revision_history` array updated. - */ - public static function remove_items_from_revision_history( $media_id, $criteria = array(), $revision_history ) { - if ( ! isset ( $revision_history ) ) { - $revision_history = self::get_revision_history( $media_id ); - } - - $from = $criteria['from']; - $to = $criteria['to'] ? $criteria['to'] : ( $from + 1 ); - - for ( $i = $from; $i < $to; $i++ ) { - $removed_item = array_slice( $revision_history, $from, 1 ); - if ( ! $removed_item ) { - break; - } - - array_splice( $revision_history, $from, 1 ); - self::delete_media_history_file( $media_id, $removed_item[0]->file ); - } - - // 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 ); - } - - return $revision_history; - } - - /** - * 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. - * @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; - } - - $revision_history = self::get_revision_history( $media_id ); - - $total = count( $revision_history ); - - if ( $total < $limit ) { - return array(); - } - - self::remove_items_from_revision_history( - $media_id, - array( 'from' => $limit, 'to' => $total ), - $revision_history - ); - - return self::get_revision_history( $media_id ); - } - - /** - * Remove the original file and clean the post metadata. - * - * @param number $media_id - media post ID - */ - public static function clean_original_media( $media_id ) { - $original_file = self::get_original_media( $media_id ); - - if ( ! $original_file ) { - return null; - } - - self::delete_media_history_file( $media_id, $original_file->file ); - return delete_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA ); - } - - /** - * Clean `revision_history` of the given $media_id. it means: - * - remove all media files tied to the `revision_history` items. - * - clean `revision_history` meta data. - * - remove and clean the `original_media` - * - * @param number $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(); - - if ( $total < 1 ) { - return $updated_history; - } - - $updated_history = self::remove_items_from_revision_history( - $media_id, - array( 'from' => 0, 'to' => $total ), - $revision_history - ); - - return $updated_history; - } - - /** - * Edit media item process: - * - * - update attachment file - * - preserve original media file - * - trace revision history - * - * @param number $media_id - media post ID - * @param array $file_array - temporal file - * @return {Post|WP_Error} Updated media item or a WP_Error is something went wrong. - */ - public static function edit_media_file( $media_id, $file_array ) { - $media_item = get_post( $media_id ); - $has_original_media = self::get_original_media( $media_id ); - - if ( ! $has_original_media ) { - // The first time that the media is updated - // the original media is stored into the revision_history - $snapshot = self::get_snapshot( $media_item ); - add_post_meta( $media_id, self::$WP_ORIGINAL_MEDIA, $snapshot, true ); - } - - // save temporary file in the correct location - $uploaded_file = self::save_temporary_file( $file_array, $media_id ); - - if ( is_wp_error( $uploaded_file ) ) { - self::remove_tmp_file( $file_array ); - return $uploaded_file; - } - - // revision_history control - self::register_revision( $media_item, $uploaded_file, $has_original_media ); - - $uploaded_path = $uploaded_file['file']; - $udpated_mime_type = $uploaded_file['type']; - $was_updated = update_attached_file( $media_id, $uploaded_path ); - - if ( ! $was_updated ) { - return WP_Error( 'update_error', 'Media update error' ); - } - - $new_metadata = wp_generate_attachment_metadata( $media_id, $uploaded_path ); - wp_update_attachment_metadata( $media_id, $new_metadata ); - - // check maximum amount of revision_history - self::limit_revision_history( $media_id ); - - $edited_action = wp_update_post( (object) array( - 'ID' => $media_id, - 'post_mime_type' => $udpated_mime_type - ), true ); - - if ( is_wp_error( $edited_action ) ) { - return $edited_action; - } - - return $media_item; - } -} - -// hook: clean revision history when the media item is deleted -function clean_revision_history( $media_id ) { - Jetpack_Media::clean_revision_history( $media_id ); -}; - -add_action( 'delete_attachment', '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 deleted file mode 100644 index e599b275..00000000 --- a/plugins/jetpack/_inc/lib/core-api/class-wpcom-rest-field-controller.php +++ /dev/null @@ -1,330 +0,0 @@ -<?php - -// @todo - nicer API for array values? - -/** - * `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()` - */ -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. - */ - protected $object_type; - - /** - * @var string $field_name The name of the REST API field to add. - */ - protected $field_name; - - 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' ); - 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' ); - return; - } - - add_action( 'rest_api_init', array( $this, 'register_fields' ) ); - } - - /** - * Registers the field with the appropriate schema and callbacks. - */ - public function register_fields() { - foreach ( (array) $this->object_type as $object_type ) { - register_rest_field( - $object_type, - $this->field_name, - array( - 'get_callback' => array( $this, 'get_for_response' ), - 'update_callback' => array( $this, 'update_from_request' ), - 'schema' => $this->get_schema(), - ) - ); - } - } - - /** - * Ensures the response matches the schema and request context. - * - * @param mixed $value - * @param WP_REST_Request $request - * @return mixed - */ - private function prepare_for_response( $value, $request ) { - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $schema = $this->get_schema(); - - $is_valid = rest_validate_value_from_schema( $value, $schema, $this->field_name ); - if ( is_wp_error( $is_valid ) ) { - return $is_valid; - } - - return $this->filter_response_by_context( $value, $schema, $context ); - } - - /** - * Returns the schema's default value - * - * If there is no default, returns the type's falsey value. - * - * @param array $schema - * @return mixed - */ - final public function get_default_value( $schema ) { - if ( isset( $schema['default'] ) ) { - return $schema['default']; - } - - // If you have something more complicated, use $schema['default']; - switch ( isset( $schema['type'] ) ? $schema['type'] : 'null' ) { - case 'string': - return ''; - case 'integer': - case 'number': - return 0; - case 'object': - return (object) array(); - case 'array': - return array(); - case 'boolean': - return false; - case 'null': - default: - return null; - } - } - - /** - * The field's wrapped getter. Does permission checks and output preparation. - * - * 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` - * @return mixed - */ - final public function get_for_response( $object_data, $field_name, $request, $object_type ) { - $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' ); - return $this->get_default_value( $this->get_schema() ); - } - - if ( is_wp_error( $permission_check ) ) { - return $this->get_default_value( $this->get_schema() ); - } - - $value = $this->get( $object_data, $request ); - - return $this->prepare_for_response( $value, $request ); - } - - /** - * The field's wrapped setter. Does permission checks. - * - * 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` - * @return void|WP_Error - */ - final public function update_from_request( $value, $object_data, $field_name, $request, $object_type ) { - $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 ) ); - } - - if ( is_wp_error( $permission_check ) ) { - return $permission_check; - } - - $updated = $this->update( $value, $object_data, $request ); - - if ( is_wp_error( $updated ) ) { - return $updated; - } - } - - /** - * 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 - * @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' ); - } - - /** - * 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 - * @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' ); - } - - /** - * 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 - * @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' ); - } - - /** - * 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 - * @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' ); - } - - /** - * The JSON Schema for the field - * - * @link https://json-schema.org/understanding-json-schema/ - * As of WordPress 5.0, Core currently understands: - * * type - * * string - not minLength, not maxLength, not pattern - * * integer - minimum, maximum, exclusiveMinimum, exclusiveMaximum, not multipleOf - * * number - minimum, maximum, exclusiveMinimum, exclusiveMaximum, not multipleOf - * * boolean - * * null - * * object - properties, additionalProperties, not propertyNames, not dependencies, not patternProperties, not required - * * array: only lists, not tuples - items, not minItems, not maxItems, not uniqueItems, not contains - * * enum - * * format - * * date-time - * * email - * * ip - * * uri - * As of WordPress 5.0, Core does not support: - * * Multiple type: `type: [ 'string', 'integer' ]` - * * $ref, allOf, anyOf, oneOf, not, const - * - * @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' ); - } - - /** - * @param array $schema - * @param string $context REST API Request context - * @return bool - */ - private function is_valid_for_context( $schema, $context ) { - return empty( $schema['context'] ) || in_array( $context, $schema['context'], true ); - } - - /** - * Removes properties that should not appear in the current - * request's context - * - * $context is a Core REST API Framework request attribute that is - * always one of: - * * view (what you see on the blog) - * * edit (what you see in an editor) - * * embed (what you see in, e.g., an oembed) - * - * Fields (and sub-fields, and sub-sub-...) can be flagged for a - * set of specific contexts via the field's schema. - * - * The Core API will filter out top-level fields with the wrong - * context, but will not recurse deeply enough into arrays/objects - * to remove all levels of sub-fields with the wrong context. - * - * This function handles that recursion. - * - * @param mixed $value - * @param array $schema - * @param string $context REST API Request context - * @return mixed Filtered $value - */ - final public function filter_response_by_context( $value, $schema, $context ) { - if ( ! $this->is_valid_for_context( $schema, $context ) ) { - // We use this intentionally odd looking WP_Error object - // internally only in this recursive function (see below - // in the `object` case). It will never be output by the REST API. - // If we return this for the top level object, Core - // correctly remove the top level object from the response - // for us. - return new WP_Error( '__wrong-context__' ); - } - - switch ( $schema['type'] ) { - case 'array': - if ( ! isset( $schema['items'] ) ) { - return $value; - } - - // Shortcircuit if we know none of the items are valid for this context. - // This would only happen in a strangely written schema. - if ( ! $this->is_valid_for_context( $schema['items'], $context ) ) { - return array(); - } - - // Recurse to prune sub-properties of each item. - foreach ( $value as $key => $item ) { - $value[ $key ] = $this->filter_response_by_context( $item, $schema['items'], $context ); - } - - return $value; - case 'object': - if ( ! isset( $schema['properties'] ) ) { - return $value; - } - - foreach ( $value as $field_name => $field_value ) { - if ( isset( $schema['properties'][ $field_name ] ) ) { - $field_value = $this->filter_response_by_context( $field_value, $schema['properties'][ $field_name ], $context ); - if ( is_wp_error( $field_value ) && '__wrong-context__' === $field_value->get_error_code() ) { - unset( $value[ $field_name ] ); - } else { - // Respect recursion that pruned sub-properties of each property. - $value[ $field_name ] = $field_value; - } - } - } - - return (object) $value; - } - - return $value; - } -} 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 deleted file mode 100644 index 368b381a..00000000 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php +++ /dev/null @@ -1,1769 +0,0 @@ -<?php -/** - * 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 { - - /** - * Check if the module requires the site to be publicly accessible from WPCOM. - * If the site meets this requirement, the module is activated. Otherwise an error is returned. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Module slug. - * @type bool $active should module be activated. - * } - * - * @return WP_REST_Response|WP_Error A REST response if the request was served successfully, otherwise an error. - */ - public function process( $request ) { - if ( $request['active'] ) { - return $this->activate_module( $request ); - } else { - return $this->deactivate_module( $request ); - } - } - - /** - * If it's a valid Jetpack module, activate it. - * - * @since 4.3.0 - * - * @param string|WP_REST_Request $request It's a WP_REST_Request when called from endpoint /module/<slug>/* - * and a string when called from Jetpack_Core_API_Data->update_data. - * { - * Array of parameters received by request. - * - * @type string $slug Module slug. - * } - * - * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error. - */ - public function activate_module( $request ) { - $module_slug = ''; - - if ( - ( - is_array( $request ) - || is_object( $request ) - ) - && isset( $request['slug'] ) - ) { - $module_slug = $request['slug']; - } else { - $module_slug = $request; - } - - if ( ! Jetpack::is_module( $module_slug ) ) { - return new WP_Error( - 'not_found', - esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - if ( ! Jetpack_Plan::supports( $module_slug ) ) { - return new WP_Error( - 'not_supported', - esc_html__( 'The requested Jetpack module is not supported by your plan.', 'jetpack' ), - array( 'status' => 424 ) - ); - } - - 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 new WP_Error( - 'activation_failed', - esc_html__( 'The requested Jetpack module could not be activated.', 'jetpack' ), - array( 'status' => 424 ) - ); - } - - /** - * If it's a valid Jetpack module, deactivate it. - * - * @since 4.3.0 - * - * @param string|WP_REST_Request $request It's a WP_REST_Request when called from endpoint /module/<slug>/* - * and a string when called from Jetpack_Core_API_Data->update_data. - * { - * Array of parameters received by request. - * - * @type string $slug Module slug. - * } - * - * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error. - */ - public function deactivate_module( $request ) { - $module_slug = ''; - - if ( - ( - is_array( $request ) - || is_object( $request ) - ) - && isset( $request['slug'] ) - ) { - $module_slug = $request['slug']; - } else { - $module_slug = $request; - } - - if ( ! Jetpack::is_module( $module_slug ) ) { - return new WP_Error( - 'not_found', - esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - if ( ! Jetpack::is_module_active( $module_slug ) ) { - return new WP_Error( - 'already_inactive', - esc_html__( 'The requested Jetpack module was already inactive.', 'jetpack' ), - array( 'status' => 409 ) - ); - } - - if ( Jetpack::deactivate_module( $module_slug ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'The requested Jetpack module was deactivated.', 'jetpack' ), - ) ); - } - return new WP_Error( - 'deactivation_failed', - esc_html__( 'The requested Jetpack module could not be deactivated.', 'jetpack' ), - array( 'status' => 400 ) - ); - } - - /** - * Check that the current user has permissions to manage Jetpack modules. - * - * @since 4.3.0 - * - * @return bool - */ - public function can_request() { - return current_user_can( 'jetpack_manage_modules' ); - } -} - -class Jetpack_Core_API_Module_List_Endpoint { - - /** - * A WordPress REST API callback method that accepts a request object and decides what to do with it. - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @since 4.3.0 - * - * @return bool|Array|WP_Error a resulting value or object, or an error. - */ - public function process( $request ) { - if ( 'GET' === $request->get_method() ) { - return $this->get_modules( $request ); - } else { - return $this->activate_modules( $request ); - } - } - - /** - * Get a list of all Jetpack modules and their information. - * - * @since 4.3.0 - * - * @return array Array of Jetpack modules. - */ - public function get_modules() { - require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php' ); - - $modules = Jetpack_Admin::init()->get_modules(); - foreach ( $modules as $slug => $properties ) { - $modules[ $slug ]['options'] = - Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $slug ); - if ( - isset( $modules[ $slug ]['requires_connection'] ) - && $modules[ $slug ]['requires_connection'] - && Jetpack::is_development_mode() - ) { - $modules[ $slug ]['activated'] = false; - } - } - - $modules = Jetpack::get_translated_modules( $modules ); - - return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $modules ); - } - - /** - * Activate a list of valid Jetpack modules. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Module slug. - * } - * - * @return bool|WP_Error True if modules were activated. Otherwise, a WP_Error instance with the corresponding error. - */ - public static function activate_modules( $request ) { - - if ( - ! isset( $request['modules'] ) - || ! is_array( $request['modules'] ) - ) { - return new WP_Error( - 'not_found', - esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - $activated = array(); - $failed = array(); - - foreach ( $request['modules'] as $module ) { - if ( Jetpack::activate_module( $module, false, false ) ) { - $activated[] = $module; - } else { - $failed[] = $module; - } - } - - if ( empty( $failed ) ) { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'All modules activated.', 'jetpack' ), - ) ); - } - - $error = ''; - - $activated_count = count( $activated ); - if ( $activated_count > 0 ) { - $activated_last = array_pop( $activated ); - $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; - - $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 ) . ' '; - } - - $failed_count = count( $failed ); - if ( count( $failed ) > 0 ) { - $failed_last = array_pop( $failed ); - $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; - - $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 ) . ' '; - } - - return new WP_Error( - 'activation_failed', - esc_html( $error ), - array( 'status' => 424 ) - ); - } - - /** - * A WordPress REST API permission callback method that accepts a request object and decides - * if the current user has enough privileges to act. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool does the current user have enough privilege. - */ - public function can_request( $request ) { - if ( 'GET' === $request->get_method() ) { - return current_user_can( 'jetpack_admin_page' ); - } else { - return current_user_can( 'jetpack_manage_modules' ); - } - } -} - -/** - * Class that manages updating of Jetpack module options and general Jetpack settings or retrieving module data. - * If no module is specified, all module settings are retrieved/updated. - * - * @since 4.3.0 - * @since 4.4.0 Renamed Jetpack_Core_API_Module_Endpoint from to Jetpack_Core_API_Data. - * - * @author Automattic - */ -class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { - - /** - * Process request by returning the module or updating it. - * If no module is specified, settings for all modules are assumed. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request - * - * @return bool|mixed|void|WP_Error - */ - public function process( $request ) { - if ( 'GET' === $request->get_method() ) { - if ( isset( $request['slug'] ) ) { - return $this->get_module( $request ); - } - - return $this->get_all_options(); - } else { - return $this->update_data( $request ); - } - } - - /** - * Get information about a specific and valid Jetpack module. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Module slug. - * } - * - * @return mixed|void|WP_Error - */ - public function get_module( $request ) { - if ( Jetpack::is_module( $request['slug'] ) ) { - - $module = Jetpack::get_module( $request['slug'] ); - - $module['options'] = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $request['slug'] ); - - if ( - isset( $module['requires_connection'] ) - && $module['requires_connection'] - && Jetpack::is_development_mode() - ) { - $module['activated'] = false; - } - - $i18n = jetpack_get_module_i18n( $request['slug'] ); - if ( isset( $module['name'] ) ) { - $module['name'] = $i18n['name']; - } - if ( isset( $module['description'] ) ) { - $module['description'] = $i18n['description']; - $module['short_description'] = $i18n['description']; - } - - return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $module ); - } - - return new WP_Error( - 'not_found', - esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - /** - * Get information about all Jetpack module options and settings. - * - * @since 4.6.0 - * - * @return WP_REST_Response $response - */ - public function get_all_options() { - $response = array(); - - $modules = Jetpack::get_available_modules(); - if ( is_array( $modules ) && ! empty( $modules ) ) { - foreach ( $modules as $module ) { - // 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 - $response[ $module ] = Jetpack::is_module_active( $module ); - } - } - - $settings = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'settings' ); - - if ( ! function_exists( 'is_plugin_active' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - foreach ( $settings as $setting => $properties ) { - switch ( $setting ) { - case 'lang_id': - if ( defined( 'WPLANG' ) ) { - // We can't affect this setting, so warn the client - $response[ $setting ] = 'error_const'; - break; - } - - if ( ! current_user_can( 'install_languages' ) ) { - // The user doesn't have caps to install language packs, so warn the client - $response[ $setting ] = 'error_cap'; - break; - } - - $value = get_option( 'WPLANG' ); - $response[ $setting ] = empty( $value ) ? 'en_US' : $value; - break; - - case 'wordpress_api_key': - // When field is clear, return empty. Otherwise it would return "false". - if ( '' === get_option( 'wordpress_api_key', '' ) ) { - $response[ $setting ] = ''; - } else { - if ( ! class_exists( 'Akismet' ) ) { - if ( is_readable( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) { - require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php'; - } - } - $response[ $setting ] = class_exists( 'Akismet' ) ? Akismet::get_api_key() : ''; - } - break; - - case 'onboarding': - $business_address = get_option( 'jpo_business_address' ); - $business_address = is_array( $business_address ) ? array_map( array( $this, 'decode_special_characters' ), $business_address ) : $business_address; - - $response[ $setting ] = array( - 'siteTitle' => $this->decode_special_characters( get_option( 'blogname' ) ), - 'siteDescription' => $this->decode_special_characters( get_option( 'blogdescription' ) ), - 'siteType' => get_option( 'jpo_site_type' ), - 'homepageFormat' => get_option( 'jpo_homepage_format' ), - 'addContactForm' => intval( get_option( 'jpo_contact_page' ) ), - 'businessAddress' => $business_address, - 'installWooCommerce' => is_plugin_active( 'woocommerce/woocommerce.php' ), - 'stats' => Jetpack::is_active() && Jetpack::is_module_active( 'stats' ), - ); - break; - - default: - $response[ $setting ] = Jetpack_Core_Json_Api_Endpoints::cast_value( get_option( $setting ), $settings[ $setting ] ); - break; - } - } - - $response['akismet'] = is_plugin_active( 'akismet/akismet.php' ); - - return rest_ensure_response( $response ); - } - - /** - * Decode the special HTML characters in a certain value. - * - * @since 5.8 - * - * @param string $value Value to decode. - * - * @return string Value with decoded HTML characters. - */ - private function decode_special_characters( $value ) { - return (string) htmlspecialchars_decode( $value, ENT_QUOTES ); - } - - /** - * If it's a valid Jetpack module and configuration parameters have been sent, update it. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Module slug. - * } - * - * @return bool|WP_Error True if module was updated. Otherwise, a WP_Error instance with the corresponding error. - */ - 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'] ) ) { - - // 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. - $request['slug'] = 'any'; - } else { - if ( ! Jetpack::is_module( $request['slug'] ) ) { - return new WP_Error( 'not_found', esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), array( 'status' => 404 ) ); - } - - if ( ! Jetpack::is_module_active( $request['slug'] ) ) { - return new WP_Error( 'inactive', esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' ), array( 'status' => 409 ) ); - } - } - - // 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: - $params = $request->get_body_params(); - } - - // Exit if no parameters were passed. - if ( ! is_array( $params ) ) { - return new WP_Error( 'missing_options', esc_html__( 'Missing options.', 'jetpack' ), array( 'status' => 404 ) ); - } - - // If $params was set via `get_body_params()` there may be some additional variables in the request that can - // cause validation to fail. This method verifies that each param was in fact updated and will throw a `some_updated` - // error if unused variables are included in the request. - foreach ( array_keys( $params ) as $key ) { - if ( is_int( $key ) || 'slug' === $key || 'context' === $key ) { - unset( $params[ $key ] ); - } - } - - // Get available module options. - $options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'any' === $request['slug'] - ? $params - : $request['slug'] - ); - - // 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 ) ); - $not_updated = array(); - - // 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', - 'message' => esc_html__( 'The requested Jetpack data updates were successful.', 'jetpack' ), - ); - - // If there are modules to activate, activate them first so they're ready when their options are set. - foreach ( $params as $option => $value ) { - if ( 'modules' === $options[ $option ]['jp_group'] ) { - - // Used if there was an error. Can be overwritten with specific error messages. - $error = ''; - - // Set to true if the module toggling was successful. - $updated = false; - - // Check if user can toggle the module. - if ( $toggle_module->can_request() ) { - - // Activate or deactivate the module according to the value passed. - $toggle_result = $value - ? $toggle_module->activate_module( $option ) - : $toggle_module->deactivate_module( $option ); - - if ( - is_wp_error( $toggle_result ) - && 'already_inactive' === $toggle_result->get_error_code() - ) { - - // If the module is already inactive, we don't fail - $updated = true; - } elseif ( is_wp_error( $toggle_result ) ) { - $error = $toggle_result->get_error_message(); - } else { - $updated = true; - } - } else { - $error = Jetpack_Core_Json_Api_Endpoints::$user_permissions_error_msg; - } - - // The module was not toggled. - if ( ! $updated ) { - $not_updated[ $option ] = $error; - } - - // Remove module from list so we don't go through it again. - unset( $params[ $option ] ); - } - } - - foreach ( $params as $option => $value ) { - - // Used if there was an error. Can be overwritten with specific error messages. - $error = ''; - - // Set to true if the option update was successful. - $updated = false; - - // Get option attributes, including the group it belongs to. - $option_attrs = $options[ $option ]; - - // If this is a module option and the related module isn't active for any reason, continue with the next one. - if ( 'settings' !== $option_attrs['jp_group'] ) { - if ( ! Jetpack::is_module( $option_attrs['jp_group'] ) ) { - $not_updated[ $option ] = esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ); - continue; - } - - if ( - 'any' !== $request['slug'] - && ! Jetpack::is_module_active( $option_attrs['jp_group'] ) - ) { - - // We only take note of skipped options when updating one module - $not_updated[ $option ] = esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' ); - continue; - } - } - - // Properly cast value based on its type defined in endpoint accepted args. - $value = Jetpack_Core_Json_Api_Endpoints::cast_value( $value, $option_attrs ); - - switch ( $option ) { - case 'lang_id': - if ( defined( 'WPLANG' ) || ! current_user_can( 'install_languages' ) ) { - // We can't affect this setting - $updated = false; - break; - } - - if ( $value === 'en_US' || empty( $value ) ) { - return delete_option( 'WPLANG' ); - } - - if ( ! function_exists( 'request_filesystem_credentials' ) ) { - 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 - $language = wp_download_language_pack( $value ); - if ( $language === false ) { - // The language pack download failed. - $updated = false; - break; - } - $updated = get_option( 'WPLANG' ) === $language ? true : update_option( 'WPLANG', $language ); - break; - - case 'monitor_receive_notifications': - $monitor = new Jetpack_Monitor(); - - // If we got true as response, consider it done. - $updated = true === $monitor->update_option_receive_jetpack_monitor_notification( $value ); - break; - - case 'post_by_email_address': - if ( 'create' == $value ) { - $result = $this->_process_post_by_email( - 'jetpack.createPostByEmailAddress', - esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' ) - ); - } elseif ( 'regenerate' == $value ) { - $result = $this->_process_post_by_email( - 'jetpack.regeneratePostByEmailAddress', - esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' ) - ); - } elseif ( 'delete' == $value ) { - $result = $this->_process_post_by_email( - 'jetpack.deletePostByEmailAddress', - esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' ) - ); - } else { - $result = false; - } - - // 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 ) { - $updated = true; - } elseif ( is_array( $result ) && isset( $result['message'] ) ) { - $error = $result['message']; - } - break; - - case 'jetpack_protect_key': - $protect = Jetpack_Protect_Module::instance(); - if ( 'create' == $value ) { - $result = $protect->get_protect_key(); - } else { - $result = false; - } - - // If we got one of Protect keys, consider it done. - if ( preg_match( '/[a-z0-9]{40,}/i', $result ) ) { - $response[$option] = $result; - $updated = true; - } - break; - - case 'jetpack_protect_global_whitelist': - $updated = jetpack_protect_save_whitelist( explode( PHP_EOL, str_replace( array( ' ', ',' ), array( '', "\n" ), $value ) ) ); - if ( is_wp_error( $updated ) ) { - $error = $updated->get_error_message(); - } - break; - - case 'show_headline': - case 'show_thumbnails': - $grouped_options = $grouped_options_current = (array) Jetpack_Options::get_option( 'relatedposts' ); - $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; - break; - - case 'google': - case 'bing': - case 'pinterest': - case 'yandex': - $grouped_options = $grouped_options_current = (array) get_option( 'verification_services_codes' ); - - // 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 { - $grouped_options[ $option ] = $value; - } - - // If option value was the same, consider it done. - $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' ) ) { - 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; - break; - - case 'button_style': - case 'sharing_label': - case 'show': - 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(); - $grouped_options[ $option ] = $value; - $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' ) ) { - break; - } - - $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; - break; - - case 'sharing_delete_service': - if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) { - break; - } - - $sharer = new Sharing_Service(); - $updated = $sharer->delete_service( $value ); - break; - - case 'jetpack-twitter-cards-site-tag': - $value = trim( ltrim( strip_tags( $value ), '@' ) ); - $updated = get_option( $option ) !== $value ? update_option( $option, $value ) : true; - break; - - case 'onpublish': - case 'onupdate': - case 'Bias Language': - case 'Cliches': - case 'Complex Expression': - case 'Diacritical Marks': - case 'Double Negative': - case 'Hidden Verbs': - case 'Jargon Language': - case 'Passive voice': - case 'Phrases to Avoid': - case 'Redundant Expression': - case 'guess_lang': - if ( in_array( $option, array( 'onpublish', 'onupdate' ) ) ) { - $atd_option = 'AtD_check_when'; - } elseif ( 'guess_lang' == $option ) { - $atd_option = 'AtD_guess_lang'; - $option = 'true'; - } else { - $atd_option = 'AtD_options'; - } - $user_id = get_current_user_id(); - if ( ! function_exists( 'AtD_get_options' ) ) { - include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' ); - } - $grouped_options_current = AtD_get_options( $user_id, $atd_option ); - unset( $grouped_options_current['name'] ); - $grouped_options = $grouped_options_current; - if ( $value && ! isset( $grouped_options [$option] ) ) { - $grouped_options [$option] = $value; - } elseif ( ! $value && isset( $grouped_options [$option] ) ) { - unset( $grouped_options [$option] ); - } - // If option value was the same, consider it done, otherwise try to update it. - $options_to_save = implode( ',', array_keys( $grouped_options ) ); - $updated = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $options_to_save ) : true; - break; - - case 'ignored_phrases': - case 'unignore_phrase': - $user_id = get_current_user_id(); - $atd_option = 'AtD_ignored_phrases'; - $grouped_options = $grouped_options_current = explode( ',', AtD_get_setting( $user_id, $atd_option ) ); - if ( 'ignored_phrases' == $option ) { - $grouped_options = explode( ',', $value ); - } else { - $index = array_search( $value, $grouped_options ); - if ( false !== $index ) { - unset( $grouped_options[$index] ); - $grouped_options = array_values( $grouped_options ); - } - } - $ignored_phrases = implode( ',', array_filter( array_map( 'strip_tags', $grouped_options ) ) ); - $updated = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $ignored_phrases ) : true; - break; - - case 'admin_bar': - case 'roles': - case 'count_roles': - case 'blog_id': - case 'do_not_track': - case 'hide_smile': - case 'version': - $grouped_options = $grouped_options_current = (array) get_option( 'stats_options' ); - $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; - 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; - break; - - case 'wordpress_api_key': - - if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) { - $error = esc_html__( 'Please install Akismet.', 'jetpack' ); - $updated = false; - break; - } - - if ( ! defined( 'AKISMET_VERSION' ) ) { - $error = esc_html__( 'Please activate Akismet.', 'jetpack' ); - $updated = false; - break; - } - - // Allow to clear the API key field - if ( '' === $value ) { - $updated = get_option( $option ) != $value ? update_option( $option, $value ) : true; - break; - } - - require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php'; - require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php'; - - if ( class_exists( 'Akismet_Admin' ) && method_exists( 'Akismet_Admin', 'save_key' ) ) { - 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; - break; - } else { - $error = esc_html__( "Akismet user status doesn't allow to update the key", 'jetpack' ); - } - } else { - $error = esc_html__( 'Invalid Akismet user', 'jetpack' ); - } - } else { - $error = esc_html__( 'Invalid Akismet key', 'jetpack' ); - } - } else { - $error = esc_html__( 'Akismet is not installed or active', 'jetpack' ); - } - $updated = false; - break; - - case 'google_analytics_tracking_id': - $grouped_options = $grouped_options_current = (array) get_option( 'jetpack_wga' ); - $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; - 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; - break; - - case 'onboarding': - jetpack_require_lib( 'widgets' ); - // Break apart and set Jetpack onboarding options. - $result = $this->_process_onboarding( (array) $value ); - if ( empty( $result ) ) { - $updated = true; - } else { - $error = sprintf( esc_html__( 'Onboarding failed to process: %s', 'jetpack' ), $result ); - $updated = false; - } - break; - - case 'show_welcome_for_new_plan': - // If option value was the same, consider it done. - $updated = get_option( $option ) !== $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; - break; - } - - // The option was not updated. - if ( ! $updated ) { - $not_updated[ $option ] = $error; - } - } - - if ( empty( $invalid ) && empty( $not_updated ) ) { - // The option was updated. - return rest_ensure_response( $response ); - } else { - $invalid_count = count( $invalid ); - $not_updated_count = count( $not_updated ); - $error = ''; - if ( $invalid_count > 0 ) { - $error = sprintf( - /* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */ - _n( 'Invalid option: %s.', 'Invalid options: %s.', $invalid_count, 'jetpack' ), - join( ', ', $invalid ) - ); - } - if ( $not_updated_count > 0 ) { - $not_updated_messages = array(); - foreach ( $not_updated as $not_updated_option => $not_updated_message ) { - if ( ! empty( $not_updated_message ) ) { - $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 ); - } - } - if ( ! empty( $error ) ) { - $error .= ' '; - } - 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 ) ); - } - - } - - /** - * Perform tasks in the site based on onboarding choices. - * - * @since 5.4.0 - * - * @param array $data Onboarding choices made by user. - * - * @return string Result of onboarding processing and, if there is one, an error message. - */ - private function _process_onboarding( $data ) { - if ( isset( $data['end'] ) && $data['end'] ) { - return Jetpack::invalidate_onboarding_token() - ? '' - : esc_html__( "The onboarding token couldn't be deleted.", 'jetpack' ); - } - - $error = array(); - - if ( ! empty( $data['siteTitle'] ) ) { - // If option value was the same, consider it done. - 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'] ) ) { - $error[] = 'siteDescription'; - } - } - - $site_title = get_option( 'blogname' ); - $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'] ) ) { - $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 - $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' ) ) { - $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 ) { - $error[] = 'home insert: 0'; - } elseif ( is_wp_error( $home ) ) { - $error[] = 'home creation: '. $home->get_error_message(); - } - 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 ) { - $error[] = 'blog insert: 0'; - } elseif ( is_wp_error( $blog ) ) { - $error[] = 'blog creation: '. $blog->get_error_message(); - } - if ( ! ( update_option( 'page_for_posts', $blog ) || get_option( 'page_for_posts' ) == $blog ) ) { - $error[] = 'blog set'; - } - } else { - $front_page = get_option( 'page_on_front' ); - $posts_page = get_option( 'page_for_posts' ); - if ( $posts_page && get_post( $posts_page ) ) { - wp_delete_post( $posts_page ); - } - if ( $front_page && get_post( $front_page ) ) { - wp_delete_post( $front_page ); - } - update_option( 'show_on_front', 'posts' ); - } - } - update_option( 'jpo_homepage_format', $data['homepageFormat'] ); - } - - // 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' ); - if ( ! $contact_form_module_active ) { - $contact_form_module_active = Jetpack::activate_module( 'contact-form', false, false ); - } - - if ( $contact_form_module_active ) { - $contact_page = '[contact-form][contact-field label="' . esc_html__( 'Name', 'jetpack' ) . '" type="name" required="true" /][contact-field label="' . esc_html__( 'Email', 'jetpack' ) . '" type="email" required="true" /][contact-field label="' . esc_html__( 'Website', 'jetpack' ) . '" type="url" /][contact-field label="' . esc_html__( 'Message', 'jetpack' ) . '" type="textarea" /][/contact-form]'; - } else { - $error[] = 'contact-form activate'; - } - } - - if ( isset( $data['businessPersonal'] ) && 'business' === $data['businessPersonal'] ) { - $contact_page .= "\n" . join( "\n", $data['businessInfo'] ); - } - - 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 ) { - $error[] = 'form insert: 0'; - } elseif ( is_wp_error( $form ) ) { - $error[] = 'form creation: '. $form->get_error_message(); - } else { - update_option( 'jpo_contact_page', $form ); - } - } - - if ( isset( $data['businessAddress'] ) ) { - $handled_business_address = self::handle_business_address( $data['businessAddress'] ); - if ( is_wp_error( $handled_business_address ) ) { - $error[] = 'BusinessAddress'; - } - } - - 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 - if ( is_wp_error( $wc_install_result ) ) { - $error[] = 'woocommerce installation'; - } - } - - if ( ! empty( $data['stats'] ) ) { - if ( Jetpack::is_active() ) { - $stats_module_active = Jetpack::is_module_active( 'stats' ); - if ( ! $stats_module_active ) { - $stats_module_active = Jetpack::activate_module( 'stats', false, false ); - } - - if ( ! $stats_module_active ) { - $error[] = 'stats activate'; - } - } else { - $error[] = 'stats not connected'; - } - } - - return empty( $error ) - ? '' - : join( ', ', $error ); - } - - /** - * Add or update Business Address widget. - * - * @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 ) { - $first_sidebar = Jetpack_Widgets::get_first_sidebar(); - - $widgets_module_active = Jetpack::is_module_active( 'widgets' ); - if ( ! $widgets_module_active ) { - $widgets_module_active = Jetpack::activate_module( 'widgets', false, false ); - } - if ( ! $widgets_module_active ) { - return new WP_Error( 'module_activation_failed', 'Failed to activate the widgets module.', 400 ); - } - - 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'] ) : ''; - $country = isset( $address['country'] ) ? sanitize_text_field( $address['country'] ) : ''; - - $full_address = implode( ' ', array_filter( array( $street, $city, $state, $zip, $country ) ) ); - - $widget_options = array( - 'title' => $title, - 'address' => $full_address, - 'phone' => '', - 'hours' => '', - 'showmap' => false, - '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 ); - } else { - $widget_updated = Jetpack_Widgets::update_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar ); - } - if ( is_wp_error( $widget_updated ) ) { - return new WP_Error( 'widget_update_failed', 'Widget could not be updated.', 400 ); - } - - $address_save = array( - '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 - 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. - * - * @return bool Whether the widget is present in a given sidebar. - */ - static function has_business_address_widget( $sidebar ) { - $sidebars_widgets = get_option( 'sidebars_widgets', array() ); - if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) { - return false; - } - foreach ( $sidebars_widgets[ $sidebar ] as $widget ) { - if ( strpos( $widget, 'widget_contact_info' ) !== false ) { - return true; - } - } - return false; - } - - /** - * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address. - * @todo: When all settings are updated to use endpoints, move this to the Post by Email module and replace __process_ajax_proxy_request. - * - * @since 4.3.0 - * - * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address. - * @param string $error Error message to return. - * - * @return array - */ - private function _process_post_by_email( $endpoint, $error ) { - if ( ! current_user_can( 'edit_posts' ) ) { - return array( 'message' => $error ); - } - - $this->xmlrpc->query( $endpoint ); - - if ( $this->xmlrpc->isError() ) { - return array( 'message' => $error ); - } - - $response = $this->xmlrpc->getResponse(); - if ( empty( $response ) ) { - return array( 'message' => $error ); - } - - // Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. - update_option( 'post_by_email_address' . get_current_user_id(), $response ); - - return $response; - } - - /** - * Check if user is allowed to perform the update. - * - * @since 4.3.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return bool - */ - public function can_request( $request ) { - $req_params = $request->get_params(); - if ( ! empty( $req_params['onboarding']['token'] ) && isset( $req_params['rest_route'] ) ) { - return Jetpack::validate_onboarding_token_action( $req_params['onboarding']['token'], $req_params['rest_route'] ); - } - - if ( 'GET' === $request->get_method() ) { - return current_user_can( 'jetpack_admin_page' ); - } else { - $module = Jetpack_Core_Json_Api_Endpoints::get_module_requested(); - if ( empty( $module ) ) { - $params = $request->get_json_params(); - if ( ! is_array( $params ) ) { - $params = $request->get_body_params(); - } - $options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( $params ); - foreach ( $options as $option => $definition ) { - if ( in_array( $options[ $option ]['jp_group'], array( 'after-the-deadline', 'post-by-email' ) ) ) { - $module = $options[ $option ]['jp_group']; - break; - } - } - } - // User is trying to create, regenerate or delete its PbE || ATD settings. - if ( 'post-by-email' === $module || 'after-the-deadline' === $module ) { - return current_user_can( 'edit_posts' ) && current_user_can( 'jetpack_admin_page' ); - } - return current_user_can( 'jetpack_configure_modules' ); - } - } -} - -class Jetpack_Core_API_Module_Data_Endpoint { - - public function process( $request ) { - switch( $request['slug'] ) { - case 'protect': - return $this->get_protect_data(); - case 'stats': - return $this->get_stats_data( $request ); - case 'akismet': - return $this->get_akismet_data(); - case 'monitor': - return $this->get_monitor_data(); - case 'verification-tools': - return $this->get_verification_tools_data(); - case 'vaultpress': - return $this->get_vaultpress_data(); - } - } - - /** - * Decide against which service to check the key. - * - * @since 4.8.0 - * - * @param WP_REST_Request $request - * - * @return bool - */ - public function key_check( $request ) { - switch( $request['service'] ) { - case 'akismet': - $params = $request->get_json_params(); - if ( isset( $params['api_key'] ) && ! empty( $params['api_key'] ) ) { - return $this->check_akismet_key( $params['api_key'] ); - } - return $this->check_akismet_key(); - } - return false; - } - - /** - * Get number of blocked intrusion attempts. - * - * @since 4.3.0 - * - * @return mixed|WP_Error Number of blocked attempts if protection is enabled. Otherwise, a WP_Error instance with the corresponding error. - */ - public function get_protect_data() { - if ( Jetpack::is_module_active( 'protect' ) ) { - return get_site_option( 'jetpack_protect_blocked_attempts' ); - } - - return new WP_Error( - 'not_active', - esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - /** - * Get number of spam messages blocked by Akismet. - * - * @since 4.3.0 - * - * @return int|string Number of spam blocked by Akismet. Otherwise, an error message. - */ - public function get_akismet_data() { - if ( ! is_wp_error( $status = $this->akismet_is_active_and_registered() ) ) { - return rest_ensure_response( Akismet_Admin::get_stats( Akismet::get_api_key() ) ); - } else { - return $status->get_error_code(); - } - } - - /** - * Verify the Akismet API key. - * - * @since 4.8.0 - * - * @param string $api_key Optional API key to check. - * - * @return array Information about the key. 'validKey' is true if key is valid, false otherwise. - */ - 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(), - ) ); - } - - $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' => isset( $key_status[1] ) && 'valid' === $key_status[1] - ) ); - } - - /** - * Check if Akismet class file exists and if class is loaded. - * - * @since 4.8.0 - * - * @return bool|WP_Error Returns true if class file exists and class is loaded, WP_Error otherwise. - */ - private function akismet_class_exists() { - if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) { - return new WP_Error( 'not_installed', esc_html__( 'Please install Akismet.', 'jetpack' ), array( 'status' => 400 ) ); - } - - if ( ! class_exists( 'Akismet' ) ) { - return new WP_Error( 'not_active', esc_html__( 'Please activate Akismet.', 'jetpack' ), array( 'status' => 400 ) ); - } - - return true; - } - - /** - * Is Akismet registered and active? - * - * @since 4.3.0 - * - * @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() ) ) { - return $akismet_exists; - } - - // What about if Akismet is put in a sub-directory or maybe in mu-plugins? - require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php'; - require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php'; - $akismet_key = Akismet::verify_key( Akismet::get_api_key() ); - - if ( ! $akismet_key || 'invalid' === $akismet_key || 'failed' === $akismet_key ) { - return new WP_Error( 'invalid_key', esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), array( 'status' => 400 ) ); - } - - return true; - } - - /** - * Get stats data for this site - * - * @since 4.1.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $date Date range to restrict results to. - * } - * - * @return WP_Error|WP_HTTP_Response|WP_REST_Response Stats information relayed from WordPress.com. - */ - public function get_stats_data( WP_REST_Request $request ) { - // Get parameters to fetch Stats data. - $range = $request->get_param( 'range' ); - - // If no parameters were passed. - if ( - 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' ); - } - - switch ( $range ) { - - // 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(), - ) ); - case 'week': - 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&' ), - ) ); - } - } - - /** - * Get date of last downtime. - * - * @since 4.3.0 - * - * @return mixed|WP_Error Number of days since last downtime. Otherwise, a WP_Error instance with the corresponding error. - */ - public function get_monitor_data() { - if ( ! Jetpack::is_module_active( 'monitor' ) ) { - return new WP_Error( - 'not_active', - esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - $monitor = new Jetpack_Monitor(); - $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, - ) ); - } else { - return rest_ensure_response( array( - 'code' => 'success', - 'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ), - ) ); - } - } - - /** - * Get services that this site is verified with. - * - * @since 4.3.0 - * - * @return mixed|WP_Error List of services that verified this site. Otherwise, a WP_Error instance with the corresponding error. - */ - public function get_verification_tools_data() { - if ( ! Jetpack::is_module_active( 'verification-tools' ) ) { - return new WP_Error( - 'not_active', - esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - $verification_services_codes = get_option( 'verification_services_codes' ); - if ( - ! is_array( $verification_services_codes ) - || empty( $verification_services_codes ) - ) { - return new WP_Error( - 'empty', - esc_html__( 'Site not verified with any service.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - $services = array(); - foreach ( jetpack_verification_services() as $name => $service ) { - if ( is_array( $service ) && ! empty( $verification_services_codes[ $name ] ) ) { - switch ( $name ) { - case 'google': - $services[] = 'Google'; - break; - case 'bing': - $services[] = 'Bing'; - break; - case 'pinterest': - $services[] = 'Pinterest'; - break; - case 'yandex': - $services[] = 'Yandex'; - break; - } - } - } - - if ( empty( $services ) ) { - return new WP_Error( - 'empty', - esc_html__( 'Site not verified with any service.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - if ( 2 > count( $services ) ) { - $message = esc_html( - sprintf( - /* translators: %s is a service name like Google, Bing, Pinterest, etc. */ - __( 'Your site is verified with %s.', 'jetpack' ), - $services[0] - ) - ); - } else { - $copy_services = $services; - $last = count( $copy_services ) - 1; - $last_service = $copy_services[ $last ]; - unset( $copy_services[ $last ] ); - $message = esc_html( - sprintf( - /* translators: %1$s is a comma separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */ - __( 'Your site is verified with %1$s and %2$s.', 'jetpack' ), - join( ', ', $copy_services ), - $last_service - ) - ); - } - - return rest_ensure_response( array( - 'code' => 'success', - 'message' => $message, - 'services' => $services, - ) ); - } - - /** - * Get VaultPress site data including, among other things, the date of the last backup if it was completed. - * - * @since 4.3.0 - * - * @return mixed|WP_Error VaultPress site data. Otherwise, a WP_Error instance with the corresponding error. - */ - public function get_vaultpress_data() { - if ( ! class_exists( 'VaultPress' ) ) { - return new WP_Error( - 'not_active', - esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - $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 $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, - ) ); - } else { - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html( - sprintf( - __( 'Your site was successfully backed-up %s ago.', 'jetpack' ), - human_time_diff( - $data->backups->last_backup, - current_time( 'timestamp' ) - ) - ) - ), - 'data' => $data, - ) ); - } - } - - /** - * A WordPress REST API permission callback method that accepts a request object and - * decides if the current user has enough privileges to act. - * - * @since 4.3.0 - * - * @return bool does a current user have enough privileges. - */ - public function can_request() { - return current_user_can( 'jetpack_admin_page' ); - } -} - -/** - * Actions performed only when Gravatar Hovercards is activated through the endpoint call. - * - * @since 4.3.1 - */ -function jetpack_do_after_gravatar_hovercards_activation() { - - // When Gravatar Hovercards is activated, enable them automatically. - update_option( 'gravatar_disable_hovercards', 'enabled' ); -} -add_action( 'jetpack_activate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_activation' ); - -/** - * Actions performed only when Gravatar Hovercards is activated through the endpoint call. - * - * @since 4.3.1 - */ -function jetpack_do_after_gravatar_hovercards_deactivation() { - - // When Gravatar Hovercards is deactivated, disable them automatically. - update_option( 'gravatar_disable_hovercards', 'disabled' ); -} -add_action( 'jetpack_deactivate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_deactivation' ); - -/** - * Actions performed only when Markdown is activated through the endpoint call. - * - * @since 4.7.0 - */ -function jetpack_do_after_markdown_activation() { - - // When Markdown is activated, enable support for post editing automatically. - update_option( 'wpcom_publish_posts_with_markdown', true ); -} -add_action( 'jetpack_activate_module_markdown', 'jetpack_do_after_markdown_activation' ); 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 deleted file mode 100644 index 68327f51..00000000 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * This is the endpoint class for `/site` endpoints. - * - */ -class Jetpack_Core_API_Site_Endpoint { - - /** - * Returns the result of `/sites/%s/features` endpoint call. - * @return object $features has 'active' and 'available' properties each of which contain feature slugs. - * 'active' is a simple array of slugs that are active on the current plan. - * 'available' is an object with keys that represent feature slugs and values are arrays - * of plan slugs that enable these features - */ - public static function get_features() { - - // Make the API request - $request = sprintf( '/sites/%d/features', Jetpack_Options::get_option( 'id' ) ); - $response = Jetpack_Client::wpcom_json_api_request_as_blog( $request, '1.1' ); - - // Bail if there was an error or malformed response - if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) { - return new WP_Error( - 'failed_to_fetch_data', - esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - // Decode the results - $results = json_decode( $response['body'], true ); - - // Bail if there were no results or plan details returned - if ( ! is_array( $results ) ) { - return new WP_Error( - 'failed_to_fetch_data', - esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), - array( 'status' => 500 ) - ); - } - - return rest_ensure_response( array( - 'code' => 'success', - 'message' => esc_html__( 'Site features correctly received.', 'jetpack' ), - 'data' => wp_remote_retrieve_body( $response ), - ) - ); - } - - /** - * Check that the current user has permissions to request information about this site. - * - * @since 5.1.0 - * - * @return bool - */ - public static function can_request() { - return current_user_can( 'jetpack_manage_modules' ); - } -} 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 deleted file mode 100644 index ffd62bb3..00000000 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -/** - * Widget information getter endpoint. - * - */ -class Jetpack_Core_API_Widget_Endpoint { - - /** - * @since 5.5.0 - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $id Widget id. - * } - * - * @return WP_REST_Response|WP_Error A REST response if the request was served successfully, otherwise an error. - */ - public function process( $request ) { - $widget_base = _get_widget_id_base( $request['id'] ); - $widget_id = (int) substr( $request['id'], strlen( $widget_base ) + 1 ); - - switch( $widget_base ) { - case 'milestone_widget': - $instances = get_option( 'widget_milestone_widget', array() ); - - if ( - class_exists( 'Milestone_Widget' ) - && is_active_widget( false, $widget_base . '-' . $widget_id, $widget_base ) - && isset( $instances[ $widget_id ] ) - ) { - $instance = $instances[ $widget_id ]; - $widget = new Milestone_Widget(); - return $widget->get_widget_data( $instance ); - } - } - - return new WP_Error( - 'not_found', - esc_html__( 'The requested widget was not found.', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - /** - * Check that the current user has permissions to view widget information. - * For the currently supported widget there are no permissions required. - * - * @since 5.5.0 - * - * @return bool - */ - public function can_request() { - return true; - } -} 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 deleted file mode 100644 index abfc8627..00000000 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php -/** - * This is the base class for every Core API endpoint that needs an XMLRPC client. - * - */ -abstract class Jetpack_Core_API_XMLRPC_Consumer_Endpoint { - - /** - * An instance of the Jetpack XMLRPC client to make WordPress.com requests - * - * @private - * @var Jetpack_IXR_Client - */ - protected $xmlrpc; - - /** - * - * @since 4.3.0 - * - * @param Jetpack_IXR_Client $xmlrpc - */ - public function __construct( $xmlrpc = null ) { - $this->xmlrpc = $xmlrpc; - } - - /** - * Checks if the site is public and returns the result. - * - * @since 4.3.0 - * - * @return Boolean $is_public - */ - protected function is_site_public() { - if ( $this->xmlrpc->query( 'jetpack.isSitePubliclyAccessible', home_url() ) ) { - return $this->xmlrpc->getResponse(); - } - 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 deleted file mode 100644 index 2b26f78c..00000000 --- a/plugins/jetpack/_inc/lib/core-api/load-wpcom-endpoints.php +++ /dev/null @@ -1,40 +0,0 @@ -<?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/ - */ - -function wpcom_rest_api_v2_load_plugin_files( $file_pattern ) { - $plugins = glob( dirname( __FILE__ ) . '/' . $file_pattern ); - - if ( ! is_array( $plugins ) ) { - return; - } - - foreach ( array_filter( $plugins, 'is_file' ) as $plugin ) { - require_once $plugin; - } -} - -// API v2 plugins: define a class, then call this function. -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(); - } - - if ( ! isset( $wpcom_rest_api_v2_plugins[ $class_name ] ) ) { - $wpcom_rest_api_v2_plugins[ $class_name ] = new $class_name; - } -} - -require dirname( __FILE__ ) . '/class-wpcom-rest-field-controller.php'; - -// Now load the endpoint files. -wpcom_rest_api_v2_load_plugin_files( 'wpcom-endpoints/*.php' ); -wpcom_rest_api_v2_load_plugin_files( 'wpcom-fields/*.php' ); 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 deleted file mode 100644 index 2bf80939..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/business-hours.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php - -/** - * Business Hours: Localized week - * - * @since 7.1 - */ -class WPCOM_REST_API_V2_Endpoint_Business_Hours extends WP_REST_Controller { - 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' ) ); - } - - 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( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_localized_week' ), - ) - ) ); - } - - /** - * Retreives localized business hours - * - * @return array data object containing information about business hours - */ - public function get_localized_week() { - global $wp_locale; - - return array( - 'days' => array( - 'Sun' => $wp_locale->get_weekday( 0 ), - 'Mon' => $wp_locale->get_weekday( 1 ), - 'Tue' => $wp_locale->get_weekday( 2 ), - 'Wed' => $wp_locale->get_weekday( 3 ), - 'Thu' => $wp_locale->get_weekday( 4 ), - 'Fri' => $wp_locale->get_weekday( 5 ), - 'Sat' => $wp_locale->get_weekday( 6 ), - ), - 'startOfWeek' => (int) get_option( 'start_of_week', 0 ), - ); - } -} - -wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Business_Hours' ); 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 deleted file mode 100644 index 09ef9499..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php - -/** - * Mailchimp: Get Mailchimp Status. - * API to determine if current site has linked Mailchimp account and mailing list selected. - * This API is meant to be used in Jetpack and on WPCOM. - * - * @since 7.1 - */ -class WPCOM_REST_API_V2_Endpoint_Mailchimp extends WP_REST_Controller { - public function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'mailchimp'; - $this->wpcom_is_wpcom_only_endpoint = true; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - /** - * Called automatically on `rest_api_init()`. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_mailchimp_status' ), - ), - ) - ); - } - - /** - * Check if MailChimp is set up properly. - * - * @return bool - */ - private function is_connected() { - $option = get_option( 'jetpack_mailchimp' ); - if ( ! $option ) { - return false; - } - $data = json_decode( $option, true ); - if ( ! $data ) { - return false; - } - return isset( $data['follower_list_id'], $data['keyring_id'] ); - } - - /** - * Get the status of current blog's Mailchimp connection - * - * @return mixed - * code:string (connected|unconnected), - * connect_url:string - * site_id:int - */ - public function get_mailchimp_status() { - $is_wpcom = ( defined( 'IS_WPCOM' ) && IS_WPCOM ); - $site_id = $is_wpcom ? get_current_blog_id() : Jetpack_Options::get_option( 'id' ); - if ( ! $site_id ) { - return new WP_Error( - 'unavailable_site_id', - __( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack' ), - 403 - ); - } - $connect_url = sprintf( 'https://wordpress.com/sharing/%s', rawurlencode( $site_id ) ); - return array( - 'code' => $this->is_connected() ? 'connected' : 'not_connected', - 'connect_url' => $connect_url, - 'site_id' => $site_id, - ); - } -} - -wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Mailchimp' ); 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 deleted file mode 100644 index a10a4056..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php +++ /dev/null @@ -1,71 +0,0 @@ -<?php - -/* - * Gutenberg: List Available Gutenberg Extensions (Blocks and Plugins) - * - * [ - * { # Availabilty Object. See schema for more detail. - * available: (boolean) Whether the extension is available - * unavailable_reason: (string) Reason for the extension not being available - * }, - * ... - * ] - * - * @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'; - $this->wpcom_is_site_specific_endpoint = true; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - public function register_routes() { - register_rest_route( $this->namespace, $this->rest_base . '/available-extensions', array( - 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' ), - ) ); - } - - /** - * Return the available Gutenberg extensions schema - * - * @return array Available Gutenberg extensions schema - */ - public function get_public_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'gutenberg-available-extensions', - 'type' => 'object', - 'properties' => array( - 'available' => array( - 'description' => __( 'Whether the extension is available', 'jetpack' ), - 'type' => 'boolean', - ), - 'unavailable_reason' => array( - 'description' => __( 'Reason for the extension not being available', 'jetpack' ), - 'type' => 'string', - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Ensure the user has proper permissions - * - * @return boolean - */ - public function get_items_permission_check() { - return current_user_can( 'edit_posts' ); - } -} - -wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php deleted file mode 100644 index a05769b2..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/hello.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php - -class WPCOM_REST_API_V2_Endpoint_Hello { - public function __construct() { - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - public function register_routes() { - register_rest_route( 'wpcom/v2', '/hello', array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_data' ), - ), - ) ); - } - - public function get_data( $request ) { - 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/publicize-connection-test-results.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php deleted file mode 100644 index 6e04a289..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ /dev/null @@ -1,121 +0,0 @@ -<?php - -require_once dirname( __FILE__ ) . '/publicize-connections.php'; - -/** - * Publicize: List Connection Test Result Data - * - * All the same data as the Publicize Connections Endpoint, plus test results. - * - * @since 6.8 - */ -class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections { - public function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'publicize/connection-test-results'; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - /** - * Called automatically on `rest_api_init()`. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permission_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Adds the test results properties to the Connection schema. - * - * @return array - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'jetpack-publicize-connection-test-results', - 'type' => 'object', - 'properties' => $this->get_connection_schema_properties() + array( - 'test_success' => array( - 'description' => __( 'Did the Publicize Connection test pass?', 'jetpack' ), - 'type' => 'boolean', - ), - 'test_message' => array( - 'description' => __( 'Publicize Connection success or error message', 'jetpack' ), - 'type' => 'string', - ), - 'can_refresh' => array( - 'description' => __( 'Can the current user refresh the Publicize Connection?', 'jetpack' ), - 'type' => 'boolean', - ), - 'refresh_text' => array( - 'description' => __( 'Message instructing the user to refresh their Connection to the Publicize Service', 'jetpack' ), - 'type' => 'string', - ), - 'refresh_url' => array( - 'description' => __( 'URL for refreshing the Connection to the Publicize Service', 'jetpack' ), - 'type' => 'string', - 'format' => 'uri', - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * @param WP_REST_Request - * @see Publicize::get_publicize_conns_test_results() - * @return WP_REST_Response suitable for 1-page collection - */ - public function get_items( $request ) { - global $publicize; - - $items = $this->get_connections(); - - $test_results = $publicize->get_publicize_conns_test_results(); - $test_results_by_unique_id = array(); - foreach ( $test_results as $test_result ) { - $test_results_by_unique_id[ $test_result['unique_id'] ] = $test_result; - } - - $mapping = array( - 'test_success' => 'connectionTestPassed', - 'test_message' => 'connectionTestMessage', - 'can_refresh' => 'userCanRefresh', - 'refresh_text' => 'refreshText', - 'refresh_url' => 'refreshURL', - ); - - foreach ( $items as &$item ) { - $test_result = $test_results_by_unique_id[ $item['id'] ]; - - foreach ( $mapping as $field => $test_result_field ) { - $item[ $field ] = $test_result[ $test_result_field ]; - } - } - - if ( 'linkedin' === $item['id'] && 'must_reauth' === $test_result['connectionTestPassed'] ) { - $item['test_success'] = 'must_reauth'; - } - - $response = rest_ensure_response( $items ); - - $response->header( 'X-WP-Total', count( $items ) ); - $response->header( 'X-WP-TotalPages', 1 ); - - 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 deleted file mode 100644 index 34d6b2a6..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php +++ /dev/null @@ -1,194 +0,0 @@ -<?php - -/** - * Publicize: List Connections - * - * [ - * { # Connnection Object. See schema for more detail. - * id: (string) Connection unique_id - * service_name: (string) Service slug - * display_name: (string) User name/display name of user/connection on Service - * global: (boolean) Is the Connection available to all users of the site? - * }, - * ... - * ] - * - * @since 6.8 - */ -class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Controller { - /** - * Flag to help WordPress.com decide where it should look for - * Publicize data. Ignored for direct requests to Jetpack sites. - * - * @var bool $wpcom_is_wpcom_only_endpoint - */ - public $wpcom_is_wpcom_only_endpoint = true; - - public function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'publicize/connections'; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - /** - * Called automatically on `rest_api_init()`. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permission_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Helper for generating schema. Used by this endpoint and by the - * Connection Test Result endpoint. - * - * @internal - * @return array - */ - protected function get_connection_schema_properties() { - return array( - 'id' => array( - 'description' => __( 'Unique identifier for the Publicize Connection', 'jetpack' ), - 'type' => 'string', - ), - 'service_name' => array( - 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ), - 'type' => 'string', - ), - 'display_name' => array( - 'description' => __( 'Username of the connected account', 'jetpack' ), - 'type' => 'string', - ), - 'global' => array( - 'description' => __( 'Is this connection available to all users?', 'jetpack' ), - 'type' => 'boolean', - ), - ); - } - - /** - * @return array - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'jetpack-publicize-connection', - 'type' => 'object', - 'properties' => $this->get_connection_schema_properties(), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Helper for retrieving Connections. Used by this endpoint and by - * the Connection Test Result endpoint. - * - * @internal - * @return array - */ - protected function get_connections() { - global $publicize; - - $items = array(); - - foreach ( (array) $publicize->get_services( 'connected' ) as $service_name => $connections ) { - foreach ( $connections as $connection ) { - $connection_meta = $publicize->get_connection_meta( $connection ); - $connection_data = $connection_meta['connection_data']; - - $items[] = array( - 'id' => (string) $publicize->get_connection_unique_id( $connection ), - 'service_name' => $service_name, - 'display_name' => $publicize->get_display_name( $service_name, $connection ), - // We expect an integer, but do loose comparison below in case some other type is stored - 'global' => 0 == $connection_data['user_id'], - ); - } - } - - return $items; - } - - /** - * @param WP_REST_Request $request - * @return WP_REST_Response suitable for 1-page collection - */ - public function get_items( $request ) { - $items = array(); - - foreach ( $this->get_connections() as $item ) { - $items[] = $this->prepare_item_for_response( $item, $request ); - } - - $response = rest_ensure_response( $items ); - $response->header( 'X-WP-Total', count( $items ) ); - $response->header( 'X-WP-TotalPages', 1 ); - - return $response; - } - - /** - * Filters out data based on ?_fields= request parameter - * - * @param array $connection - * @param WP_REST_Request $request - * @return array filtered $connection - */ - public function prepare_item_for_response( $connection, $request ) { - if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) { - return $connection; - } - - $fields = $this->get_fields_for_response( $request ); - - $response_data = array(); - foreach ( $connection as $field => $value ) { - if ( in_array( $field, $fields, true ) ) { - $response_data[ $field ] = $value; - } - } - - return $response_data; - } - - /** - * Verify that user can access Publicize data - * - * @return true|WP_Error - */ - public function get_items_permission_check() { - global $publicize; - - if ( ! $publicize ) { - return new WP_Error( - 'publicize_not_available', - __( 'Sorry, Publicize is not available on your site right now.', 'jetpack' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - if ( $publicize->current_user_can_access_publicize_data() ) { - return true; - } - - return new WP_Error( - 'invalid_user_permission_publicize', - __( 'Sorry, you are not allowed to access Publicize data on this site.', 'jetpack' ), - array( 'status' => rest_authorization_required_code() ) - ); - } -} - -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 deleted file mode 100644 index 4641b218..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php +++ /dev/null @@ -1,167 +0,0 @@ -<?php - -/** - * Publicize: List Publicize Services - * - * [ - * { # Service Object. See schema for more detail. - * name: (string) Service slug - * label: (string) Human readable label for the Service - * url: (string) Connect URL - * }, - * ... - * ] - * - * @since 6.8 - */ -class WPCOM_REST_API_V2_Endpoint_List_Publicize_Services extends WP_REST_Controller { - /** - * Flag to help WordPress.com decide where it should look for - * Publicize data. Ignored for direct requests to Jetpack sites. - * - * @var bool $wpcom_is_wpcom_only_endpoint - */ - public $wpcom_is_wpcom_only_endpoint = true; - - public function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'publicize/services'; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - /** - * Called automatically on `rest_api_init()`. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permission_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * @return array - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'jetpack-publicize-service', - 'type' => 'object', - 'properties' => array( - 'name' => array( - 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ), - 'type' => 'string', - ), - 'label' => array( - 'description' => __( 'Human readable label for the Publicize Service', 'jetpack' ), - 'type' => 'string', - ), - 'url' => array( - 'description' => __( 'The URL used to connect to the Publicize Service', 'jetpack' ), - 'type' => 'string', - 'format' => 'uri', - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Retrieves available Publicize Services. - * - * @see Publicize::get_available_service_data() - * - * @param WP_REST_Request $request - * @return WP_REST_Response suitable for 1-page collection - */ - public function get_items( $request ) { - global $publicize; - /** - * We need this because Publicize::get_available_service_data() uses `Jetpack_Keyring_Service_Helper` - * and `Jetpack_Keyring_Service_Helper` relies on `menu_page_url()`. - * - * We also need add_submenu_page(), as the URLs for connecting each service - * rely on the `sharing` menu subpage being present. - */ - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - - // The `sharing` submenu page must exist for service connect URLs to be correct. - add_submenu_page( 'options-general.php', '', '', 'manage_options', 'sharing', '__return_empty_string' ); - - $services_data = $publicize->get_available_service_data(); - - $services = array(); - foreach ( $services_data as $service_data ) { - $services[] = $this->prepare_item_for_response( $service_data, $request ); - } - - $response = rest_ensure_response( $services ); - $response->header( 'X-WP-Total', count( $services ) ); - $response->header( 'X-WP-TotalPages', 1 ); - - return $response; - } - - /** - * Filters out data based on ?_fields= request parameter - * - * @param array $service - * @param WP_REST_Request $request - * @return array filtered $service - */ - public function prepare_item_for_response( $service, $request ) { - if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) { - return $service; - } - - $fields = $this->get_fields_for_response( $request ); - - $response_data = array(); - foreach ( $service as $field => $value ) { - if ( in_array( $field, $fields, true ) ) { - $response_data[ $field ] = $value; - } - } - - return $response_data; - } - - /** - * Verify that user can access Publicize data - * - * @return true|WP_Error - */ - public function get_items_permission_check() { - global $publicize; - - if ( ! $publicize ) { - return new WP_Error( - 'publicize_not_available', - __( 'Sorry, Publicize is not available on your site right now.', 'jetpack' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - if ( $publicize->current_user_can_access_publicize_data() ) { - return true; - } - - return new WP_Error( - 'invalid_user_permission_publicize', - __( 'Sorry, you are not allowed to access Publicize data on this site.', 'jetpack' ), - array( 'status' => rest_authorization_required_code() ) - ); - } -} - -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 deleted file mode 100644 index 05d0ddd3..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/service-api-keys.php +++ /dev/null @@ -1,281 +0,0 @@ -<?php -/* - * Service API Keys: Exposes 3rd party api keys that are used on a site. - * - * [ - * { # Availabilty Object. See schema for more detail. - * code: (string) Displays success if the operation was successfully executed and an error code if it was not - * service: (string) The name of the service in question - * service_api_key: (string) The API key used by the service empty if one is not set yet - * message: (string) User friendly message - * }, - * ... - * ] - * - * @since 6.9 - */ -class WPCOM_REST_API_V2_Endpoint_Service_API_Keys extends WP_REST_Controller { - - function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'service-api-keys'; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - public function register_routes() { - register_rest_route( - 'wpcom/v2', - '/service-api-keys/(?P<service>[a-z\-_]+)', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( __CLASS__, 'get_service_api_key' ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( __CLASS__, 'update_service_api_key' ), - 'permission_callback' => array( __CLASS__, 'edit_others_posts_check' ), - 'args' => array( - 'service_api_key' => array( - 'required' => true, - 'type' => 'text', - ), - ), - ), - array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => array( __CLASS__, 'delete_service_api_key' ), - 'permission_callback' => array( __CLASS__, 'edit_others_posts_check' ), - ), - ) - ); - } - - public static function edit_others_posts_check() { - if ( current_user_can( 'edit_others_posts' ) ) { - return true; - } - - $user_permissions_error_msg = esc_html__( - 'You do not have the correct user permissions to perform this action. - Please contact your site admin if you think this is a mistake.', - 'jetpack' - ); - - return new WP_Error( 'invalid_user_permission_edit_others_posts', $user_permissions_error_msg, rest_authorization_required_code() ); - } - - /** - * Return the available Gutenberg extensions schema - * - * @return array Service API Key schema - */ - public function get_public_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'service-api-keys', - 'type' => 'object', - 'properties' => array( - 'code' => array( - 'description' => __( 'Displays success if the operation was successfully executed and an error code if it was not', 'jetpack' ), - 'type' => 'string', - ), - 'service' => array( - 'description' => __( 'The name of the service in question', 'jetpack' ), - 'type' => 'string', - ), - 'service_api_key' => array( - 'description' => __( 'The API key used by the service. Empty if none has been set yet', 'jetpack' ), - 'type' => 'string', - ), - 'message' => array( - 'description' => __( 'User friendly message', 'jetpack' ), - 'type' => 'string', - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Get third party plugin API keys. - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function get_service_api_key( $request ) { - - $service = self::validate_service_api_service( $request['service'] ); - if ( ! $service ) { - return self::service_api_invalid_service_response(); - } - $option = self::key_for_api_service( $service ); - $message = esc_html__( 'API key retrieved successfully.', 'jetpack' ); - return array( - 'code' => 'success', - 'service' => $service, - 'service_api_key' => Jetpack_Options::get_option( $option, '' ), - 'message' => $message, - ); - } - - /** - * Update third party plugin API keys. - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function update_service_api_key( $request ) { - $service = self::validate_service_api_service( $request['service'] ); - 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 ); - - $validation = self::validate_service_api_key( $service_api_key, $service, $params ); - if ( ! $validation['status'] ) { - return new WP_Error( 'invalid_key', esc_html__( 'Invalid API Key', 'jetpack' ), array( 'status' => 404 ) ); - } - $message = esc_html__( 'API key updated successfully.', 'jetpack' ); - Jetpack_Options::update_option( $option, $service_api_key ); - return array( - 'code' => 'success', - 'service' => $service, - 'service_api_key' => Jetpack_Options::get_option( $option, '' ), - 'message' => $message, - ); - } - - /** - * Delete a third party plugin API key. - * - * @param WP_REST_Request $request { - * Array of parameters received by request. - * - * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'. - * } - */ - public static function delete_service_api_key( $request ) { - $service = self::validate_service_api_service( $request['service'] ); - if ( ! $service ) { - return self::service_api_invalid_service_response(); - } - $option = self::key_for_api_service( $service ); - Jetpack_Options::delete_option( $option ); - $message = esc_html__( 'API key deleted successfully.', 'jetpack' ); - return array( - 'code' => 'success', - 'service' => $service, - 'service_api_key' => Jetpack_Options::get_option( $option, '' ), - 'message' => $message, - ); - } - - /** - * Validate the service provided in /service-api-keys/ endpoints. - * To add a service to these endpoints, add the service name to $valid_services - * and add '{service name}_api_key' to the non-compact return array in get_option_names(), - * in class-jetpack-options.php - * - * @param string $service The service the API key is for. - * @return string Returns the service name if valid, null if invalid. - */ - public static function validate_service_api_service( $service = null ) { - $valid_services = array( - 'mapbox', - ); - return in_array( $service, $valid_services, true ) ? $service : null; - } - - /** - * Error response for invalid service API key requests with an invalid service. - */ - public static function service_api_invalid_service_response() { - return new WP_Error( - 'invalid_service', - esc_html__( 'Invalid Service', 'jetpack' ), - array( 'status' => 404 ) - ); - } - - /** - * Validate API Key - * - * @param string $key The API key to be validated. - * @param string $service The service the API key is for. - */ - public static function validate_service_api_key( $key = null, $service = null ) { - $validation = false; - switch ( $service ) { - case 'mapbox': - $validation = self::validate_service_api_key_mapbox( $key ); - break; - } - return $validation; - } - - /** - * Validate Mapbox API key - * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php - * - * @param string $key The API key to be validated. - */ - public static function validate_service_api_key_mapbox( $key ) { - $status = true; - $msg = null; - $mapbox_url = sprintf( - 'https://api.mapbox.com?%s', - $key - ); - $mapbox_response = wp_safe_remote_get( esc_url_raw( $mapbox_url ) ); - $mapbox_body = wp_remote_retrieve_body( $mapbox_response ); - if ( '{"api":"mapbox"}' !== $mapbox_body ) { - $status = false; - $msg = esc_html__( 'Can\'t connect to Mapbox', 'jetpack' ); - return array( - 'status' => $status, - 'error_message' => $msg, - ); - } - $mapbox_geocode_url = esc_url_raw( - sprintf( - 'https://api.mapbox.com/geocoding/v5/mapbox.places/%s.json?access_token=%s', - '1+broadway+new+york+ny+usa', - $key - ) - ); - $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 ) ) { - $status = false; - $msg = $mapbox_geocode_json->message; - } - return array( - 'status' => $status, - 'error_message' => $msg, - ); - } - - /** - * Create site option key for service - * - * @param string $service The service to create key for. - */ - private static function key_for_api_service( $service ) { - 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 deleted file mode 100644 index 4c34161c..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/sites-posts-featured-media-url.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -/* - * Plugin Name: WPCOM Add Featured Media URL - * - * Adds `jetpack_featured_media_url` to post responses - */ - -class WPCOM_REST_API_V2_Sites_Posts_Add_Featured_Media_URL { - 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', - array( - 'get_callback' => array( $this, 'get_featured_media_url' ), - 'update_callback' => null, - 'schema' => null, - ) - ); - } - - function get_featured_media_url( $object, $field_name, $request ) { - $featured_media_url = ''; - $image_attributes = wp_get_attachment_image_src( - get_post_thumbnail_id( $object['id'] ), - 'full' - ); - if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) { - $featured_media_url = (string) $image_attributes[0]; - } - 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 deleted file mode 100644 index c1a712bd..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -/** - * Subscribers: Get subscriber count - * - * @since 6.9 - */ -class WPCOM_REST_API_V2_Endpoint_Subscribers extends WP_REST_Controller { - function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'subscribers'; - // This endpoint *does not* need to connect directly to Jetpack sites. - $this->wpcom_is_wpcom_only_endpoint = true; - add_action( 'rest_api_init', array( $this, 'register_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( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_subscriber_count' ), - 'permission_callback' => array( $this, 'readable_permission_check' ), - ) - ) ); - } - - 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 ) ); - } - - return true; - } - - /** - * Retrieves subscriber count - * - * @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 - if ( ! Jetpack_Constants::is_defined( 'TESTING_IN_JETPACK' ) ) { - delete_transient( 'wpcom_subscribers_total' ); - } - - $subscriber_info = Jetpack_Subscriptions_Widget::fetch_subscriber_count(); - $subscriber_count = $subscriber_info['value']; - - return array( - 'count' => $subscriber_count - ); - } -} - -if ( - Jetpack::is_module_active( 'subscriptions' ) || - ( Jetpack_Constants::is_defined( 'TESTING_IN_JETPACK' ) && Jetpack_Constants::get_constant( 'TESTING_IN_JETPACK' ) ) -) { - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Subscribers' ); -} 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 deleted file mode 100644 index b615c4e6..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/attachment-fields-videopress.php +++ /dev/null @@ -1,171 +0,0 @@ -<?php -/** - * Extend the REST API functionality for VideoPress users. - * - * @package Jetpack - */ - -/** - * Add per-attachment VideoPress data. - * - * { # Attachment Object - * ... - * jetpack_videopress_guid: (string) VideoPress identifier - * ... - * } - * - * @since 7.1.0 - */ -class WPCOM_REST_API_V2_Attachment_VideoPress_Field extends WPCOM_REST_API_V2_Field_Controller { - /** - * The REST Object Type to which the jetpack_videopress_guid field will be added. - * - * @var string - */ - protected $object_type = 'attachment'; - - /** - * The name of the REST API field to add. - * - * @var string $field_name - */ - protected $field_name = 'jetpack_videopress_guid'; - - /** - * Registers the jetpack_videopress field and adds a filter to remove it for attachments that are not videos. - */ - public function register_fields() { - parent::register_fields(); - - add_filter( 'rest_prepare_attachment', array( $this, 'remove_field_for_non_videos' ), 10, 2 ); - } - - /** - * Defines data structure and what elements are visible in which contexts - */ - public function get_schema() { - return array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => $this->field_name, - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - 'description' => __( 'Unique VideoPress ID', 'jetpack' ), - ); - } - - /** - * Getter: Retrieve current VideoPress data for a given attachment. - * - * @param array $attachment Response from the attachment endpoint. - * @param WP_REST_Request $request Request to the attachment endpoint. - * - * @return string - */ - public function get( $attachment, $request ) { - if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { - $blog_id = get_current_blog_id(); - } else { - $blog_id = Jetpack_Options::get_option( 'id' ); - } - - $post_id = absint( $attachment['id'] ); - - $videopress_guid = $this->get_videopress_guid( $post_id, $blog_id ); - - if ( ! $videopress_guid ) { - return ''; - } - - return $videopress_guid; - } - - /** - * Gets the VideoPress GUID for a given attachment. - * - * This is pulled out into a separate method to support unit test mocking. - * - * @param int $attachment_id Attachment ID. - * @param int $blog_id Blog ID. - * - * @return string - */ - public function get_videopress_guid( $attachment_id, $blog_id ) { - return video_get_info_by_blogpostid( $blog_id, $attachment_id )->guid; - } - - /** - * Checks if the given attachment is a video. - * - * @param object $attachment The attachment object. - * - * @return false|int - */ - public function is_video( $attachment ) { - return wp_startswith( $attachment->post_mime_type, 'video/' ); - } - - /** - * Removes the jetpack_videopress_guid field from the response if the - * given attachment is not a video. - * - * @param WP_REST_Response $response Response from the attachment endpoint. - * @param WP_Post $attachment The original attachment object. - * - * @return mixed - */ - public function remove_field_for_non_videos( $response, $attachment ) { - if ( ! $this->is_video( $attachment ) ) { - unset( $response->data[ $this->field_name ] ); - } - - return $response; - } - - /** - * Setter: It does nothing since `jetpack_videopress` is a read-only field. - * - * @param mixed $value The new value for the field. - * @param WP_Post $object The attachment object. - * @param WP_REST_Request $request The request object. - * - * @return null - */ - public function update( $value, $object, $request ) { - return null; - } - - /** - * Permission Check for the field's getter. Delegate the responsibility to the - * attachment endpoint, so it always returns true. - * - * @param mixed $object Response from the attachment endpoint. - * @param WP_REST_Request $request Request to the attachment endpoint. - * - * @return true - */ - public function get_permission_check( $object, $request ) { - return true; - } - - /** - * Permission Check for the field's setter. Delegate the responsibility to the - * attachment endpoint, so it always returns true. - * - * @param mixed $value The new value for the field. - * @param WP_Post $object The attachment object. - * @param WP_REST_Request $request Request to the attachment endpoint. - * - * @return true - */ - public function update_permission_check( $value, $object, $request ) { - return true; - } -} - -if ( - ( method_exists( 'Jetpack', 'is_active' ) && Jetpack::is_active() ) || - ( defined( 'IS_WPCOM' ) && IS_WPCOM ) -) { - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Attachment_VideoPress_Field' ); -} 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 deleted file mode 100644 index c4254a9d..00000000 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php +++ /dev/null @@ -1,353 +0,0 @@ -<?php - -/** - * Add per-post Publicize Connection data. - * - * { # Post Object - * ... - * jetpack_publicize_connections: { # Defined below in this file. See schema for more detail. - * id: (string) Connection unique_id - * service_name: (string) Service slug - * display_name: (string) User name/display name of user/connection on Service - * enabled: (boolean) Is this connection slated to be shared to? context=edit only - * done: (boolean) Is this post (or connection) done sharing? context=edit only - * toggleable: (boolean) Can the current user change the `enabled` setting for this Connection+Post? context=edit only - * } - * ... - * meta: { # Not defined in this file. Handled in modules/publicize/publicize.php via `register_meta()` - * jetpack_publicize_message: (string) The message to use instead of the post's title when sharing. - * } - * ... - * } - * - * @since 6.8.0 - */ -class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_V2_Field_Controller { - protected $object_type = 'post'; - protected $field_name = 'jetpack_publicize_connections'; - - public $memoized_updates = array(); - - /** - * Registers the jetpack_publicize_connections field. Called - * automatically on `rest_api_init()`. - */ - public function register_fields() { - $this->object_type = get_post_types_by_support( 'publicize' ); - - foreach ( $this->object_type as $post_type ) { - // 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' ) ) { - add_post_type_support( $post_type, 'custom-fields' ); - } - - add_filter( 'rest_pre_insert_' . $post_type, array( $this, 'rest_pre_insert' ), 10, 2 ); - add_action( 'rest_insert_' . $post_type, array( $this, 'rest_insert' ), 10, 3 ); - } - - parent::register_fields(); - } - - /** - * Defines data structure and what elements are visible in which contexts - */ - public function get_schema() { - return array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'jetpack-publicize-post-connections', - 'type' => 'array', - 'context' => array( 'view', 'edit' ), - 'items' => $this->post_connection_schema(), - 'default' => array(), - ); - } - - private function post_connection_schema() { - return array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'jetpack-publicize-post-connection', - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the Publicize Connection', 'jetpack' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'service_name' => array( - 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'display_name' => array( - 'description' => __( 'Username of the connected account', 'jetpack' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'enabled' => array( - 'description' => __( 'Whether to share to this connection', 'jetpack' ), - 'type' => 'boolean', - 'context' => array( 'edit' ), - ), - 'done' => array( - 'description' => __( 'Whether Publicize has already finished sharing for this post', 'jetpack' ), - 'type' => 'boolean', - 'context' => array( 'edit' ), - 'readonly' => true, - ), - 'toggleable' => array( - 'description' => __( 'Whether `enable` can be changed for this post/connection', 'jetpack' ), - 'type' => 'boolean', - 'context' => array( 'edit' ), - 'readonly' => true, - ), - ), - ); - } - - /** - * @param int $post_id - * @return true|WP_Error - */ - function permission_check( $post_id ) { - global $publicize; - - if ( ! $publicize ) { - return new WP_Error( - 'publicize_not_available', - __( 'Sorry, Publicize is not available on your site right now.', 'jetpack' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - if ( $publicize->current_user_can_access_publicize_data( $post_id ) ) { - return true; - } - - return new WP_Error( - 'invalid_user_permission_publicize', - __( 'Sorry, you are not allowed to access Publicize data for this post.', 'jetpack' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - /** - * Getter permission check - * - * @param array $post_array Response data from Post Endpoint - * @return true|WP_Error - */ - function get_permission_check( $post_array, $request ) { - return $this->permission_check( isset( $post_array['id'] ) ? $post_array['id'] : 0 ); - - } - - /** - * Setter permission check - * - * @param WP_Post $post - * @return true|WP_Error - */ - public function update_permission_check( $value, $post, $request ) { - return $this->permission_check( isset( $post->ID ) ? $post->ID : 0 ); - } - - /** - * Getter: Retrieve current list of connected social accounts for a given post. - * - * @see Publicize::get_filtered_connection_data() - * - * @param array $post_array Response from Post Endpoint - * @param WP_REST_Request - * - * @return array List of connections - */ - public function get( $post_array, $request ) { - global $publicize; - - if ( ! $publicize ) { - return array(); - } - - $schema = $this->post_connection_schema(); - $properties = array_keys( $schema['properties'] ); - - $connections = $publicize->get_filtered_connection_data( $post_array['id'] ); - - $output_connections = array(); - foreach ( $connections as $connection ) { - $output_connection = array(); - foreach ( $properties as $property ) { - if ( isset( $connection[ $property ] ) ) { - $output_connection[ $property ] = $connection[ $property ]; - } - } - - $output_connection['id'] = (string) $connection['unique_id']; - - $output_connections[] = $output_connection; - } - - return $output_connections; - } - - /** - * 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 - * @return Filtered $post - */ - public function rest_pre_insert( $post, $request ) { - if ( ! isset( $request['jetpack_publicize_connections'] ) ) { - return $post; - } - - $permission_check = $this->update_permission_check( $request['jetpack_publicize_connections'], $post, $request ); - - if ( is_wp_error( $permission_check ) ) { - return $permission_check; - } - - // memoize - $this->get_meta_to_update( $request['jetpack_publicize_connections'], isset( $post->ID ) ? $post->ID : 0 ); - - return $post; - } - - /** - * 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 - */ - public function rest_insert( $post, $request, $is_new ) { - if ( ! $is_new ) { - // An existing post was edited - no need to update - // our cache - we started out knowing the correct - // post ID. - return; - } - - if ( ! isset( $request['jetpack_publicize_connections'] ) ) { - return; - } - - if ( ! isset( $this->memoized_updates[0] ) ) { - return; - } - - $this->memoized_updates[ $post->ID ] = $this->memoized_updates[0]; - unset( $this->memoized_updates[0] ); - } - - protected function get_meta_to_update( $requested_connections, $post_id = 0 ) { - global $publicize; - - if ( ! $publicize ) { - return array(); - } - - 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 - $available_connections_by_unique_id = array(); - $available_connections_by_service_name = array(); - foreach ( $available_connections as $available_connection ) { - $available_connections_by_unique_id[ $available_connection['unique_id'] ] = $available_connection; - - if ( ! isset( $available_connections_by_service_name[ $available_connection['service_name'] ] ) ) { - $available_connections_by_service_name[ $available_connection['service_name'] ] = array(); - } - $available_connections_by_service_name[ $available_connection['service_name'] ][] = $available_connection; - } - - // Handle { service_name: $service_name, enabled: (bool) } - foreach ( $requested_connections as $requested_connection ) { - if ( ! isset( $requested_connection['service_name'] ) ) { - continue; - } - - if ( ! isset( $available_connections_by_service_name[ $requested_connection['service_name'] ] ) ) { - continue; - } - - foreach ( $available_connections_by_service_name[ $requested_connection['service_name'] ] as $available_connection ) { - $changed_connections[ $available_connection['unique_id'] ] = $requested_connection['enabled']; - } - } - - // Handle { id: $id, enabled: (bool) } - // These override the service_name settings - foreach ( $requested_connections as $requested_connection ) { - if ( ! isset( $requested_connection['id'] ) ) { - continue; - } - - if ( ! isset( $available_connections_by_unique_id[ $requested_connection['id'] ] ) ) { - continue; - } - - $changed_connections[ $requested_connection['id'] ] = $requested_connection['enabled']; - } - - // Set all changed connections to their new value - foreach ( $changed_connections as $unique_id => $enabled ) { - $connection = $available_connections_by_unique_id[ $unique_id ]; - - if ( $connection['done'] || ! $connection['toggleable'] ) { - continue; - } - - $available_connections_by_unique_id[ $unique_id ]['enabled'] = $enabled; - } - - $meta_to_update = array(); - // 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; - } else { - $meta_to_update[$publicize->POST_SKIP . $unique_id] = 1; - } - } - - $this->memoized_updates[$post_id] = $meta_to_update; - - return $meta_to_update; - } - - /** - * 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 - */ - public function update( $requested_connections, $post, $request ) { - foreach ( $this->get_meta_to_update( $requested_connections, $post->ID ) as $meta_key => $meta_value ) { - if ( is_null( $meta_value ) ) { - delete_post_meta( $post->ID, $meta_key ); - } else { - update_post_meta( $post->ID, $meta_key, $meta_value ); - } - } - } -} - -if ( Jetpack::is_module_active( 'publicize' ) ) { - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Post_Publicize_Connections_Field' ); -} 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 b097dc50..00000000 --- a/plugins/jetpack/_inc/lib/debugger/0-load.php +++ /dev/null @@ -1,13 +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'; -/* The "In-Plugin Debugger" admin page. */ -require_once 'class-jetpack-debugger.php'; diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php deleted file mode 100644 index 0cc861db..00000000 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php +++ /dev/null @@ -1,302 +0,0 @@ -<?php -/** - * Jetpack Connection Testing - * - * Framework for various "unit tests" against the Jetpack connection. - * - * Individual tests should be added to the class-jetpack-cxn-tests.php file. - * - * @author Brandon Kraft - * @package Jetpack - */ - -/** - * "Unit Tests" for the Jetpack connection. - */ -class Jetpack_Cxn_Test_Base { - - /** - * Tests to run on the Jetpack connection. - * - * @var array $tests - */ - protected $tests = array(); - - /** - * Results of the Jetpack connection tests. - * - * @var array $results - */ - protected $results = array(); - - /** - * Status of the testing suite. - * - * Used internally to determine if a test should be skipped since the tests are already failing. Assume passing. - * - * @var bool $pass - */ - protected $pass = true; - - /** - * Jetpack_Cxn_Test constructor. - */ - public function __construct() { - $this->tests = array(); - $this->results = array(); - } - - /** - * Adds a new test to the Jetpack Connection Testing suite. - * - * @param callable $callable Test to add to queue. - * @param array $groups Testing groups to add test to. - * - * @return bool True if successfully added. False for a failure. - */ - public function add_test( $callable, $groups = array( 'default' ) ) { - if ( is_callable( $callable ) ) { - $this->tests[] = array( - 'test' => $callable, - 'group' => $groups, - ); - return true; - } - - return false; - } - - /** - * Runs the Jetpack connection suite. - */ - public function run_tests() { - foreach ( $this->tests as $test ) { - $result = call_user_func( $test['test'] ); - $result['group'] = $test['group']; - $this->results[] = $result; - if ( false === $result['pass'] ) { - $this->pass = false; - } - } - } - - /** - * Returns the full results array. - * - * @param string $group Testing group whose results we want. Defaults to "default" group. Use "all" for all tests. - * @return array Array of test results. - */ - public function raw_results( $group = 'default' ) { - if ( ! $this->results ) { - $this->run_tests(); - } - - $results = $this->results; - - if ( 'all' === $group ) { - return $results; - } - - foreach ( $results as $test => $result ) { - if ( ! in_array( $group, $result['group'], true ) ) { - unset( $results[ $test ] ); - } - } - - return $results; - } - - /** - * Returns the status of the connection suite. - * - * @param string $group Testing group to check status of. Optional, default all tests. - * - * @return true|array True if all tests pass. Array of failed tests. - */ - public function pass( $group = 'default' ) { - $results = $this->raw_results( $group ); - - foreach ( $results as $result ) { - // 'pass' could be true, false, or 'skipped'. We only want false. - if ( isset( $result['pass'] ) && false === $result['pass'] ) { - return false; - } - } - - return true; - - } - - /** - * Return array of failed test messages. - * - * @param string $group Testing group whose failures we want. Defaults to "default". Use "all" for all tests. - * - * @return false|array False if no failed tests. Otherwise, array of failed tests. - */ - public function list_fails( $group = 'default' ) { - $results = $this->raw_results( $group ); - - foreach ( $results as $test => $result ) { - // We do not want tests that passed or ones that are misconfigured (no pass status or no failure message). - if ( ! isset( $result['pass'] ) || false !== $result['pass'] || ! isset( $result['message'] ) ) { - unset( $results[ $test ] ); - } - } - - return $results; - } - - /** - * Helper function to return consistent responses for a passing test. - * - * @param string $name Test name. - * - * @return array Test results. - */ - public static function passing_test( $name = 'Unnamed' ) { - return array( - 'name' => $name, - 'pass' => true, - 'message' => __( 'Test Passed!', 'jetpack' ), - 'resolution' => false, - ); - } - - /** - * Helper function to return consistent responses for a skipped test. - * - * @param string $name Test name. - * @param string $message Reason for skipping the test. Optional. - * - * @return array Test results. - */ - public static function skipped_test( $name = 'Unnamed', $message = false ) { - return array( - 'name' => $name, - 'pass' => 'skipped', - 'message' => $message, - 'resolution' => false, - ); - } - - /** - * Helper function to return consistent responses for a failing test. - * - * @param string $name Test name. - * @param string $message Message detailing the failure. - * @param string $resolution Steps to resolve. - * - * @return array Test results. - */ - public static function failing_test( $name, $message, $resolution = false ) { - // Provide standard resolutions steps, but allow pass-through of non-standard ones. - switch ( $resolution ) { - case 'cycle_connection': - $resolution = __( 'Please disconnect and reconnect Jetpack.', 'jetpack' ); // @todo: Link. - break; - case 'outbound_requests': - $resolution = __( 'Please ask your hosting provider to confirm your server can make outbound requests to jetpack.com.', 'jetpack' ); - break; - case 'support': - $resolution = __( 'Please contact support.', 'jetpack' ); // @todo: Link to support. - break; - } - - return array( - 'name' => $name, - 'pass' => false, - 'message' => $message, - 'resolution' => $resolution, - ); - } - - /** - * Provide WP_CLI friendly testing results. - * - * @param string $group Testing group whose results we are outputting. Default "default". Use "all" for all tests. - */ - public function output_results_for_cli( $group = 'default' ) { - if ( defined( 'WP_CLI' ) && WP_CLI ) { - if ( Jetpack::is_development_mode() ) { - WP_CLI::line( __( 'Jetpack is in Development Mode:', 'jetpack' ) ); - WP_CLI::line( Jetpack::development_mode_trigger_text() ); - } - WP_CLI::line( __( 'TEST RESULTS:', 'jetpack' ) ); - foreach ( $this->raw_results( $group ) as $test ) { - if ( true === $test['pass'] ) { - WP_CLI::log( WP_CLI::colorize( '%gPassed:%n ' . $test['name'] ) ); - } elseif ( 'skipped' === $test['pass'] ) { - WP_CLI::log( WP_CLI::colorize( '%ySkipped:%n ' . $test['name'] ) ); - if ( $test['message'] ) { - WP_CLI::log( ' ' . $test['message'] ); // Number of spaces to "tab indent" the reason. - } - } else { // Failed. - WP_CLI::log( WP_CLI::colorize( '%rFailed:%n ' . $test['name'] ) ); - WP_CLI::log( ' ' . $test['message'] ); // Number of spaces to "tab indent" the reason. - } - } - } - } - - /** - * Provide single WP Error instance of all failures. - * - * @param string $group Testing group whose failures we want converted. Default "default". Use "all" for all tests. - * - * @return WP_Error|false WP_Error with all failed tests or false if there were no failures. - */ - public function output_fails_as_wp_error( $group = 'default' ) { - if ( $this->pass( $group ) ) { - return false; - } - $fails = $this->list_fails( $group ); - $error = false; - - foreach ( $fails as $result ) { - $code = 'failed_' . $result['name']; - $message = $result['message']; - $data = array( - 'resolution' => $result['resolution'], - ); - if ( ! $error ) { - $error = new WP_Error( $code, $message, $data ); - } else { - $error->add( $code, $message, $data ); - } - } - - return $error; - } - - /** - * Encrypt data for sending to WordPress.com. - * - * @todo When PHP minimum is 5.3+, add cipher detection to use an agreed better cipher than RC4. RC4 should be the last resort. - * - * @param string $data Data to encrypt with the WP.com Public Key. - * - * @return false|array False if functionality not available. Array of encrypted data, encryption key. - */ - public function encrypt_string_for_wpcom( $data ) { - $return = false; - if ( ! function_exists( 'openssl_get_publickey' ) || ! function_exists( 'openssl_seal' ) ) { - return $return; - } - - $public_key = openssl_get_publickey( JETPACK__DEBUGGER_PUBLIC_KEY ); - - if ( $public_key && openssl_seal( $data, $encrypted_data, $env_key, array( $public_key ) ) ) { - // We are returning base64-encoded values to ensure they're characters we can use in JSON responses without issue. - $return = array( - 'data' => base64_encode( $encrypted_data ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode - 'key' => base64_encode( $env_key[0] ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode - 'cipher' => 'RC4', // When Jetpack's minimum WP version is at PHP 5.3+, we will add in detecting and using a stronger one. - ); - } - - openssl_free_key( $public_key ); - - return $return; - } -} diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php deleted file mode 100644 index 6d4f00e6..00000000 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php +++ /dev/null @@ -1,338 +0,0 @@ -<?php -/** - * Collection of tests to run on the Jetpack connection locally. - * - * @package Jetpack - */ - -/** - * Class Jetpack_Cxn_Tests contains all of the actual tests. - */ -class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base { - - /** - * Jetpack_Cxn_Tests constructor. - */ - public function __construct() { - parent::__construct(); - - $methods = get_class_methods( 'Jetpack_Cxn_Tests' ); - - foreach ( $methods as $method ) { - if ( false === strpos( $method, 'test__' ) ) { - continue; - } - $this->add_test( array( $this, $method ) ); - } - - /** - * Fires after loading default Jetpack Connection tests. - * - * @since 7.1.0 - */ - do_action( 'jetpack_connection_tests_loaded' ); - - /** - * Determines if the WP.com testing suite should be included. - * - * @since 7.1.0 - * - * @param bool $run_test To run the WP.com testing suite. Default true. - */ - if ( apply_filters( 'jetpack_debugger_run_self_test', true ) ) { - /** - * Intentionally added last as it checks for an existing failure state before attempting. - * Generally, any failed location condition would result in the WP.com check to fail too, so - * we will skip it to avoid confusing error messages. - */ - $this->add_test( array( $this, 'last__wpcom_self_test' ) ); - } - } - - /** - * Helper function to look up the expected master user and return the local WP_User. - * - * @return WP_User Jetpack's expected master user. - */ - protected function helper_retrieve_local_master_user() { - $master_user = Jetpack_Options::get_option( 'master_user' ); - return new WP_User( $master_user ); - } - - /** - * Is Jetpack even connected and supposed to be talking to WP.com? - */ - protected function helper_is_jetpack_connected() { - return ( Jetpack::is_active() && ! Jetpack::is_development_mode() ); - } - - /** - * Test if Jetpack is connected. - */ - protected function test__check_if_connected() { - $name = __FUNCTION__; - if ( $this->helper_is_jetpack_connected() ) { - $result = self::passing_test( $name ); - } elseif ( Jetpack::is_development_mode() ) { - $result = self::skipped_test( $name, __( 'Jetpack is in Development Mode:', 'jetpack' ) . ' ' . Jetpack::development_mode_trigger_text(), __( 'Disable development mode.', 'jetpack' ) ); - } else { - $result = self::failing_test( $name, __( 'Jetpack is not connected.', 'jetpack' ), 'cycle_connection' ); - } - - return $result; - } - - /** - * Test that the master user still exists on this site. - * - * @return array Test results. - */ - protected function test__master_user_exists_on_site() { - $name = __FUNCTION__; - if ( ! $this->helper_is_jetpack_connected() ) { - return self::skipped_test( $name, __( 'Jetpack is not connected. No master user to check.', 'jetpack' ) ); // Skip test. - } - $local_user = $this->helper_retrieve_local_master_user(); - - if ( $local_user->exists() ) { - $result = self::passing_test( $name ); - } else { - $result = self::failing_test( $name, __( 'The user who setup the Jetpack connection no longer exists on this site.', 'jetpack' ), 'cycle_connection' ); - } - - return $result; - } - - /** - * Test that the master user has the manage options capability (e.g. is an admin). - * - * Generic calls from WP.com execute on Jetpack as the master user. If it isn't an admin, random things will fail. - * - * @return array Test results. - */ - protected function test__master_user_can_manage_options() { - $name = __FUNCTION__; - if ( ! $this->helper_is_jetpack_connected() ) { - return self::skipped_test( $name, __( 'Jetpack is not connected.', 'jetpack' ) ); // Skip test. - } - $master_user = $this->helper_retrieve_local_master_user(); - - if ( user_can( $master_user, 'manage_options' ) ) { - $result = self::passing_test( $name ); - } else { - /* translators: a WordPress username */ - $result = self::failing_test( $name, sprintf( __( 'The user (%s) who setup the Jetpack connection is not an administrator.', 'jetpack' ), $master_user->user_login ), __( 'Either upgrade the user or disconnect and reconnect Jetpack.', 'jetpack' ) ); // @todo: Link to the right places. - } - - return $result; - } - - /** - * Test that the PHP's XML library is installed. - * - * While it should be installed by default, increasingly in PHP 7, some OSes require an additional php-xml package. - * - * @return array Test results. - */ - protected function test__xml_parser_available() { - $name = __FUNCTION__; - if ( function_exists( 'xml_parser_create' ) ) { - $result = self::passing_test( $name ); - } else { - $result = self::failing_test( $name, __( 'PHP XML manipluation libraries are not available.', 'jetpack' ), __( "Please ask your hosting provider to refer to our server requirements at https://jetpack.com/support/server-requirements/ and enable PHP's XML module.", 'jetpack' ) ); - } - - return $result; - } - - /** - * Test that the server is able to send an outbound http communication. - * - * @return array Test results. - */ - protected function test__outbound_http() { - $name = __FUNCTION__; - $request = wp_remote_get( preg_replace( '/^https:/', 'http:', JETPACK__API_BASE ) . 'test/1/' ); - $code = wp_remote_retrieve_response_code( $request ); - - if ( 200 === intval( $code ) ) { - $result = self::passing_test( $name ); - } else { - $result = self::failing_test( $name, __( 'Your server did not successfully connect to the Jetpack server using HTTP', 'jetpack' ), 'outbound_requests' ); - } - - return $result; - } - - /** - * Test that the server is able to send an outbound https communication. - * - * @return array Test results. - */ - protected function test__outbound_https() { - $name = __FUNCTION__; - $request = wp_remote_get( preg_replace( '/^http:/', 'https:', JETPACK__API_BASE ) . 'test/1/' ); - $code = wp_remote_retrieve_response_code( $request ); - - if ( 200 === intval( $code ) ) { - $result = self::passing_test( $name ); - } else { - $result = self::failing_test( $name, __( 'Your server did not successfully connect to the Jetpack server using HTTPS', 'jetpack' ), 'outbound_requests' ); - } - - return $result; - } - - /** - * Check for an IDC. - * - * @return array Test results. - */ - protected function test__identity_crisis() { - $name = __FUNCTION__; - if ( ! $this->helper_is_jetpack_connected() ) { - return self::skipped_test( $name, __( 'Jetpack is not connected.', 'jetpack' ) ); // Skip test. - } - $identity_crisis = Jetpack::check_identity_crisis(); - - if ( ! $identity_crisis ) { - $result = self::passing_test( $name ); - } else { - $message = sprintf( - /* translators: Two URLs. The first is the locally-recorded value, the second is the value as recorded on WP.com. */ - __( 'Your url is set as `%1$s`, but your WordPress.com connection lists it as `%2$s`!', 'jetpack' ), - $identity_crisis['home'], - $identity_crisis['wpcom_home'] - ); - $result = self::failing_test( $name, $message, 'support' ); - } - return $result; - } - - /** - * Tests connection status against wp.com's test-connection endpoint - * - * @todo: Compare with the wpcom_self_test. We only need one of these. - * - * @return array Test results. - */ - protected function test__wpcom_connection_test() { - $name = __FUNCTION__; - - if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || Jetpack::is_staging_site() || ! $this->pass ) { - return self::skipped_test( $name ); - } - - $response = Jetpack_Client::wpcom_json_api_request_as_blog( - sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ), - Jetpack_Client::WPCOM_JSON_API_VERSION - ); - - if ( is_wp_error( $response ) ) { - /* translators: %1$s is the error code, %2$s is the error message */ - $message = sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ); - return self::failing_test( $name, $message ); - } - - $body = wp_remote_retrieve_body( $response ); - if ( ! $body ) { - $message = __( 'Connection test failed (empty response body)', 'jetpack' ) . wp_remote_retrieve_response_code( $response ); - return self::failing_test( $name, $message ); - } - - $result = json_decode( $body ); - $is_connected = (bool) $result->connected; - $message = $result->message . wp_remote_retrieve_response_code( $response ); - - if ( $is_connected ) { - return self::passing_test( $name ); - } else { - return self::failing_test( $name, $message ); - } - } - - /** - * Tests the port number to ensure it is an expected value. - * - * We expect that sites on be on one of: - * port 80, - * port 443 (https sites only), - * the value of JETPACK_SIGNATURE__HTTP_PORT, - * unless the site is intentionally on a different port (e.g. example.com:8080 is the site's URL). - * - * If the value isn't one of those and the site's URL doesn't include a port, then the signature verification will fail. - * - * This happens most commonly on sites with reverse proxies, so the edge (e.g. Varnish) is running on 80/443, but nginx - * or Apache is responding internally on a different port (e.g. 81). - * - * @return array Test results - */ - protected function test__server_port_value() { - $name = __FUNCTION__; - if ( ! isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && ! isset( $_SERVER['SERVER_PORT'] ) ) { - $message = 'The server port values are not defined. This is most common when running PHP via a CLI.'; - return self::skipped_test( $name, $message ); - } - $site_port = wp_parse_url( home_url(), PHP_URL_PORT ); - $server_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? (int) $_SERVER['HTTP_X_FORWARDED_PORT'] : (int) $_SERVER['SERVER_PORT']; - $http_ports = array( 80 ); - $https_ports = array( 80, 443 ); - - if ( defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ) { - $http_ports[] = JETPACK_SIGNATURE__HTTP_PORT; - } - - if ( defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ) { - $https_ports[] = JETPACK_SIGNATURE__HTTPS_PORT; - } - - if ( $site_port ) { - return self::skipped_test( $name ); // Not currently testing for this situation. - } - - if ( is_ssl() && in_array( $server_port, $https_ports, true ) ) { - return self::passing_test( $name ); - } elseif ( in_array( $server_port, $http_ports, true ) ) { - return self::passing_test( $name ); - } else { - if ( is_ssl() ) { - $needed_constant = 'JETPACK_SIGNATURE__HTTPS_PORT'; - } else { - $needed_constant = 'JETPACK_SIGNATURE__HTTP_PORT'; - } - $message = __( 'The server port value is unexpected.', 'jetpack' ); - $resolution = __( 'Try adding the following to your wp-config.php file:', 'jetpack' ) . " define( '$needed_constant', $server_port );"; - return self::failing_test( $name, $message, $resolution ); - } - } - - /** - * Calls to WP.com to run the connection diagnostic testing suite. - * - * Intentionally added last as it will be skipped if any local failed conditions exist. - * - * @return array Test results. - */ - protected function last__wpcom_self_test() { - $name = 'test__wpcom_self_test'; - if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || Jetpack::is_staging_site() || ! $this->pass ) { - return self::skipped_test( $name ); - } - - $self_xml_rpc_url = site_url( 'xmlrpc.php' ); - - $testsite_url = Jetpack::fix_url_for_bad_hosts( JETPACK__API_BASE . 'testsite/1/?url=' ); - - add_filter( 'http_request_timeout', array( 'Jetpack_Debugger', 'jetpack_increase_timeout' ) ); - - $response = wp_remote_get( $testsite_url . $self_xml_rpc_url ); - - remove_filter( 'http_request_timeout', array( 'Jetpack_Debugger', 'jetpack_increase_timeout' ) ); - - if ( 200 === wp_remote_retrieve_response_code( $response ) ) { - return self::passing_test( $name ); - } else { - return self::failing_test( $name, __( 'Jetpack.com detected an error.', 'jetpack' ), __( 'Visit the Jetpack.com debugging page for more information or contact support.', 'jetpack' ) ); // @todo direct links. - } - } -} diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php deleted file mode 100644 index 599ccfda..00000000 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php +++ /dev/null @@ -1,530 +0,0 @@ -<?php -/** - * Jetpack Debugger functionality allowing for self-service diagnostic information. - * - * @package jetpack - */ - -/** - * Class Jetpack_Debugger - * - * A namespacing class for functionality related to the in-plugin diagnostic tooling. - */ -class Jetpack_Debugger { - - /** - * Determine the active plan and normalize it for the debugger results. - * - * @return string The plan slug prepended with "JetpackPlan" - */ - private static function what_jetpack_plan() { - $plan = Jetpack_Plan::get(); - $plan = ! empty( $plan['class'] ) ? $plan['class'] : 'undefined'; - return 'JetpackPlan' . $plan; - } - - /** - * Convert seconds to human readable time. - * - * A dedication function instead of using Core functionality to allow for output in seconds. - * - * @param int $seconds Number of seconds to convert to human time. - * - * @return string Human readable time. - */ - public static function seconds_to_time( $seconds ) { - $seconds = intval( $seconds ); - $units = array( - 'week' => WEEK_IN_SECONDS, - 'day' => DAY_IN_SECONDS, - 'hour' => HOUR_IN_SECONDS, - 'minute' => MINUTE_IN_SECONDS, - 'second' => 1, - ); - // specifically handle zero. - if ( 0 === $seconds ) { - return '0 seconds'; - } - $human_readable = ''; - foreach ( $units as $name => $divisor ) { - $quot = intval( $seconds / $divisor ); - if ( $quot ) { - $human_readable .= "$quot $name"; - $human_readable .= ( abs( $quot ) > 1 ? 's' : '' ) . ', '; - $seconds -= $quot * $divisor; - } - } - return substr( $human_readable, 0, -2 ); - } - - /** - * Returns 30 for use with a filter. - * - * To allow time for WP.com to run upstream testing, this function exists to increase the http_request_timeout value - * to 30. - * - * @return int 30 - */ - public static function jetpack_increase_timeout() { - return 30; // seconds. - } - - /** - * Disconnect Jetpack and redirect user to connection flow. - */ - public static function disconnect_and_redirect() { - if ( ! ( isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'jp_disconnect' ) ) ) { - return; - } - - if ( isset( $_GET['disconnect'] ) && $_GET['disconnect'] ) { - if ( Jetpack::is_active() ) { - Jetpack::disconnect(); - wp_safe_redirect( Jetpack::admin_url() ); - exit; - } - } - } - - /** - * Handles output to the browser for the in-plugin debugger. - */ - public static function jetpack_debug_display_handler() { - if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'jetpack' ) ); - } - - $user_id = get_current_user_id(); - $user_tokens = Jetpack_Options::get_option( 'user_tokens' ); - if ( is_array( $user_tokens ) && array_key_exists( $user_id, $user_tokens ) ) { - $user_token = $user_tokens[ $user_id ]; - } else { - $user_token = '[this user has no token]'; - } - unset( $user_tokens ); - - $debug_info = "\r\n"; - foreach ( array( - 'CLIENT_ID' => 'id', - 'BLOG_TOKEN' => 'blog_token', - 'MASTER_USER' => 'master_user', - 'CERT' => 'fallback_no_verify_ssl_certs', - 'TIME_DIFF' => 'time_diff', - 'VERSION' => 'version', - 'OLD_VERSION' => 'old_version', - 'PUBLIC' => 'public', - ) as $label => $option_name ) { - $debug_info .= "\r\n" . esc_html( $label . ': ' . Jetpack_Options::get_option( $option_name ) ); - } - - $debug_info .= "\r\n" . esc_html( 'USER_ID: ' . $user_id ); - $debug_info .= "\r\n" . esc_html( 'USER_TOKEN: ' . $user_token ); - $debug_info .= "\r\n" . esc_html( 'PHP_VERSION: ' . PHP_VERSION ); - $debug_info .= "\r\n" . esc_html( 'WORDPRESS_VERSION: ' . $GLOBALS['wp_version'] ); - $debug_info .= "\r\n" . esc_html( 'JETPACK__VERSION: ' . JETPACK__VERSION ); - $debug_info .= "\r\n" . esc_html( 'JETPACK__PLUGIN_DIR: ' . JETPACK__PLUGIN_DIR ); - $debug_info .= "\r\n" . esc_html( 'SITE_URL: ' . site_url() ); - $debug_info .= "\r\n" . esc_html( 'HOME_URL: ' . home_url() ); - $debug_info .= "\r\n" . esc_html( 'PLAN: ' . self::what_jetpack_plan() ); - - $debug_info .= "\r\n"; - - $debug_info .= "\r\n" . '-- SYNC Status -- '; - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-modules.php'; - $sync_module = Jetpack_Sync_Modules::get_module( 'full-sync' ); - if ( $sync_module ) { - $sync_statuses = $sync_module->get_status(); - $human_readable_sync_status = array(); - foreach ( $sync_statuses as $sync_status => $sync_status_value ) { - $human_readable_sync_status[ $sync_status ] = - in_array( $sync_status, array( 'started', 'queue_finished', 'send_started', 'finished' ), true ) - ? date( 'r', $sync_status_value ) : $sync_status_value; - } - /* translators: A string reporting status. Example: "started" */ - $debug_info .= "\r\n" . sprintf( esc_html__( 'Jetpack Sync Full Status: `%1$s`', 'jetpack' ), print_r( $human_readable_sync_status, 1 ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - } - - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php'; - - $queue = Jetpack_Sync_Sender::get_instance()->get_sync_queue(); - - /* translators: The number of items waiting to be synced. */ - $debug_info .= "\r\n" . sprintf( esc_html__( 'Sync Queue size: %1$s', 'jetpack' ), $queue->size() ); - /* translators: Human-readable time since the oldest item in the sync queue. */ - $debug_info .= "\r\n" . sprintf( esc_html__( 'Sync Queue lag: %1$s', 'jetpack' ), self::seconds_to_time( $queue->lag() ) ); - - $full_sync_queue = Jetpack_Sync_Sender::get_instance()->get_full_sync_queue(); - - /* translators: The number of items waiting to be synced. */ - $debug_info .= "\r\n" . sprintf( esc_html__( 'Full Sync Queue size: %1$s', 'jetpack' ), $full_sync_queue->size() ); - /* translators: Human-readable time since the oldest item in the sync queue. */ - $debug_info .= "\r\n" . sprintf( esc_html__( 'Full Sync Queue lag: %1$s', 'jetpack' ), self::seconds_to_time( $full_sync_queue->lag() ) ); - - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php'; - $idc_urls = array( - 'home' => Jetpack_Sync_Functions::home_url(), - 'siteurl' => Jetpack_Sync_Functions::site_url(), - 'WP_HOME' => Jetpack_Constants::is_defined( 'WP_HOME' ) ? Jetpack_Constants::get_constant( 'WP_HOME' ) : '', - 'WP_SITEURL' => Jetpack_Constants::is_defined( 'WP_SITEURL' ) ? Jetpack_Constants::get_constant( 'WP_SITEURL' ) : '', - ); - /* translators: List of URLs. */ - $debug_info .= "\r\n" . esc_html( sprintf( 'Sync IDC URLs: %s', wp_json_encode( $idc_urls ) ) ); - /* translators: String of a current option. */ - $debug_info .= "\r\n" . esc_html( sprintf( 'Sync error IDC option: %s', wp_json_encode( Jetpack_Options::get_option( 'sync_error_idc' ) ) ) ); - /* translators: String of a current option. */ - $debug_info .= "\r\n" . esc_html( sprintf( 'Sync IDC Optin: %s', (string) Jetpack::sync_idc_optin() ) ); - - $debug_info .= "\r\n"; - - foreach ( array( - 'HTTP_HOST', - 'SERVER_PORT', - 'HTTPS', - 'GD_PHP_HANDLER', - 'HTTP_AKAMAI_ORIGIN_HOP', - 'HTTP_CF_CONNECTING_IP', - 'HTTP_CLIENT_IP', - 'HTTP_FASTLY_CLIENT_IP', - 'HTTP_FORWARDED', - 'HTTP_FORWARDED_FOR', - 'HTTP_INCAP_CLIENT_IP', - 'HTTP_TRUE_CLIENT_IP', - 'HTTP_X_CLIENTIP', - 'HTTP_X_CLUSTER_CLIENT_IP', - 'HTTP_X_FORWARDED', - 'HTTP_X_FORWARDED_FOR', - 'HTTP_X_IP_TRAIL', - 'HTTP_X_REAL_IP', - 'HTTP_X_VARNISH', - 'REMOTE_ADDR', - ) as $header ) { - if ( isset( $_SERVER[ $header ] ) ) { - $debug_info .= "\r\n" . esc_html( $header . ': ' . $_SERVER[ $header ] ); - } - } - - $debug_info .= "\r\n" . esc_html( 'PROTECT_TRUSTED_HEADER: ' . wp_json_encode( get_site_option( 'trusted_ip_header' ) ) ); - - $debug_info .= "\r\n\r\nTEST RESULTS:\r\n\r\n"; - - $cxntests = new Jetpack_Cxn_Tests(); - ?> - <div class="wrap"> - <h2><?php esc_html_e( 'Debugging Center', 'jetpack' ); ?></h2> - <h3><?php esc_html_e( "Testing your site's compatibility with Jetpack...", 'jetpack' ); ?></h3> - <div class="jetpack-debug-test-container"> - <?php - if ( $cxntests->pass() ) { - echo '<div class="jetpack-tests-succeed">' . esc_html__( 'Your Jetpack setup looks a-okay!', 'jetpack' ) . '</div>'; - $debug_info .= "All tests passed.\r\n"; - $debug_info .= print_r( $cxntests->raw_results(), true ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - } else { - $failures = $cxntests->list_fails(); - foreach ( $failures as $fail ) { - echo '<div class="jetpack-test-error">'; - echo '<p><a class="jetpack-test-heading" href="#">' . esc_html( $fail['message'] ); - echo '<span class="noticon noticon-collapse"></span></a></p>'; - echo '<p class="jetpack-test-details">' . esc_html( $fail['resolution'] ) . '</p>'; - echo '</div>'; - - $debug_info .= "FAILED TESTS!\r\n"; - $debug_info .= $fail['name'] . ': ' . $fail['message'] . "\r\n"; - $debug_info .= print_r( $cxntests->raw_results(), true ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - } - } - ?> - </div> - <div class="entry-content"> - <h3><?php esc_html_e( 'Trouble with Jetpack?', 'jetpack' ); ?></h3> - <h4><?php esc_html_e( 'It may be caused by one of these issues, which you can diagnose yourself:', 'jetpack' ); ?></h4> - <ol> - <li><b><em> - <?php - esc_html_e( 'A known issue.', 'jetpack' ); - ?> - </em></b> - <?php - echo sprintf( - wp_kses( - /* translators: URLs to Jetpack support pages. */ - __( 'Some themes and plugins have <a href="%1$s" target="_blank">known conflicts</a> with Jetpack – check the <a href="%2$s" target="_blank">list</a>. (You can also browse the <a href="%3$s" target="_blank">Jetpack support pages</a> or <a href="%4$s" target="_blank">Jetpack support forum</a> to see if others have experienced and solved the problem.)', 'jetpack' ), - array( - 'a' => array( - 'href' => array(), - 'target' => array(), - ), - ) - ), - 'http://jetpack.com/support/getting-started-with-jetpack/known-issues/', - 'http://jetpack.com/support/getting-started-with-jetpack/known-issues/', - 'http://jetpack.com/support/', - 'https://wordpress.org/support/plugin/jetpack' - ); - ?> - </li> - <li><b><em><?php esc_html_e( 'An incompatible plugin.', 'jetpack' ); ?></em></b> <?php esc_html_e( "Find out by disabling all plugins except Jetpack. If the problem persists, it's not a plugin issue. If the problem is solved, turn your plugins on one by one until the problem pops up again – there's the culprit! Let us know, and we'll try to help.", 'jetpack' ); ?></li> - <li> - <b><em><?php esc_html_e( 'A theme conflict.', 'jetpack' ); ?></em></b> - <?php - $default_theme = wp_get_theme( WP_DEFAULT_THEME ); - - if ( $default_theme->exists() ) { - /* translators: %s is the name of a theme */ - echo esc_html( sprintf( __( "If your problem isn't known or caused by a plugin, try activating %s (the default WordPress theme).", 'jetpack' ), $default_theme->get( 'Name' ) ) ); - } else { - esc_html_e( "If your problem isn't known or caused by a plugin, try activating the default WordPress theme.", 'jetpack' ); - } - ?> - <?php esc_html_e( "If this solves the problem, something in your theme is probably broken – let the theme's author know.", 'jetpack' ); ?> - </li> - <li><b><em><?php esc_html_e( 'A problem with your XMLRPC file.', 'jetpack' ); ?></em></b> - <?php - echo sprintf( - wp_kses( - /* translators: The URL to the site's xmlrpc.php file. */ - __( 'Load your <a href="%s">XMLRPC file</a>. It should say “XML-RPC server accepts POST requests only.” on a line by itself.', 'jetpack' ), - array( 'a' => array( 'href' => array() ) ) - ), - esc_attr( site_url( 'xmlrpc.php' ) ) - ); - ?> - <ul> - <li>- <?php esc_html_e( "If it's not by itself, a theme or plugin is displaying extra characters. Try steps 2 and 3.", 'jetpack' ); ?></li> - <li>- <?php esc_html_e( 'If you get a 404 message, contact your web host. Their security may block XMLRPC.', 'jetpack' ); ?></li> - </ul> - </li> - <?php if ( current_user_can( 'jetpack_disconnect' ) && Jetpack::is_active() ) : ?> - <li> - <strong><em><?php esc_html_e( 'A connection problem with WordPress.com.', 'jetpack' ); ?></em></strong> - <?php - echo sprintf( - wp_kses( - /* translators: URL to disconnect and reconnect Jetpack. */ - __( 'Jetpack works by connecting to WordPress.com for a lot of features. Sometimes, when the connection gets messed up, you need to disconnect and reconnect to get things working properly. <a href="%s">Disconnect from WordPress.com</a>', 'jetpack' ), - array( - 'a' => array( - 'href' => array(), - 'class' => array(), - ), - ) - ), - esc_attr( - wp_nonce_url( - Jetpack::admin_url( - array( - 'page' => 'jetpack-debugger', - 'disconnect' => true, - ) - ), - 'jp_disconnect', - 'nonce' - ) - ) - ); - ?> - </li> - <?php endif; ?> - </ol> - <h4><?php esc_html_e( 'Still having trouble?', 'jetpack' ); ?></h4> - <p><b><em><?php esc_html_e( 'Ask us for help!', 'jetpack' ); ?></em></b> - <?php - echo sprintf( - wp_kses( - /* translators: URL for Jetpack support. */ - __( '<a href="%s">Contact our Happiness team</a>. When you do, please include the full debug information below.', 'jetpack' ), - array( 'a' => array( 'href' => array() ) ) - ), - 'https://jetpack.com/contact-support/' - ); - ?> - </p> - <hr /> - <?php if ( Jetpack::is_active() ) : ?> - <div id="connected-user-details"> - <h3><?php esc_html_e( 'More details about your Jetpack settings', 'jetpack' ); ?></h3> - <p> - <?php - printf( - wp_kses( - /* translators: %s is an e-mail address */ - __( 'The primary connection is owned by <strong>%s</strong>\'s WordPress.com account.', 'jetpack' ), - array( 'strong' => array() ) - ), - esc_html( Jetpack::get_master_user_email() ) - ); - ?> - </p> - </div> - <?php else : ?> - <div id="dev-mode-details"> - <p> - <?php - printf( - wp_kses( - /* translators: Link to a Jetpack support page. */ - __( 'Would you like to use Jetpack on your local development site? You can do so thanks to <a href="%s">Jetpack\'s development mode</a>.', 'jetpack' ), - array( 'a' => array( 'href' => array() ) ) - ), - 'https://jetpack.com/support/development-mode/' - ); - ?> - </p> - </div> - <?php endif; ?> - <?php - if ( - current_user_can( 'jetpack_manage_modules' ) - && ( Jetpack::is_development_mode() || Jetpack::is_active() ) - ) { - printf( - wp_kses( - '<p><a href="%1$s">%2$s</a></p>', - array( - 'a' => array( 'href' => array() ), - 'p' => array(), - ) - ), - esc_attr( Jetpack::admin_url( 'page=jetpack_modules' ) ), - esc_html__( 'Access the full list of Jetpack modules available on your site.', 'jetpack' ) - ); - } - ?> - </div> - <hr /> - <div id="toggle_debug_info"><?php esc_html_e( 'Advanced Debug Results', 'jetpack' ); ?></div> - <div id="debug_info_div"> - <h4><?php esc_html_e( 'Debug Info', 'jetpack' ); ?></h4> - <div id="debug_info"><pre><?php echo esc_html( $debug_info ); ?></pre></div> - </div> - </div> - <?php - } - - /** - * Outputs html needed within the <head> for the in-plugin debugger page. - */ - public static function jetpack_debug_admin_head() { - - Jetpack_Admin_Page::load_wrapper_styles(); - ?> - <style type="text/css"> - - .jetpack-debug-test-container { - margin-top: 20px; - margin-bottom: 30px; - } - - .jetpack-tests-succeed { - font-size: large; - color: #8BAB3E; - } - - .jetpack-test-details { - margin: 4px 6px; - padding: 10px; - overflow: auto; - display: none; - } - - .jetpack-test-error { - margin-bottom: 10px; - background: #FFEBE8; - border: solid 1px #C00; - border-radius: 3px; - } - - .jetpack-test-error p { - margin: 0; - padding: 0; - } - - p.jetpack-test-details { - margin: 4px 6px; - padding: 10px; - } - - .jetpack-test-error a.jetpack-test-heading { - padding: 4px 6px; - display: block; - text-decoration: none; - color: inherit; - } - - .jetpack-test-error .noticon { - float: right; - } - - .formbox { - margin: 0 0 25px 0; - } - - .formbox input[type="text"], .formbox input[type="email"], .formbox input[type="url"], .formbox textarea, #debug_info_div { - border: 1px solid #e5e5e5; - border-radius: 11px; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); - color: #666; - font-size: 14px; - padding: 10px; - width: 97%; - } - #debug_info_div { - border-radius: 0; - margin-top: 16px; - background: #FFF; - padding: 16px; - } - .formbox .contact-support input[type="submit"] { - float: right; - margin: 0 !important; - border-radius: 20px !important; - cursor: pointer; - font-size: 13pt !important; - height: auto !important; - margin: 0 0 2em 10px !important; - padding: 8px 16px !important; - background-color: #ddd; - border: 1px solid rgba(0,0,0,0.05); - border-top-color: rgba(255,255,255,0.1); - border-bottom-color: rgba(0,0,0,0.15); - color: #333; - font-weight: 400; - display: inline-block; - text-align: center; - text-decoration: none; - } - - .formbox span.errormsg { - margin: 0 0 10px 10px; - color: #d00; - display: none; - } - - .formbox.error span.errormsg { - display: block; - } - - #debug_info_div, #toggle_debug_info, #debug_info_div p { - font-size: 12px; - } - - #category_div ul li { - list-style-type: none; - } - - </style> - <script type="text/javascript"> - jQuery( document ).ready( function($) { - - $( '#debug_info' ).prepend( 'jQuery version: ' + jQuery.fn.jquery + "\r\n" ); - $( '#debug_form_info' ).prepend( 'jQuery version: ' + jQuery.fn.jquery + "\r\n" ); - - $( '.jetpack-test-error .jetpack-test-heading' ).on( 'click', function() { - $( this ).parents( '.jetpack-test-error' ).find( '.jetpack-test-details' ).slideToggle(); - return false; - } ); - - } ); - </script> - <?php - } -} diff --git a/plugins/jetpack/_inc/lib/functions.wp-notify.php b/plugins/jetpack/_inc/lib/functions.wp-notify.php deleted file mode 100644 index 6be0c3ac..00000000 --- a/plugins/jetpack/_inc/lib/functions.wp-notify.php +++ /dev/null @@ -1,353 +0,0 @@ -<?php - -if ( ! function_exists( 'wp_notify_postauthor' ) && Jetpack::is_active() ) : - /** - * Notify an author (and/or others) of a comment/trackback/pingback on a post. - * - * @since 1.0.0 - * - * @param int|WP_Comment $comment_id Comment ID or WP_Comment object. - * @param string $deprecated Not used - * @return bool True on completion. False if no email addresses were specified. - */ - function wp_notify_postauthor( $comment_id, $deprecated = null ) { - if ( null !== $deprecated ) { - _deprecated_argument( __FUNCTION__, '3.8.0' ); - } - - $comment = get_comment( $comment_id ); - - if ( empty( $comment ) || empty( $comment->comment_post_ID ) ) { - return false; - } - - $post = get_post( $comment->comment_post_ID ); - $author = get_userdata( $post->post_author ); - - // Who to notify? By default, just the post author, but others can be added. - $emails = array(); - if ( $author ) { - $emails[] = $author->user_email; - } - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $emails = apply_filters( 'comment_notification_recipients', $emails, $comment->comment_ID ); - $emails = array_filter( $emails ); - - // If there are no addresses to send the comment to, bail. - if ( ! count( $emails ) ) { - return false; - } - - // Facilitate unsetting below without knowing the keys. - $emails = array_flip( $emails ); - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $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 ) { - unset( $emails[ $author->user_email ] ); - } - - // The author moderated a comment on their own post - if ( $author && ! $notify_author && $post->post_author == get_current_user_id() ) { - unset( $emails[ $author->user_email ] ); - } - - // The post author is no longer a member of the blog - if ( $author && ! $notify_author && ! user_can( $post->post_author, 'read_post', $post->ID ) ) { - unset( $emails[ $author->user_email ] ); - } - - // If there's no email to send the comment to, bail, otherwise flip array back around for use below - if ( ! count( $emails ) ) { - return false; - } else { - $emails = array_flip( $emails ); - } - - $switched_locale = switch_to_locale( get_locale() ); - - $comment_author_domain = @gethostbyaddr( $comment->comment_author_IP ); - - // The blogname option is escaped with esc_html on the way into the database in sanitize_option - // we want to reverse this for the plain text arena of emails. - $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); - $comment_content = wp_specialchars_decode( $comment->comment_content ); - - function is_user_connected( $email ) { - $user = get_user_by( 'email', $email ); - return Jetpack::is_user_connected( $user->ID ); - } - - $moderate_on_wpcom = ! in_array( false, array_map( 'is_user_connected', $emails ) ); - - $primary_site_slug = Jetpack::build_raw_urls( get_home_url() ); - - switch ( $comment->comment_type ) { - case 'trackback': - /* translators: 1: Post title */ - $notify_message = sprintf( __( 'New trackback on your post "%s"' ), $post->post_title ) . "\r\n"; - /* translators: 1: Trackback/pingback website name, 2: website IP address, 3: website hostname */ - $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; - $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; - $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; - $notify_message .= __( 'You can see all trackbacks on this post here:' ) . "\r\n"; - /* translators: 1: blog name, 2: post title */ - $subject = sprintf( __( '[%1$s] Trackback: "%2$s"' ), $blogname, $post->post_title ); - break; - case 'pingback': - /* translators: 1: Post title */ - $notify_message = sprintf( __( 'New pingback on your post "%s"' ), $post->post_title ) . "\r\n"; - /* translators: 1: Trackback/pingback website name, 2: website IP address, 3: website hostname */ - $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; - $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; - $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; - $notify_message .= __( 'You can see all pingbacks on this post here:' ) . "\r\n"; - /* translators: 1: blog name, 2: post title */ - $subject = sprintf( __( '[%1$s] Pingback: "%2$s"' ), $blogname, $post->post_title ); - break; - default: // Comments - $notify_message = sprintf( __( 'New comment on your post "%s"' ), $post->post_title ) . "\r\n"; - /* translators: 1: comment author, 2: comment author's IP address, 3: comment author's hostname */ - $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; - $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n"; - $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; - $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; - $notify_message .= __( 'You can see all comments on this post here:' ) . "\r\n"; - /* translators: 1: blog name, 2: post title */ - $subject = sprintf( __( '[%1$s] Comment: "%2$s"' ), $blogname, $post->post_title ); - break; - } - - $notify_message .= $moderate_on_wpcom - ? "https://wordpress.com/comments/all/{$primary_site_slug}/{$comment->comment_post_ID}/\r\n\r\n" - : get_permalink( $comment->comment_post_ID ) . "#comments\r\n\r\n"; - - $notify_message .= sprintf( __( 'Permalink: %s' ), get_comment_link( $comment ) ) . "\r\n"; - - if ( user_can( $post->post_author, 'edit_comment', $comment->comment_ID ) ) { - if ( EMPTY_TRASH_DAYS ) { - $notify_message .= sprintf( - __( 'Trash it: %s' ), $moderate_on_wpcom - ? "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=trash" - : admin_url( "comment.php?action=trash&c={$comment->comment_ID}#wpbody-content" ) - ) . "\r\n"; - } else { - $notify_message .= sprintf( - __( 'Delete it: %s' ), $moderate_on_wpcom - ? "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=delete" - : admin_url( "comment.php?action=delete&c={$comment->comment_ID}#wpbody-content" ) - ) . "\r\n"; - } - $notify_message .= sprintf( - __( 'Spam it: %s' ), $moderate_on_wpcom ? - "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=spam" - : admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) - ) . "\r\n"; - } - - $wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', strtolower( $_SERVER['SERVER_NAME'] ) ); - - if ( '' == $comment->comment_author ) { - $from = "From: \"$blogname\" <$wp_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 ) { - $reply_to = "Reply-To: \"$comment->comment_author_email\" <$comment->comment_author_email>"; - } - } - - $message_headers = "$from\n" - . 'Content-Type: text/plain; charset="' . get_option( 'blog_charset' ) . "\"\n"; - - if ( isset( $reply_to ) ) { - $message_headers .= $reply_to . "\n"; - } - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $notify_message = apply_filters( 'comment_notification_text', $notify_message, $comment->comment_ID ); - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $subject = apply_filters( 'comment_notification_subject', $subject, $comment->comment_ID ); - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $message_headers = apply_filters( 'comment_notification_headers', $message_headers, $comment->comment_ID ); - - foreach ( $emails as $email ) { - @wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers ); - } - - if ( $switched_locale ) { - restore_previous_locale(); - } - - return true; - } -endif; - -if ( ! function_exists( 'wp_notify_moderator' ) && Jetpack::is_active() ) : - /** - * Notifies the moderator of the site about a new comment that is awaiting approval. - * - * @since 1.0.0 - * - * @global wpdb $wpdb WordPress database abstraction object. - * - * Uses the {@see 'notify_moderator'} filter to determine whether the site moderator - * should be notified, overriding the site setting. - * - * @param int $comment_id Comment ID. - * @return true Always returns true. - */ - function wp_notify_moderator( $comment_id ) { - global $wpdb; - - $maybe_notify = get_option( 'moderation_notify' ); - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_id ); - - if ( ! $maybe_notify ) { - return true; - } - - $comment = get_comment( $comment_id ); - $post = get_post( $comment->comment_post_ID ); - $user = get_userdata( $post->post_author ); - // Send to the administration and to the post author if the author can modify the comment. - $emails = array( get_option( 'admin_email' ) ); - if ( $user && user_can( $user->ID, 'edit_comment', $comment_id ) && ! empty( $user->user_email ) ) { - if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) { - $emails[] = $user->user_email; - } - } - - $switched_locale = switch_to_locale( get_locale() ); - - $comment_author_domain = @gethostbyaddr( $comment->comment_author_IP ); - $comments_waiting = $wpdb->get_var( "SELECT count(comment_ID) FROM $wpdb->comments WHERE comment_approved = '0'" ); - - // The blogname option is escaped with esc_html on the way into the database in sanitize_option - // we want to reverse this for the plain text arena of emails. - $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); - $comment_content = wp_specialchars_decode( $comment->comment_content ); - - switch ( $comment->comment_type ) { - case 'trackback': - /* translators: 1: Post title */ - $notify_message = sprintf( __( 'A new trackback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n"; - $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n"; - /* translators: 1: Trackback/pingback website name, 2: website IP address, 3: website hostname */ - $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; - /* translators: 1: Trackback/pingback/comment author URL */ - $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; - $notify_message .= __( 'Trackback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n"; - break; - case 'pingback': - /* translators: 1: Post title */ - $notify_message = sprintf( __( 'A new pingback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n"; - $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n"; - /* translators: 1: Trackback/pingback website name, 2: website IP address, 3: website hostname */ - $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; - /* translators: 1: Trackback/pingback/comment author URL */ - $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; - $notify_message .= __( 'Pingback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n"; - break; - default: // Comments - /* translators: 1: Post title */ - $notify_message = sprintf( __( 'A new comment on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n"; - $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n"; - /* translators: 1: Comment author name, 2: comment author's IP address, 3: comment author's hostname */ - $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n"; - /* translators: 1: Comment author URL */ - $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n"; - /* translators: 1: Trackback/pingback/comment author URL */ - $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n"; - /* translators: 1: Comment text */ - $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n"; - break; - } - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $emails = apply_filters( 'comment_moderation_recipients', $emails, $comment_id ); - - function is_user_connected( $email ) { - $user = get_user_by( 'email', $email ); - return Jetpack::is_user_connected( $user->ID ); - } - - $moderate_on_wpcom = ! in_array( false, array_map( 'is_user_connected', $emails ) ); - - $primary_site_slug = Jetpack::build_raw_urls( get_home_url() ); - - /* translators: Comment moderation. 1: Comment action URL */ - $notify_message .= sprintf( - __( 'Approve it: %s' ), $moderate_on_wpcom - ? "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=approve" - : admin_url( "comment.php?action=approve&c={$comment_id}#wpbody-content" ) - ) . "\r\n"; - - if ( EMPTY_TRASH_DAYS ) { - /* translators: Comment moderation. 1: Comment action URL */ - $notify_message .= sprintf( - __( 'Trash it: %s' ), $moderate_on_wpcom - ? "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=trash" - : admin_url( "comment.php?action=trash&c={$comment_id}#wpbody-content" ) - ) . "\r\n"; - } else { - /* translators: Comment moderation. 1: Comment action URL */ - $notify_message .= sprintf( - __( 'Delete it: %s' ), $moderate_on_wpcom - ? "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=delete" - : admin_url( "comment.php?action=delete&c={$comment_id}#wpbody-content" ) - ) . "\r\n"; - } - - /* translators: Comment moderation. 1: Comment action URL */ - $notify_message .= sprintf( - __( 'Spam it: %s' ), $moderate_on_wpcom - ? "https://wordpress.com/comment/{$primary_site_slug}/{$comment_id}?action=spam" - : admin_url( "comment.php?action=spam&c={$comment_id}#wpbody-content" ) - ) . "\r\n"; - - /* translators: Comment moderation. 1: Number of comments awaiting approval */ - $notify_message .= sprintf( - _n( - 'Currently %s comment is waiting for approval. Please visit the moderation panel:', - 'Currently %s comments are waiting for approval. Please visit the moderation panel:', $comments_waiting - ), number_format_i18n( $comments_waiting ) - ) . "\r\n"; - - $notify_message .= $moderate_on_wpcom - ? "https://wordpress.com/comments/pending/{$primary_site_slug}/" - : admin_url( 'edit-comments.php?comment_status=moderated#wpbody-content' ) . "\r\n"; - - /* translators: Comment moderation notification email subject. 1: Site name, 2: Post title */ - $subject = sprintf( __( '[%1$s] Please moderate: "%2$s"' ), $blogname, $post->post_title ); - $message_headers = ''; - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $notify_message = apply_filters( 'comment_moderation_text', $notify_message, $comment_id ); - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $subject = apply_filters( 'comment_moderation_subject', $subject, $comment_id ); - - /** This filter is documented in core/src/wp-includes/pluggable.php */ - $message_headers = apply_filters( 'comment_moderation_headers', $message_headers, $comment_id ); - - foreach ( $emails as $email ) { - @wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers ); - } - - if ( $switched_locale ) { - restore_previous_locale(); - } - - return true; - } -endif; diff --git a/plugins/jetpack/_inc/lib/icalendar-reader.php b/plugins/jetpack/_inc/lib/icalendar-reader.php deleted file mode 100644 index f7e047f9..00000000 --- a/plugins/jetpack/_inc/lib/icalendar-reader.php +++ /dev/null @@ -1,913 +0,0 @@ -<?php - -/** - * Gets and renders iCal feeds for the Upcoming Events widget and shortcode - */ - -class iCalendarReader { - - public $todo_count = 0; - public $event_count = 0; - public $cal = array(); - public $_lastKeyWord = ''; - public $timezone = null; - - /** - * Class constructor - * - * @return void - */ - public function __construct() {} - - /** - * Return an array of events - * - * @param string $url (default: '') - * @return array | false on failure - */ - public function get_events( $url = '', $count = 5 ) { - $count = (int) $count; - $transient_id = 'icalendar_vcal_' . md5( $url ) . '_' . $count; - - $vcal = get_transient( $transient_id ); - - if ( ! empty( $vcal ) ) { - if ( isset( $vcal['TIMEZONE'] ) ) - $this->timezone = $this->timezone_from_string( $vcal['TIMEZONE'] ); - - if ( isset( $vcal['VEVENT'] ) ) { - $vevent = $vcal['VEVENT']; - - if ( $count > 0 ) - $vevent = array_slice( $vevent, 0, $count ); - - $this->cal['VEVENT'] = $vevent; - - return $this->cal['VEVENT']; - } - } - - if ( ! $this->parse( $url ) ) - return false; - - $vcal = array(); - - if ( $this->timezone ) { - $vcal['TIMEZONE'] = $this->timezone->getName(); - } else { - $this->timezone = $this->timezone_from_string( '' ); - } - - if ( ! empty( $this->cal['VEVENT'] ) ) { - $vevent = $this->cal['VEVENT']; - - // check for recurring events - // $vevent = $this->add_recurring_events( $vevent ); - - // remove before caching - no sense in hanging onto the past - $vevent = $this->filter_past_and_recurring_events( $vevent ); - - // order by soonest start date - $vevent = $this->sort_by_recent( $vevent ); - - $vcal['VEVENT'] = $vevent; - } - - set_transient( $transient_id, $vcal, HOUR_IN_SECONDS ); - - if ( !isset( $vcal['VEVENT'] ) ) - return false; - - if ( $count > 0 ) - return array_slice( $vcal['VEVENT'], 0, $count ); - - return $vcal['VEVENT']; - } - - function apply_timezone_offset( $events ) { - if ( ! $events ) { - return $events; - } - - // get timezone offset from the timezone name. - $timezone_name = get_option( 'timezone_string' ); - if ( $timezone_name ) { - $timezone = new DateTimeZone( $timezone_name ); - $timezone_offset_interval = false; - } else { - // If the timezone isn't set then the GMT offset must be set. - // generate a DateInterval object from the timezone offset - $gmt_offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS; - $timezone_offset_interval = date_interval_create_from_date_string( "{$gmt_offset} seconds" ); - $timezone = new DateTimeZone( 'UTC' ); - } - - $offsetted_events = array(); - - foreach ( $events as $event ) { - // 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 ); - $start_time->setTimeZone( $timezone ); - - $end_time = preg_replace( '/Z$/', '', $event['DTEND'] ); - $end_time = new DateTime( $end_time, $this->timezone ); - $end_time->setTimeZone( $timezone ); - - if ( $timezone_offset_interval ) { - $start_time->add( $timezone_offset_interval ); - $end_time->add( $timezone_offset_interval ); - } - - $event['DTSTART'] = $start_time->format( 'YmdHis\Z' ); - $event['DTEND'] = $end_time->format( 'YmdHis\Z' ); - } - - $offsetted_events[] = $event; - } - - return $offsetted_events; - } - - protected function filter_past_and_recurring_events( $events ) { - $upcoming = array(); - $set_recurring_events = array(); - $recurrences = array(); - /** - * This filter allows any time to be passed in for testing or changing timezones, etc... - * - * @module widgets - * - * @since 3.4.0 - * - * @param object time() A time object. - */ - $current = apply_filters( 'ical_get_current_time', time() ); - - foreach ( $events as $event ) { - - $date_from_ics = strtotime( $event['DTSTART'] ); - if ( isset( $event['DTEND'] ) ) { - $duration = strtotime( $event['DTEND'] ) - strtotime( $event['DTSTART'] ); - } else { - $duration = 0; - } - - if ( isset( $event['RRULE'] ) && $this->timezone->getName() && 8 != strlen( $event['DTSTART'] ) ) { - try { - $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['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); - } catch ( Exception $e ) { - // 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->setTimeZone( new DateTimeZone( $this->timezone->getName() ) ); - if ( 8 == strlen( $event['DTSTART'] ) ) { - $exdates[] = $adjusted_time->format( 'Ymd' ); - } else { - $exdates[] = $adjusted_time->format( 'Ymd\THis' ); - } - } catch ( Exception $e ) { - // Invalid argument to DateTime - } - } - $event['EXDATE'] = $exdates; - } else { - $event['EXDATE'] = array(); - } - } - - if ( ! isset( $event['DTSTART'] ) ) { - continue; - } - - // 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 ) ) { - - // 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; - } - - $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 ); - - // Used to bound event checks - $echo_limit = 10; - $noop = false; - - // Set bydays for the event - $weekdays = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); - $bydays = $weekdays; - - // Calculate a recent start date for incrementing depending on the frequency and interval - switch ( $rrule_array['FREQ'] ) { - - case 'DAILY': - $frequency = 'day'; - $echo_limit = 10; - - if ( $date_from_ics >= $current ) { - $recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) ); - } else { - // 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'] ) ); - } else { - $noop = true; - } - } else { - $recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' days', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); - } - } - break; - - case 'WEEKLY': - $frequency = 'week'; - $echo_limit = 4; - - // 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 ) ); - } - $bydays = explode( ',', $rrule_array['BYDAY'] ); - - if ( $date_from_ics >= $current ) { - $recurring_event_date_start = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) ); - } else { - // 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 - $recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' weeks', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); - } else { - $noop = true; - } - } else { - $recurring_event_date_start = date( 'Ymd', strtotime( '+ ' . ( $interval * $catchup ) . ' weeks', strtotime( $event['DTSTART'] ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); - } - } - - // 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'] ) ); - } - break; - - case 'MONTHLY': - $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 - 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' ); - $event_date_desc = "{$day_cardinals[$day_number]} {$weekdays[$week_day]} of "; - } else { - $event_date_desc = date( 'd ', strtotime( $event['DTSTART'] ) ); - } - - // Interval only - if ( $interval > 1 ) { - $catchup = 0; - $maybe = strtotime( $event['DTSTART'] ); - while ( $maybe < $current ) { - $maybe = strtotime( '+ ' . ( $interval * $catchup ) . ' months', strtotime( $event['DTSTART'] ) ); - $catchup++; - } - $recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', strtotime( '+ ' . ( $interval * ( $catchup - 1 ) ) . ' months', strtotime( $event['DTSTART'] ) ) ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); - } else { - $recurring_event_date_start = date( 'Ymd', strtotime( $event_date_desc . date( 'F Y', $current ) ) ) . date( '\THis', strtotime( $event['DTSTART'] ) ); - } - - // 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'] ) ); - } else { - try { - $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 - } - } - } - } - break; - - case 'YEARLY': - $frequency = 'year'; - $echo_limit = 1; - - if ( $date_from_ics >= $current ) { - $recurring_event_date_start = date( "Ymd\THis", strtotime( $event['DTSTART'] ) ); - } else { - $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 - } - } - } - break; - - default: - $frequency = false; - } - - if ( $frequency !== false && ! $noop ) { - $count_counter = 1; - - // If no COUNT limit, go to 10 - if ( empty( $rrule_count ) ) { - $rrule_count = 10; - } - - // 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 ) { - $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'] ) ) { - $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'] ) ) { - $event['DTSTART'] = date( 'Ymd', $event_start_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 ); - } - 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['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); - } catch ( Exception $e ) { - // Invalid argument to DateTime - } - } - $upcoming[] = $event; - $count_counter++; - } - - // Move forward one day - $byday_event_date_start = strtotime( date( 'Ymd\T', strtotime( '+ 1 day', $event_start_timestamp ) ) . $start_time ); - } - - // 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'] ) ) { - $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'] ) ) { - $event['DTSTART'] = date( 'Ymd', $event_start_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; - } - 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['DTEND'] = date( 'Ymd\THis', strtotime( $event['DTSTART'] ) + $duration ); - } catch ( Exception $e ) { - // Invalid argument to DateTime - } - } - $upcoming[] = $event; - $count_counter++; - } - } - - // 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'] ) ) { - $event['DTSTART'] = date( 'Ymd', $next_start_timestamp ); - $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 ); - } - - // Move recurring event date forward - $recurring_event_date_start = $event['DTSTART']; - } - $set_recurring_events[] = $uid; - - } - - } else { - // Process normal events - if ( strtotime( isset( $event['DTEND'] ) ? $event['DTEND'] : $event['DTSTART'] ) >= $current ) { - $upcoming[] = $event; - } - } - } - return $upcoming; - } - - /** - * Parse events from an iCalendar feed - * - * @param string $url (default: '') - * @return array | false on failure - */ - public function parse( $url = '' ) { - $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 ) ) - 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 ) ); - if ( 200 !== wp_remote_retrieve_response_code( $r ) ) { - // 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 ) ) - return false; - - $body = str_replace( "\r\n", "\n", $body ); - $lines = preg_split( "/\n(?=[A-Z])/", $body ); - - if ( empty( $lines ) ) - return false; - - if ( false === stristr( $lines[0], 'BEGIN:VCALENDAR' ) ) - return false; - - foreach ( $lines as $line ) { - $add = $this->key_value_from_string( $line ); - if ( ! $add ) { - $this->add_component( $type, false, $line ); - continue; - } - list( $keyword, $value ) = $add; - - switch ( $keyword ) { - case 'BEGIN': - case 'END': - switch ( $line ) { - case 'BEGIN:VTODO': - $this->todo_count++; - $type = 'VTODO'; - break; - case 'BEGIN:VEVENT': - $this->event_count++; - $type = 'VEVENT'; - break; - case 'BEGIN:VCALENDAR': - case 'BEGIN:DAYLIGHT': - case 'BEGIN:VTIMEZONE': - case 'BEGIN:STANDARD': - $type = $value; - break; - case 'END:VTODO': - case 'END:VEVENT': - case 'END:VCALENDAR': - case 'END:DAYLIGHT': - case 'END:VTIMEZONE': - case 'END:STANDARD': - $type = 'VCALENDAR'; - break; - } - break; - case 'TZID': - if ( 'VTIMEZONE' == $type && ! $this->timezone ) - $this->timezone = $this->timezone_from_string( $value ); - break; - case 'X-WR-TIMEZONE': - if ( ! $this->timezone ) - $this->timezone = $this->timezone_from_string( $value ); - break; - default: - $this->add_component( $type, $keyword, $value ); - break; - } - } - - // Filter for RECURRENCE-IDs - $recurrences = array(); - if ( array_key_exists( 'VEVENT', $this->cal ) ) { - foreach ( $this->cal['VEVENT'] as $event ) { - if ( isset( $event['RECURRENCE-ID'] ) ) { - $recurrences[] = $event; - } - } - 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'] ) ) { - $this->cal['VEVENT'][ $i ]['EXDATE'][] = $recurrence['RECURRENCE-ID']; - break; - } - } - } - } - - return $this->cal; - } - - /** - * Parse key:value from a string - * - * @param string $text (default: '') - * @return array - */ - public function key_value_from_string( $text = '' ) { - preg_match( '/([^:]+)(;[^:]+)?[:]([\w\W]*)/', $text, $matches ); - - if ( 0 == count( $matches ) ) - return false; - - return array( $matches[1], $matches[3] ); - } - - /** - * Convert a timezone name into a timezone object. - * - * @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 ) { - try { - $timezone = new DateTimeZone( $text ); - } catch ( Exception $e ) { - $blog_timezone = get_option( 'timezone_string' ); - if ( ! $blog_timezone ) { - $blog_timezone = 'Etc/UTC'; - } - - $timezone = new DateTimeZone( $blog_timezone ); - } - - return $timezone; - } - - /** - * Add a component to the calendar array - * - * @param string $component (default: '') - * @param string $keyword (default: '') - * @param string $value (default: '') - * @return void - */ - public function add_component( $component = '', $keyword = '', $value = '' ) { - if ( false == $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; - } - } - - /* - * Some events have a specific timezone set in their start/end date, - * and it may or may not be different than the calendar timzeone. - * Valid formats include: - * DTSTART;TZID=Pacific Standard Time:20141219T180000 - * DTEND;TZID=Pacific Standard Time:20141219T200000 - * EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z - * EXDATE;VALUE=DATE:2015050 - * EXDATE;TZID=America/New_York:20150424T170000 - * EXDATE;TZID=Pacific Standard Time:20120615T140000,20120629T140000,20120706T140000 - */ - - // Always store EXDATE as an array - if ( stristr( $keyword, 'EXDATE' ) ) { - $value = explode( ',', $value ); - } - - // 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 ) ) { - $tparam = $keyword[1]; - - if ( strpos( $tparam, "TZID" ) !== false ) { - $tzid = $this->timezone_from_string( str_replace( 'TZID=', '', $tparam ) ); - } - } - - // 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'); - } catch ( Exception $e ) { - // Invalid argument to DateTime - return; - } - } - $value = $adjusted_times; - } - - // Format for adding to event - $keyword = $keyword[0]; - if ( 'EXDATE' != $keyword ) { - $value = implode( (array) $value ); - } - } - - foreach ( (array) $value as $v ) { - switch ($component) { - case 'VTODO': - 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 ) { - $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ][] = $v; - } else { - $this->cal[ $component ][ $this->event_count - 1 ][ $keyword ] = $v; - } - break; - default: - $this->cal[ $component ][ $keyword ] = $v; - break; - } - } - $this->last_keyword = $keyword; - } - - /** - * Escape strings with wp_kses, allow links - * - * @param string $string (default: '') - * @return string - */ - public function escape( $string = '' ) { - // 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() - ) - ); - - $allowed_tags = ''; - foreach ( array_keys( $allowed_html ) as $tag ) { - $allowed_tags .= "<{$tag}>"; - } - - // Running strip_tags() first with allowed tags to get rid of remaining gallery markup, etc - // because wp_kses() would only htmlentity'fy that. Then still running wp_kses(), for extra - // safety and good measure. - return wp_kses( strip_tags( $string, $allowed_tags ), $allowed_html ); - } - - /** - * Render the events - * - * @param string $url (default: '') - * @param string $context (default: 'widget') or 'shortcode' - * @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 - ) ); - - $events = $this->get_events( $url, $args['number'] ); - $events = $this->apply_timezone_offset( $events ); - - if ( empty( $events ) ) - return false; - - ob_start(); - - 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> - <?php if ( ! empty( $event['LOCATION'] ) ) : ?> - <span class="event-location"><?php echo $this->escape( stripslashes( $event['LOCATION'] ) ); ?></span> - <?php endif; ?> - <?php if ( ! empty( $event['DESCRIPTION'] ) ) : ?> - <span class="event-description"><?php echo wp_trim_words( $this->escape( stripcslashes( $event['DESCRIPTION'] ) ) ); ?></span> - <?php endif; ?> - </li> - <?php endforeach; ?> - </ul> - <?php endif; - - if ( 'shortcode' == $args['context'] ) : ?> - <table class="upcoming-events"> - <thead> - <tr> - <th><?php esc_html_e( 'Location', 'jetpack' ); ?></th> - <th><?php esc_html_e( 'When', 'jetpack' ); ?></th> - <th><?php esc_html_e( 'Summary', 'jetpack' ); ?></th> - <th><?php esc_html_e( 'Description', 'jetpack' ); ?></th> - </tr> - </thead> - <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> - </tr> - <?php endforeach; ?> - </tbody> - </table> - <?php endif; - - $rendered = ob_get_clean(); - - if ( empty( $rendered ) ) - return false; - - return $rendered; - } - - 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; - - $all_day = ( 8 == strlen( $event['DTSTART'] ) ); - - if ( !$all_day && $this->timezone ) { - try { - $start_time = new DateTime( $event['DTSTART'] ); - $timezone_offset = $this->timezone->getOffset( $start_time ); - $start += $timezone_offset; - - if ( $end ) { - $end += $timezone_offset; - } - } catch ( Exception $e ) { - // Invalid argument to DateTime - } - } - $single_day = $end ? ( $end - $start ) <= DAY_IN_SECONDS : true; - - /* translators: Date and time */ - $date_with_time = __( '%1$s at %2$s' , 'jetpack' ); - /* translators: Two dates with a separator */ - $two_dates = __( '%1$s – %2$s' , 'jetpack' ); - - // 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 ) ); - - // single day, timed - if ( $single_day && ! $all_day && false !== $end ) - $date = sprintf( $two_dates, $date, date_i18n( $time_format, $end ) ); - - // multi-day - if ( ! $single_day ) { - - if ( $all_day ) { - // 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 ) ); - } - - $date = sprintf( $two_dates, $date, $end_date ); - - } - - return $date; - } - - protected function sort_by_recent( $list ) { - $dates = $sorted_list = array(); - - foreach ( $list as $key => $row ) { - $date = $row['DTSTART']; - // pad some time onto an all day date - if ( 8 === strlen( $date ) ) - $date .= 'T000000Z'; - $dates[$key] = $date; - } - asort( $dates ); - foreach( $dates as $key => $value ) { - $sorted_list[$key] = $list[$key]; - } - unset($list); - return $sorted_list; - } - -} - - -/** - * Wrapper function for iCalendarReader->get_events() - * - * @param string $url (default: '') - * @return array - */ -function icalendar_get_events( $url = '', $count = 5 ) { - // Find your calendar's address http://support.google.com/calendar/bin/answer.py?hl=en&answer=37103 - $ical = new iCalendarReader(); - return $ical->get_events( $url, $count ); -} - -/** - * Wrapper function for iCalendarReader->render() - * - * @param string $url (default: '') - * @param string $context (default: 'widget') or 'shortcode' - * @return mixed bool|string false on failure, rendered HTML string on success. - */ -function icalendar_render_events( $url = '', $args = array() ) { - $ical = new iCalendarReader(); - return $ical->render( $url, $args ); -} 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 d3481ce5..00000000 --- a/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-builder.php +++ /dev/null @@ -1,341 +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 - * - * 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 $decays = array(); - protected $scripts = array(); - protected $functions_max_boost = 2.0; - protected $functions_score_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(); - - //////////////////////////////////// - // Methods for building a query - - public function add_filter( $filter ) { - $this->es_filters[] = $filter; - } - - 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; - } - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * 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; - } - - public function add_max_boost_to_functions( $boost ) { - $this->functions_max_boost = $boost; - } - - public function add_boost_to_query_bool( $boost ) { - $this->query_bool_boost = $boost; - } - - public function add_aggs( $aggs_name, $aggs ) { - $this->aggs_query = true; - $this->aggs[$aggs_name] = $aggs; - } - - 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; - } - - public function add_bucketed_query( $name, $query ) { - $this->_add_bucket_filter( $name, $query ); - - $this->add_query( $query, 'dis_max' ); - } - - 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' ); - } - - public function add_bucket_sub_aggs( $agg ) { - $this->bucket_sub_aggs = array_merge( $this->bucket_sub_aggs, $agg ); - } - - 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 ) ) { - if ( 1 == count( $this->must_queries ) ) { - $query = $this->must_queries[0]; - } else { - $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 ) { - $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, - ), - ); - } // 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 2b7710cb..00000000 --- a/plugins/jetpack/_inc/lib/jetpack-wpes-query-builder/jetpack-wpes-query-parser.php +++ /dev/null @@ -1,683 +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 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(); - - /////////////////////////////////////////////////////// - // 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/markdown/README.md b/plugins/jetpack/_inc/lib/markdown/README.md deleted file mode 100644 index 45f298d1..00000000 --- a/plugins/jetpack/_inc/lib/markdown/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Markdown parsing library - -Contains two libraries: - -* `/extra` - - Gives you `MardownExtra_Parser` and `Markdown_Parser` - - Docs at http://michelf.ca/projects/php-markdown/extra/ - -* `/gfm` -- Github Flavored Markdown - - Gives you `WPCom_GHF_Markdown_Parser` - - It has the same interface as `MarkdownExtra_Parser` - - Adds support for fenced code blocks: https://help.github.com/articles/creating-and-highlighting-code-blocks/#fenced-code-blocks - - By default it replaces them with a code shortcode - - You can change this using the `$use_code_shortcode` member variable - - You can change the code shortcode wrapping with `$shortcode_start` and `$shortcode_end` member variables - - The `$preserve_shortcodes` member variable will preserve all registered shortcodes untouched. Requires WordPress to be loaded for `get_shortcode_regex()` - - The `$preserve_latex` member variable will preserve oldskool $latex yer-latex$ codes untouched. - - The `$strip_paras` member variable will strip <p> tags because that's what WordPress likes. - - See `WPCom_GHF_Markdown_Parser::__construct()` for how the above member variable defaults are set. diff --git a/plugins/jetpack/_inc/lib/markdown/extra.php b/plugins/jetpack/_inc/lib/markdown/extra.php deleted file mode 100644 index fd85a3c8..00000000 --- a/plugins/jetpack/_inc/lib/markdown/extra.php +++ /dev/null @@ -1,3207 +0,0 @@ -<?php -# -# Markdown Extra - A text-to-HTML conversion tool for web writers -# -# PHP Markdown & Extra -# Copyright (c) 2004-2013 Michel Fortin -# <http://michelf.ca/projects/php-markdown/> -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# <http://daringfireball.net/projects/markdown/> -# -# Tweaked to remove WordPress interface - - -define( 'MARKDOWN_VERSION', "1.0.2" ); # 29 Nov 2013 -define( 'MARKDOWNEXTRA_VERSION', "1.2.8" ); # 29 Nov 2013 - - -# -# Global default settings: -# - -# Change to ">" for HTML output -@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />"); - -# Define the width of a tab for code blocks. -@define( 'MARKDOWN_TAB_WIDTH', 4 ); - -# Optional title attribute for footnote links and backlinks. -@define( 'MARKDOWN_FN_LINK_TITLE', "" ); -@define( 'MARKDOWN_FN_BACKLINK_TITLE', "" ); - -# Optional class attribute for footnote links and backlinks. -@define( 'MARKDOWN_FN_LINK_CLASS', "jetpack-footnote" ); -@define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); - -# Optional class prefix for fenced code block. -@define( 'MARKDOWN_CODE_CLASS_PREFIX', "language-" ); - -# Class attribute for code blocks goes on the `code` tag; -# setting this to true will put attributes on the `pre` tag instead. -@define( 'MARKDOWN_CODE_ATTR_ON_PRE', false ); - - - -### Standard Function Interface ### - -@define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' ); - -function Markdown($text) { -# -# Initialize the parser and return the result of its transform method. -# - # Setup static parser variable. - static $parser; - if (!isset($parser)) { - $parser_class = MARKDOWN_PARSER_CLASS; - $parser = new $parser_class; - } - - # Transform text using parser. - return $parser->transform($text); -} - -/** - * Returns the length of $text loosely counting the number of UTF-8 characters with regular expression. - * Used by the Markdown_Parser class when mb_strlen is not available. - * - * @since 5.9 - * - * @return string Length of the multibyte string - * - */ -function jetpack_utf8_strlen( $text ) { - return preg_match_all( "/[\\x00-\\xBF]|[\\xC0-\\xFF][\\x80-\\xBF]*/", $text, $m ); -} - -# -# Markdown Parser Class -# - -class Markdown_Parser { - - ### Configuration Variables ### - - # Change to ">" for HTML output. - public $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; - public $tab_width = MARKDOWN_TAB_WIDTH; - - # Change to `true` to disallow markup or entities. - public $no_markup = false; - public $no_entities = false; - - # Predefined urls and titles for reference links and images. - public $predef_urls = array(); - public $predef_titles = array(); - - - ### Parser Implementation ### - - # Regex to match balanced [brackets]. - # Needed to insert a maximum bracked depth while converting to PHP. - public $nested_brackets_depth = 6; - public $nested_brackets_re; - - public $nested_url_parenthesis_depth = 4; - public $nested_url_parenthesis_re; - - # Table of hash values for escaped characters: - public $escape_chars = '\`*_{}[]()>#+-.!'; - public $escape_chars_re; - - - function __construct() { - # - # Constructor function. Initialize appropriate member variables. - # - $this->_initDetab(); - $this->prepareItalicsAndBold(); - - $this->nested_brackets_re = - str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). - str_repeat('\])*', $this->nested_brackets_depth); - - $this->nested_url_parenthesis_re = - str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). - str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); - - $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; - - # Sort document, block, and span gamut in ascendent priority order. - asort($this->document_gamut); - asort($this->block_gamut); - asort($this->span_gamut); - } - - - # Internal hashes used during transformation. - public $urls = array(); - public $titles = array(); - public $html_hashes = array(); - - # Status flag to avoid invalid nesting. - public $in_anchor = false; - - - function setup() { - # - # Called before the transformation process starts to setup parser - # states. - # - # Clear global hashes. - $this->urls = $this->predef_urls; - $this->titles = $this->predef_titles; - $this->html_hashes = array(); - - $this->in_anchor = false; - } - - function teardown() { - # - # Called after the transformation process to clear any variable - # which may be taking up memory unnecessarly. - # - $this->urls = array(); - $this->titles = array(); - $this->html_hashes = array(); - } - - - function transform($text) { - # - # Main function. Performs some preprocessing on the input text - # and pass it through the document gamut. - # - $this->setup(); - - # Remove UTF-8 BOM and marker character in input, if present. - $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); - - # Standardize line endings: - # DOS to Unix and Mac to Unix - $text = preg_replace('{\r\n?}', "\n", $text); - - # Make sure $text ends with a couple of newlines: - $text .= "\n\n"; - - # Convert all tabs to spaces. - $text = $this->detab($text); - - # Turn block-level HTML blocks into hash entries - $text = $this->hashHTMLBlocks($text); - - # Strip any lines consisting only of spaces and tabs. - # This makes subsequent regexen easier to write, because we can - # match consecutive blank lines with /\n+/ instead of something - # contorted like /[ ]*\n+/ . - $text = preg_replace('/^[ ]+$/m', '', $text); - - # Run document gamut methods. - foreach ($this->document_gamut as $method => $priority) { - $text = $this->$method($text); - } - - $this->teardown(); - - return $text . "\n"; - } - - public $document_gamut = array( - # Strip link definitions, store in hashes. - "stripLinkDefinitions" => 20, - - "runBasicBlockGamut" => 30, - ); - - - function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - (?: - <(.+?)> # url = $2 - | - (\S+?) # url = $3 - ) - [ ]* - \n? # maybe one newline - [ ]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.*?) # title = $4 - [")] - [ ]* - )? # title is optional - (?:\n+|\Z) - }xm', - array(&$this, '_stripLinkDefinitions_callback'), - $text); - return $text; - } - function _stripLinkDefinitions_callback($matches) { - $link_id = strtolower($matches[1]); - $url = $matches[2] == '' ? $matches[3] : $matches[2]; - $this->urls[$link_id] = $url; - $this->titles[$link_id] =& $matches[4]; - return ''; # String that will replace the block - } - - - function hashHTMLBlocks($text) { - if ($this->no_markup) return $text; - - $less_than_tab = $this->tab_width - 1; - - # Hashify HTML blocks: - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap <p>s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded: - # - # * List "a" is made of tags which can be both inline or block-level. - # These will be treated block-level when the start tag is alone on - # its line, otherwise they're not matched here and will be taken as - # inline later. - # * List "b" is made of tags which are always block-level; - # - $block_tags_a_re = 'ins|del'; - $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. - 'script|noscript|form|fieldset|iframe|math|svg|'. - 'article|section|nav|aside|hgroup|header|footer|'. - 'figure'; - - # Regular expression for the content of a block tag. - $nested_tags_level = 4; - $attr = ' - (?> # optional tag attributes - \s # starts with whitespace - (?> - [^>"/]+ # text outside quotes - | - /+(?!>) # slash not followed by ">" - | - "[^"]*" # text inside double quotes (tolerate ">") - | - \'[^\']*\' # text inside single quotes (tolerate ">") - )* - )? - '; - $content = - str_repeat(' - (?> - [^<]+ # content without tag - | - <\2 # nested opening tag - '.$attr.' # attributes - (?> - /> - | - >', $nested_tags_level). # end of opening tag - '.*?'. # last level nested tag content - str_repeat(' - </\2\s*> # closing nested tag - ) - | - <(?!/\2\s*> # other tags with a different name - ) - )*', - $nested_tags_level); - $content2 = str_replace('\2', '\3', $content); - - # First, look for nested blocks, e.g.: - # <div> - # <div> - # tags for inner block must be indented. - # </div> - # </div> - # - # The outermost tags must start at the left margin for this to match, and - # the inner nested divs must be indented. - # We need to do this before the next, more liberal match, because the next - # match will start at the first `<div>` and stop at the first `</div>`. - $text = preg_replace_callback('{(?> - (?> - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - - # Match from `\n<tag>` to `</tag>\n`, handling nested tags - # in between. - - [ ]{0,'.$less_than_tab.'} - <('.$block_tags_b_re.')# start tag = $2 - '.$attr.'> # attributes followed by > and \n - '.$content.' # content, support nesting - </\2> # the matching end tag - [ ]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - - | # Special version for tags of group a. - - [ ]{0,'.$less_than_tab.'} - <('.$block_tags_a_re.')# start tag = $3 - '.$attr.'>[ ]*\n # attributes followed by > - '.$content2.' # content, support nesting - </\3> # the matching end tag - [ ]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - - | # Special case just for <hr />. It was easier to make a special - # case than to make the other regex more complicated. - - [ ]{0,'.$less_than_tab.'} - <(hr) # start tag = $2 - '.$attr.' # attributes - /?> # the matching end tag - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - | # Special case for standalone HTML comments: - - [ ]{0,'.$less_than_tab.'} - (?s: - <!-- .*? --> - ) - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - | # PHP and ASP-style processor instructions (<? and <%) - - [ ]{0,'.$less_than_tab.'} - (?s: - <([?%]) # $2 - .*? - \2> - ) - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - ) - )}Sxmi', - array(&$this, '_hashHTMLBlocks_callback'), - $text); - - return $text; - } - function _hashHTMLBlocks_callback($matches) { - $text = $matches[1]; - $key = $this->hashBlock($text); - return "\n\n$key\n\n"; - } - - - function hashPart($text, $boundary = 'X') { - # - # Called whenever a tag must be hashed when a function insert an atomic - # element in the text stream. Passing $text to through this function gives - # a unique text-token which will be reverted back when calling unhash. - # - # The $boundary argument specify what character should be used to surround - # the token. By convension, "B" is used for block elements that needs not - # to be wrapped into paragraph tags at the end, ":" is used for elements - # that are word separators and "X" is used in the general case. - # - # Swap back any tag hash found in $text so we do not have to `unhash` - # multiple times at the end. - $text = $this->unhash($text); - - # Then hash the block. - static $i = 0; - $key = "$boundary\x1A" . ++$i . $boundary; - $this->html_hashes[$key] = $text; - return $key; # String that will replace the tag. - } - - - function hashBlock($text) { - # - # Shortcut function for hashPart with block-level boundaries. - # - return $this->hashPart($text, 'B'); - } - - - public $block_gamut = array( - # - # These are all the transformations that form block-level - # tags like paragraphs, headers, and list items. - # - "doHeaders" => 10, - "doHorizontalRules" => 20, - - "doLists" => 40, - "doCodeBlocks" => 50, - "doBlockQuotes" => 60, - ); - - function runBlockGamut($text) { - # - # Run block gamut tranformations. - # - # We need to escape raw HTML in Markdown source before doing anything - # else. This need to be done for each block, and not only at the - # beginning in the Markdown function since hashed blocks can be part of - # list items and could have been indented. Indented blocks would have - # been seen as a code block in a previous pass of hashHTMLBlocks. - $text = $this->hashHTMLBlocks($text); - - return $this->runBasicBlockGamut($text); - } - - function runBasicBlockGamut($text) { - # - # Run block gamut tranformations, without hashing HTML blocks. This is - # useful when HTML blocks are known to be already hashed, like in the first - # whole-document pass. - # - foreach ($this->block_gamut as $method => $priority) { - $text = $this->$method($text); - } - - # Finally form paragraph and restore hashed blocks. - $text = $this->formParagraphs($text); - - return $text; - } - - - function doHorizontalRules($text) { - # Do Horizontal Rules: - return preg_replace( - '{ - ^[ ]{0,3} # Leading space - ([-*_]) # $1: First marker - (?> # Repeated marker group - [ ]{0,2} # Zero, one, or two spaces. - \1 # Marker character - ){2,} # Group repeated at least twice - [ ]* # Tailing spaces - $ # End of line. - }mx', - "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n", - $text); - } - - - public $span_gamut = array( - # - # These are all the transformations that occur *within* block-level - # tags like paragraphs, headers, and list items. - # - # Process character escapes, code spans, and inline HTML - # in one shot. - "parseSpan" => -30, - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. - "doImages" => 10, - "doAnchors" => 20, - - # Make links out of things like `<http://example.com/>` - # Must come after doAnchors, because you can use < and > - # delimiters in inline links like [this](<url>). - "doAutoLinks" => 30, - "encodeAmpsAndAngles" => 40, - - "doItalicsAndBold" => 50, - "doHardBreaks" => 60, - ); - - function runSpanGamut($text) { - # - # Run span gamut tranformations. - # - foreach ($this->span_gamut as $method => $priority) { - $text = $this->$method($text); - } - - return $text; - } - - - function doHardBreaks($text) { - # Do hard breaks: - return preg_replace_callback('/ {2,}\n/', - array(&$this, '_doHardBreaks_callback'), $text); - } - function _doHardBreaks_callback($matches) { - return $this->hashPart("<br$this->empty_element_suffix\n"); - } - - - function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML <a> tags. - # - if ($this->in_anchor) return $text; - $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }xs', - array(&$this, '_doAnchors_reference_callback'), $text); - - # - # Next, inline-style links: [link text](url "optional title") - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - \( # literal paren - [ \n]* - (?: - <(.+?)> # href = $3 - | - ('.$this->nested_url_parenthesis_re.') # href = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # Title = $7 - \6 # matching quote - [ \n]* # ignore any spaces/tabs between closing quote and ) - )? # title is optional - \) - ) - }xs', - array(&$this, '_doAnchors_inline_callback'), $text); - - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link text][1] - # or [link text](/foo) - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ([^\[\]]+) # link text = $2; can\'t contain [ or ] - \] - ) - }xs', - array(&$this, '_doAnchors_reference_callback'), $text); - - $this->in_anchor = false; - return $text; - } - function _doAnchors_reference_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $link_id =& $matches[3]; - - if ($link_id == "") { - # for shortcut links like [this][] or [this]. - $link_id = $link_text; - } - - # lower-case and turn embedded newlines into spaces - $link_id = strtolower($link_id); - $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); - - if (isset($this->urls[$link_id])) { - $url = $this->urls[$link_id]; - $url = $this->encodeAttribute($url); - - $result = "<a href=\"$url\""; - if ( isset( $this->titles[$link_id] ) ) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text</a>"; - $result = $this->hashPart($result); - } - else { - $result = $whole_match; - } - return $result; - } - function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; - $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - - $url = $this->encodeAttribute($url); - - $result = "<a href=\"$url\""; - if (isset($title)) { - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text</a>"; - - return $this->hashPart($result); - } - - - function doImages($text) { - # - # Turn Markdown image shortcuts into <img> tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }xs', - array(&$this, '_doImages_reference_callback'), $text); - - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - \s? # One optional whitespace character - \( # literal paren - [ \n]* - (?: - <(\S*)> # src url = $3 - | - ('.$this->nested_url_parenthesis_re.') # src url = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # title = $7 - \6 # matching quote - [ \n]* - )? # title is optional - \) - ) - }xs', - array(&$this, '_doImages_inline_callback'), $text); - - return $text; - } - function _doImages_reference_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $link_id = strtolower($matches[3]); - - if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. - } - - $alt_text = $this->encodeAttribute($alt_text); - if (isset($this->urls[$link_id])) { - $url = $this->encodeAttribute($this->urls[$link_id]); - $result = "<img src=\"$url\" alt=\"$alt_text\""; - if (isset($this->titles[$link_id])) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - $result .= $this->empty_element_suffix; - $result = $this->hashPart($result); - } - else { - # If there's no such link ID, leave intact: - $result = $whole_match; - } - - return $result; - } - function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - - $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeAttribute($url); - $result = "<img src=\"$url\" alt=\"$alt_text\""; - if (isset($title)) { - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted - } - $result .= $this->empty_element_suffix; - - return $this->hashPart($result); - } - - - function doHeaders($text) { - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # - $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext'), $text); - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # - $text = preg_replace_callback('{ - ^(\#{1,6}) # $1 = string of #\'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #\'s (not counted) - \n+ - }xm', - array(&$this, '_doHeaders_callback_atx'), $text); - - return $text; - } - function _doHeaders_callback_setext($matches) { - # Terrible hack to check we haven't found an empty list item. - if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) - return $matches[0]; - - $level = $matches[2]{0} == '=' ? 1 : 2; - $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>"; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - function _doHeaders_callback_atx($matches) { - $level = strlen($matches[1]); - $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>"; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - - - function doLists($text) { - # - # Form HTML ordered (numbered) and unordered (bulleted) lists. - # - $less_than_tab = $this->tab_width - 1; - - # Re-usable patterns to match list item bullets and number markers: - $marker_ul_re = '[*+-]'; - $marker_ol_re = '\d+[\.]'; - $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; - - $markers_relist = array( - $marker_ul_re => $marker_ol_re, - $marker_ol_re => $marker_ul_re, - ); - - foreach ($markers_relist as $marker_re => $other_marker_re) { - # Re-usable pattern to match any entirel ul or ol list: - $whole_list_re = ' - ( # $1 = whole list - ( # $2 - ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces - ('.$marker_re.') # $4 = first list item marker - [ ]+ - ) - (?s:.+?) - ( # $5 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ ]* - '.$marker_re.'[ ]+ - ) - | - (?= # Lookahead for another kind of list - \n - \3 # Must have the same indentation - '.$other_marker_re.'[ ]+ - ) - ) - ) - '; // mx - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - - if ($this->list_level) { - $text = preg_replace_callback('{ - ^ - '.$whole_list_re.' - }mx', - array(&$this, '_doLists_callback'), $text); - } - else { - $text = preg_replace_callback('{ - (?:(?<=\n)\n|\A\n?) # Must eat the newline - '.$whole_list_re.' - }mx', - array(&$this, '_doLists_callback'), $text); - } - } - - return $text; - } - function _doLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: - $marker_ul_re = '[*+-]'; - $marker_ol_re = '\d+[\.]'; - $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; - - $list = $matches[1]; - $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol"; - - $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); - - $list .= "\n"; - $result = $this->processListItems($list, $marker_any_re); - - $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>"); - return "\n". $result ."\n\n"; - } - - public $list_level = 0; - - function processListItems($list_str, $marker_any_re) { - # - # Process the contents of a single ordered or unordered list, splitting it - # into individual list items. - # - # The $this->list_level global keeps track of when we're inside a list. - # Each time we enter a list, we increment it; when we leave a list, - # we decrement. If it's zero, we're not in a list anymore. - # - # We do this because when we're not inside a list, we want to treat - # something like this: - # - # I recommend upgrading to version - # 8. Oops, now this line is treated - # as a sub-list. - # - # As a single paragraph, despite the fact that the second line starts - # with a digit-period-space sequence. - # - # Whereas when we're inside a list (or sub-list), that line will be - # treated as the start of a sub-list. What a kludge, huh? This is - # an aspect of Markdown's syntax that's hard to parse perfectly - # without resorting to mind-reading. Perhaps the solution is to - # change the syntax rules such that sub-lists must start with a - # starting cardinal number; e.g. "1." or "a.". - - $this->list_level++; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - $list_str = preg_replace_callback('{ - (\n)? # leading line = $1 - (^[ ]*) # leading whitespace = $2 - ('.$marker_any_re.' # list marker and space = $3 - (?:[ ]+|(?=\n)) # space only required if item is not empty - ) - ((?s:.*?)) # list item text = $4 - (?:(\n+(?=\n))|\n) # tailing blank line = $5 - (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) - }xm', - array(&$this, '_processListItems_callback'), $list_str); - - $this->list_level--; - return $list_str; - } - function _processListItems_callback($matches) { - $item = $matches[4]; - $leading_line =& $matches[1]; - $leading_space =& $matches[2]; - $marker_space = $matches[3]; - $tailing_blank_line =& $matches[5]; - - if ($leading_line || $tailing_blank_line || - preg_match('/\n{2,}/', $item)) - { - # Replace marker with the appropriate whitespace indentation - $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; - $item = $this->runBlockGamut($this->outdent($item)."\n"); - } - else { - # Recursion for sub-lists: - $item = $this->doLists($this->outdent($item)); - $item = preg_replace('/\n+$/', '', $item); - $item = $this->runSpanGamut($item); - } - - return "<li>" . $item . "</li>\n"; - } - - - function doCodeBlocks($text) { - # - # Process Markdown `<pre><code>` blocks. - # - $text = preg_replace_callback('{ - (?:\n\n|\A\n?) - ( # $1 = the code block -- one or more lines, starting with a space/tab - (?> - [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces - .*\n+ - )+ - ) - ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc - }xm', - array(&$this, '_doCodeBlocks_callback'), $text); - - return $text; - } - function _doCodeBlocks_callback($matches) { - $codeblock = $matches[1]; - - $codeblock = $this->outdent($codeblock); - $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); - - # trim leading newlines and trailing newlines - $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock); - - $codeblock = "<pre><code>$codeblock\n</code></pre>"; - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - - - function makeCodeSpan($code) { - # - # Create a code span markup for $code. Called from handleSpanToken. - # - $code = htmlspecialchars(trim($code), ENT_NOQUOTES); - return $this->hashPart("<code>$code</code>"); - } - - - public $em_relist = array( - '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)', - '*' => '(?<=\S|^)(?<!\*)\*(?!\*)', - '_' => '(?<=\S|^)(?<!_)_(?!_)', - ); - public $strong_relist = array( - '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)', - '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)', - '__' => '(?<=\S|^)(?<!_)__(?!_)', - ); - public $em_strong_relist = array( - '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)', - '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)', - '___' => '(?<=\S|^)(?<!_)___(?!_)', - ); - public $em_strong_prepared_relist; - - function prepareItalicsAndBold() { - # - # Prepare regular expressions for searching emphasis tokens in any - # context. - # - foreach ($this->em_relist as $em => $em_re) { - foreach ($this->strong_relist as $strong => $strong_re) { - # Construct list of allowed token expressions. - $token_relist = array(); - if (isset($this->em_strong_relist["$em$strong"])) { - $token_relist[] = $this->em_strong_relist["$em$strong"]; - } - $token_relist[] = $em_re; - $token_relist[] = $strong_re; - - # Construct master expression from list. - $token_re = '{('. implode('|', $token_relist) .')}'; - $this->em_strong_prepared_relist["$em$strong"] = $token_re; - } - } - } - - function doItalicsAndBold($text) { - $token_stack = array(''); - $text_stack = array(''); - $em = ''; - $strong = ''; - $tree_char_em = false; - - while (1) { - # - # Get prepared regular expression for seraching emphasis tokens - # in current context. - # - $token_re = $this->em_strong_prepared_relist["$em$strong"]; - - # - # Each loop iteration search for the next emphasis token. - # Each token is then passed to handleSpanToken. - # - $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - $text_stack[0] .= $parts[0]; - $token =& $parts[1]; - $text =& $parts[2]; - - if (empty($token)) { - # Reached end of text span: empty stack without emitting. - # any more emphasis. - while ($token_stack[0]) { - $text_stack[1] .= array_shift($token_stack); - $text_stack[0] .= array_shift($text_stack); - } - break; - } - - $token_len = strlen($token); - if ($tree_char_em) { - # Reached closing marker while inside a three-char emphasis. - if ($token_len == 3) { - # Three-char closing marker, close em and strong. - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "<strong><em>$span</em></strong>"; - $text_stack[0] .= $this->hashPart($span); - $em = ''; - $strong = ''; - } else { - # Other closing marker: close one em or strong and - # change current token state to match the other - $token_stack[0] = str_repeat($token{0}, 3-$token_len); - $tag = $token_len == 2 ? "strong" : "em"; - $span = $text_stack[0]; - $span = $this->runSpanGamut($span); - $span = "<$tag>$span</$tag>"; - $text_stack[0] = $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong - } - $tree_char_em = false; - } else if ($token_len == 3) { - if ($em) { - # Reached closing marker for both em and strong. - # Closing strong marker: - for ($i = 0; $i < 2; ++$i) { - $shifted_token = array_shift($token_stack); - $tag = strlen($shifted_token) == 2 ? "strong" : "em"; - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "<$tag>$span</$tag>"; - $text_stack[0] .= $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong - } - } else { - # Reached opening three-char emphasis marker. Push on token - # stack; will be handled by the special condition above. - $em = $token{0}; - $strong = "$em$em"; - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $tree_char_em = true; - } - } else if ($token_len == 2) { - if ($strong) { - # Unwind any dangling emphasis marker: - if (strlen($token_stack[0]) == 1) { - $text_stack[1] .= array_shift($token_stack); - $text_stack[0] .= array_shift($text_stack); - } - # Closing strong marker: - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "<strong>$span</strong>"; - $text_stack[0] .= $this->hashPart($span); - $strong = ''; - } else { - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $strong = $token; - } - } else { - # Here $token_len == 1 - if ($em) { - if (strlen($token_stack[0]) == 1) { - # Closing emphasis marker: - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "<em>$span</em>"; - $text_stack[0] .= $this->hashPart($span); - $em = ''; - } else { - $text_stack[0] .= $token; - } - } else { - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $em = $token; - } - } - } - return $text_stack[0]; - } - - - function doBlockQuotes($text) { - $text = preg_replace_callback('/ - ( # Wrap whole match in $1 - (?> - ^[ ]*>[ ]? # ">" at the start of a line - .+\n # rest of the first line - (.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - ) - /xm', - array(&$this, '_doBlockQuotes_callback'), $text); - - return $text; - } - function _doBlockQuotes_callback($matches) { - $bq = $matches[1]; - # trim one level of quoting - trim whitespace-only lines - $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); - $bq = $this->runBlockGamut($bq); # recurse - - $bq = preg_replace('/^/m', " ", $bq); - # These leading spaces cause problem with <pre> content, - # so we need to fix that: - $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', - array(&$this, '_doBlockQuotes_callback2'), $bq); - - return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n"; - } - function _doBlockQuotes_callback2($matches) { - $pre = $matches[1]; - $pre = preg_replace('/^ /m', '', $pre); - return $pre; - } - - - function formParagraphs($text) { - # - # Params: - # $text - string to process with html <p> tags - # - # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap <p> tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { - # Is a paragraph. - $value = $this->runSpanGamut($value); - $value = preg_replace('/^([ ]*)/', "<p>", $value); - $value .= "</p>"; - $grafs[$key] = $this->unhash($value); - } - else { - # Is a block. - # Modify elements of @grafs in-place... - $graf = $value; - $block = $this->html_hashes[$graf]; - $graf = $block; -// if (preg_match('{ -// \A -// ( # $1 = <div> tag -// <div \s+ -// [^>]* -// \b -// markdown\s*=\s* ([\'"]) # $2 = attr quote char -// 1 -// \2 -// [^>]* -// > -// ) -// ( # $3 = contents -// .* -// ) -// (</div>) # $4 = closing tag -// \z -// }xs', $block, $matches)) -// { -// list(, $div_open, , $div_content, $div_close) = $matches; -// -// # We can't call Markdown(), because that resets the hash; -// # that initialization code should be pulled into its own sub, though. -// $div_content = $this->hashHTMLBlocks($div_content); -// -// # Run document gamut methods on the content. -// foreach ($this->document_gamut as $method => $priority) { -// $div_content = $this->$method($div_content); -// } -// -// $div_open = preg_replace( -// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); -// -// $graf = $div_open . "\n" . $div_content . "\n" . $div_close; -// } - $grafs[$key] = $graf; - } - } - - return implode("\n\n", $grafs); - } - - - function encodeAttribute($text) { - # - # Encode text for a double-quoted HTML attribute. This function - # is *not* suitable for attributes enclosed in single quotes. - # - $text = $this->encodeAmpsAndAngles($text); - $text = str_replace('"', '"', $text); - return $text; - } - - - function encodeAmpsAndAngles($text) { - # - # Smart processing for ampersands and angle brackets that need to - # be encoded. Valid character entities are left alone unless the - # no-entities mode is set. - # - if ($this->no_entities) { - $text = str_replace('&', '&', $text); - } else { - # Ampersand-encoding based entirely on Nat Irons's Amputator - # MT plugin: <http://bumppo.net/projects/amputator/> - $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', - '&', $text);; - } - # Encode remaining <'s - $text = str_replace('<', '<', $text); - - return $text; - } - - - function doAutoLinks($text) { - $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', - array(&$this, '_doAutoLinks_url_callback'), $text); - - # Email addresses: <address@domain.foo> - $text = preg_replace_callback('{ - < - (?:mailto:)? - ( - (?: - [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+ - | - ".*?" - ) - \@ - (?: - [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ - | - \[[\d.a-fA-F:]+\] # IPv4 & IPv6 - ) - ) - > - }xi', - array(&$this, '_doAutoLinks_email_callback'), $text); - $text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text); - - return $text; - } - function _doAutoLinks_tel_callback($matches) { - $url = $this->encodeAttribute($matches[1]); - $tel = $this->encodeAttribute($matches[2]); - $link = "<a href=\"$url\">$tel</a>"; - return $this->hashPart($link); - } - function _doAutoLinks_url_callback($matches) { - $url = $this->encodeAttribute($matches[1]); - $link = "<a href=\"$url\">$url</a>"; - return $this->hashPart($link); - } - function _doAutoLinks_email_callback($matches) { - $address = $matches[1]; - $link = $this->encodeEmailAddress($address); - return $this->hashPart($link); - } - - - function encodeEmailAddress($addr) { - # - # Input: an email address, e.g. "foo@example.com" - # - # Output: the email address as a mailto link, with each character - # of the address encoded as either a decimal or hex entity, in - # the hopes of foiling most address harvesting spam bots. E.g.: - # - # <p><a href="mailto:foo - # @example.co - # m">foo@exampl - # e.com</a></p> - # - # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. - # With some optimizations by Milian Wolff. - # - $addr = "mailto:" . $addr; - $chars = preg_split('/(?<!^)(?!$)/', $addr); - $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed. - - foreach ($chars as $key => $char) { - $ord = ord($char); - # Ignore non-ascii chars. - if ($ord < 128) { - $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. - # roughly 10% raw, 45% hex, 45% dec - # '@' *must* be encoded. I insist. - if ($r > 90 && $char != '@') /* do nothing */; - else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; - else $chars[$key] = '&#'.$ord.';'; - } - } - - $addr = implode('', $chars); - $text = implode('', array_slice($chars, 7)); # text without `mailto:` - $addr = "<a href=\"$addr\">$text</a>"; - - return $addr; - } - - - function parseSpan($str) { - # - # Take the string $str and parse it into tokens, hashing embedded HTML, - # escaped characters and handling code spans. - # - $output = ''; - - $span_re = '{ - ( - \\\\'.$this->escape_chars_re.' - | - (?<![`\\\\]) - `+ # code span marker - '.( $this->no_markup ? '' : ' - | - <!-- .*? --> # comment - | - <\?.*?\?> | <%.*?%> # processing instruction - | - <[!$]?[-a-zA-Z0-9:_]+ # regular tags - (?> - \s - (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* - )? - > - | - <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag - | - </[-a-zA-Z0-9:_]+\s*> # closing tag - ').' - ) - }xs'; - - while (1) { - # - # Each loop iteration search for either the next tag, the next - # openning code span marker, or the next escaped character. - # Each token is then passed to handleSpanToken. - # - $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); - - # Create token from text preceding tag. - if ($parts[0] != "") { - $output .= $parts[0]; - } - - # Check if we reach the end. - if (isset($parts[1])) { - $output .= $this->handleSpanToken($parts[1], $parts[2]); - $str = $parts[2]; - } - else { - break; - } - } - - return $output; - } - - - function handleSpanToken($token, &$str) { - # - # Handle $token provided by parseSpan by determining its nature and - # returning the corresponding value that should replace it. - # - switch ($token{0}) { - case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); - case "`": - # Search for end marker in remaining text. - if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', - $str, $matches)) - { - $str = $matches[2]; - $codespan = $this->makeCodeSpan($matches[1]); - return $this->hashPart($codespan); - } - return $token; // return as text since no ending marker found. - default: - return $this->hashPart($token); - } - } - - - function outdent($text) { - # - # Remove one level of line-leading tabs or spaces - # - return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); - } - - - # String length function for detab. `_initDetab` will create a function to - # hanlde UTF-8 if the default function does not exist. - public $utf8_strlen = 'mb_strlen'; - - function detab($text) { - # - # Replace tabs with the appropriate amount of space. - # - # For each line we separate the line in blocks delemited by - # tab characters. Then we reconstruct every line by adding the - # appropriate number of space between each blocks. - - $text = preg_replace_callback('/^.*\t.*$/m', - array(&$this, '_detab_callback'), $text); - - return $text; - } - function _detab_callback($matches) { - $line = $matches[0]; - $strlen = $this->utf8_strlen; # strlen function for UTF-8. - - # Split in blocks. - $blocks = explode("\t", $line); - # Add each blocks to the line. - $line = $blocks[0]; - unset($blocks[0]); # Do not add first block twice. - foreach ($blocks as $block) { - # Calculate amount of space, insert spaces, insert block. - $amount = $this->tab_width - - $strlen($line, 'UTF-8') % $this->tab_width; - $line .= str_repeat(" ", $amount) . $block; - } - return $line; - } - function _initDetab() { - # - # Check for the availability of the function in the `utf8_strlen` property - # (initially `mb_strlen`). If the function is not available, use jetpack_utf8_strlen - # that will loosely count the number of UTF-8 characters with a - # regular expression. - # - if ( function_exists( $this->utf8_strlen ) ) { - return; - } - $this->utf8_strlen = 'jetpack_utf8_strlen'; - } - - - function unhash($text) { - # - # Swap back in all the tags hashed by _HashHTMLBlocks. - # - return preg_replace_callback('/(.)\x1A[0-9]+\1/', - array(&$this, '_unhash_callback'), $text); - } - function _unhash_callback($matches) { - return $this->html_hashes[$matches[0]]; - } - -} - - -# -# Markdown Extra Parser Class -# - -class MarkdownExtra_Parser extends Markdown_Parser { - - ### Configuration Variables ### - - # Prefix for footnote ids. - public $fn_id_prefix = ""; - - # Optional title attribute for footnote links and backlinks. - public $fn_link_title = MARKDOWN_FN_LINK_TITLE; - public $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE; - - # Optional class attribute for footnote links and backlinks. - public $fn_link_class = MARKDOWN_FN_LINK_CLASS; - public $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS; - - # Optional class prefix for fenced code block. - public $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX; - # Class attribute for code blocks goes on the `code` tag; - # setting this to true will put attributes on the `pre` tag instead. - public $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE; - - # Predefined abbreviations. - public $predef_abbr = array(); - - - ### Parser Implementation ### - - function __construct() { - # - # Constructor function. Initialize the parser object. - # - # Add extra escapable characters before parent constructor - # initialize the table. - $this->escape_chars .= ':|'; - - # Insert extra document, block, and span transformations. - # Parent constructor will do the sorting. - $this->document_gamut += array( - "doFencedCodeBlocks" => 5, - "stripFootnotes" => 15, - "stripAbbreviations" => 25, - "appendFootnotes" => 50, - ); - $this->block_gamut += array( - "doFencedCodeBlocks" => 5, - "doTables" => 15, - "doDefLists" => 45, - ); - $this->span_gamut += array( - "doFootnotes" => 5, - "doAbbreviations" => 70, - ); - - parent::__construct(); - } - - - # Extra variables used during extra transformations. - public $footnotes = array(); - public $footnotes_ordered = array(); - public $footnotes_ref_count = array(); - public $footnotes_numbers = array(); - public $abbr_desciptions = array(); - public $abbr_word_re = ''; - - # Give the current footnote number. - public $footnote_counter = 1; - - - function setup() { - # - # Setting up Extra-specific variables. - # - parent::setup(); - - $this->footnotes = array(); - $this->footnotes_ordered = array(); - $this->footnotes_ref_count = array(); - $this->footnotes_numbers = array(); - $this->abbr_desciptions = array(); - $this->abbr_word_re = ''; - $this->footnote_counter = 1; - - foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { - if ($this->abbr_word_re) - $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - } - } - - function teardown() { - # - # Clearing Extra-specific variables. - # - $this->footnotes = array(); - $this->footnotes_ordered = array(); - $this->footnotes_ref_count = array(); - $this->footnotes_numbers = array(); - $this->abbr_desciptions = array(); - $this->abbr_word_re = ''; - - parent::teardown(); - } - - - ### Extra Attribute Parser ### - - # Expression to use to catch attributes (includes the braces) - public $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}'; - # Expression to use when parsing in a context when no capture is desired - public $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}'; - - function doExtraAttributes($tag_name, $attr) { - # - # Parse attributes caught by the $this->id_class_attr_catch_re expression - # and return the HTML-formatted list of attributes. - # - # Currently supported attributes are .class and #id. - # - if (empty($attr)) return ""; - - # Split on components - preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches); - $elements = $matches[0]; - - # handle classes and ids (only first id taken into account) - $classes = array(); - $id = false; - foreach ($elements as $element) { - if ($element{0} == '.') { - $classes[] = substr($element, 1); - } else if ($element{0} == '#') { - if ($id === false) $id = substr($element, 1); - } - } - - # compose attributes as string - $attr_str = ""; - if (!empty($id)) { - $attr_str .= ' id="'.$id.'"'; - } - if (!empty($classes)) { - $attr_str .= ' class="'.implode(" ", $classes).'"'; - } - return $attr_str; - } - - - function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - (?: - <(.+?)> # url = $2 - | - (\S+?) # url = $3 - ) - [ ]* - \n? # maybe one newline - [ ]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.*?) # title = $4 - [")] - [ ]* - )? # title is optional - (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr - (?:\n+|\Z) - }xm', - array(&$this, '_stripLinkDefinitions_callback'), - $text); - return $text; - } - function _stripLinkDefinitions_callback($matches) { - $link_id = strtolower($matches[1]); - $url = $matches[2] == '' ? $matches[3] : $matches[2]; - $this->urls[$link_id] = $url; - $this->titles[$link_id] =& $matches[4]; - $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]); - return ''; # String that will replace the block - } - - - ### HTML Block Parser ### - - # Tags that are always treated as block tags: - public $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption'; - - # Tags treated as block tags only if the opening tag is alone on its line: - public $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; - - # Tags where markdown="1" default to span mode: - public $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; - - # Tags which must not have their contents modified, no matter where - # they appear: - public $clean_tags_re = 'script|math|svg'; - - # Tags that do not need to be closed. - public $auto_close_tags_re = 'hr|img|param|source|track'; - - - function hashHTMLBlocks($text) { - # - # Hashify HTML Blocks and "clean tags". - # - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap <p>s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded. - # - # This works by calling _HashHTMLBlocks_InMarkdown, which then calls - # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" - # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back - # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. - # These two functions are calling each other. It's recursive! - # - if ($this->no_markup) return $text; - - # - # Call the HTML-in-Markdown hasher. - # - list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text); - - return $text; - } - function _hashHTMLBlocks_inMarkdown($text, $indent = 0, - $enclosing_tag_re = '', $span = false) - { - # - # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. - # - # * $indent is the number of space to be ignored when checking for code - # blocks. This is important because if we don't take the indent into - # account, something like this (which looks right) won't work as expected: - # - # <div> - # <div markdown="1"> - # Hello World. <-- Is this a Markdown code block or text? - # </div> <-- Is this a Markdown code block or a real tag? - # <div> - # - # If you don't like this, just don't indent the tag on which - # you apply the markdown="1" attribute. - # - # * If $enclosing_tag_re is not empty, stops at the first unmatched closing - # tag with that name. Nested tags supported. - # - # * If $span is true, text inside must treated as span. So any double - # newline will be replaced by a single newline so that it does not create - # paragraphs. - # - # Returns an array of that form: ( processed text , remaining text ) - # - if ($text === '') return array('', ''); - - # Regex to check for the presence of newlines around a block tag. - $newline_before_re = '/(?:^\n?|\n\n)*$/'; - $newline_after_re = - '{ - ^ # Start of text following the tag. - (?>[ ]*<!--.*?-->)? # Optional comment. - [ ]*\n # Must be followed by newline. - }xs'; - - # Regex to match any tag. - $block_tag_re = - '{ - ( # $2: Capture whole tag. - </? # Any opening or closing tag. - (?> # Tag name. - '.$this->block_tags_re.' | - '.$this->context_block_tags_re.' | - '.$this->clean_tags_re.' | - (?!\s)'.$enclosing_tag_re.' - ) - (?: - (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. - (?> - ".*?" | # Double quotes (can contain `>`) - \'.*?\' | # Single quotes (can contain `>`) - .+? # Anything but quotes and `>`. - )*? - )? - > # End of tag. - | - <!-- .*? --> # HTML Comment - | - <\?.*?\?> | <%.*?%> # Processing instruction - | - <!\[CDATA\[.*?\]\]> # CData Block - '. ( !$span ? ' # If not in span. - | - # Indented code block - (?: ^[ ]*\n | ^ | \n[ ]*\n ) - [ ]{'.($indent+4).'}[^\n]* \n - (?> - (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n - )* - | - # Fenced code block marker - (?<= ^ | \n ) - [ ]{0,'.($indent+3).'}(?:~{3,}|`{3,}) - [ ]* - (?: - \.?[-_:a-zA-Z0-9]+ # standalone class name - | - '.$this->id_class_attr_nocatch_re.' # extra attributes - )? - [ ]* - (?= \n ) - ' : '' ). ' # End (if not is span). - | - # Code span marker - # Note, this regex needs to go after backtick fenced - # code blocks but it should also be kept outside of the - # "if not in span" condition adding backticks to the parser - `+ - ) - }xs'; - - - $depth = 0; # Current depth inside the tag tree. - $parsed = ""; # Parsed text that will be returned. - - # - # Loop through every tag until we find the closing tag of the parent - # or loop until reaching the end of text if no parent tag specified. - # - do { - # - # Split the text using the first $tag_match pattern found. - # Text before pattern will be first in the array, text after - # pattern will be at the end, and between will be any catches made - # by the pattern. - # - $parts = preg_split($block_tag_re, $text, 2, - PREG_SPLIT_DELIM_CAPTURE); - - # If in Markdown span mode, add a empty-string span-level hash - # after each newline to prevent triggering any block element. - if ($span) { - $void = $this->hashPart("", ':'); - $newline = "$void\n"; - $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void; - } - - $parsed .= $parts[0]; # Text before current tag. - - # If end of $text has been reached. Stop loop. - if (count($parts) < 3) { - $text = ""; - break; - } - - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - $tag_re = preg_quote($tag); # For use in a regular expression. - - # - # Check for: Fenced code block marker. - # Note: need to recheck the whole tag to disambiguate backtick - # fences from code spans - # - if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) { - # Fenced code block marker: find matching end marker. - $fence_indent = strlen($capture[1]); # use captured indent in re - $fence_re = $capture[2]; # use captured fence in re - if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text, - $matches)) - { - # End marker found: pass text unchanged until marker. - $parsed .= $tag . $matches[0]; - $text = substr($text, strlen($matches[0])); - } - else { - # No end marker: just skip it. - $parsed .= $tag; - } - } - # - # Check for: Indented code block. - # - else if ($tag{0} == "\n" || $tag{0} == " ") { - # Indented code block: pass it unchanged, will be handled - # later. - $parsed .= $tag; - } - # - # Check for: Code span marker - # Note: need to check this after backtick fenced code blocks - # - else if ($tag{0} == "`") { - # Find corresponding end marker. - $tag_re = preg_quote($tag); - if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}', - $text, $matches)) - { - # End marker found: pass text unchanged until marker. - $parsed .= $tag . $matches[0]; - $text = substr($text, strlen($matches[0])); - } - else { - # Unmatched marker: just skip it. - $parsed .= $tag; - } - } - # - # Check for: Opening Block level tag or - # Opening Context Block tag (like ins and del) - # used as a block tag (tag is alone on it's line). - # - else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) || - ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && - preg_match($newline_before_re, $parsed) && - preg_match($newline_after_re, $text) ) - ) - { - # Need to parse tag and following text using the HTML parser. - list($block_text, $text) = - $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true); - - # Make sure it stays outside of any paragraph by adding newlines. - $parsed .= "\n\n$block_text\n\n"; - } - # - # Check for: Clean tag (like script, math) - # HTML Comments, processing instructions. - # - else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') - { - # Need to parse tag and following text using the HTML parser. - # (don't check for markdown attribute) - list($block_text, $text) = - $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false); - - $parsed .= $block_text; - } - # - # Check for: Tag with same name as enclosing tag. - # - else if ($enclosing_tag_re !== '' && - # Same name as enclosing tag. - preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag)) - { - # - # Increase/decrease nested tag count. - # - if ($tag{1} == '/') $depth--; - else if ($tag{strlen($tag)-2} != '/') $depth++; - - if ($depth < 0) { - # - # Going out of parent element. Clean up and break so we - # return to the calling function. - # - $text = $tag . $text; - break; - } - - $parsed .= $tag; - } - else { - $parsed .= $tag; - } - } while ($depth >= 0); - - return array($parsed, $text); - } - function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { - # - # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. - # - # * Calls $hash_method to convert any blocks. - # * Stops when the first opening tag closes. - # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. - # (it is not inside clean tags) - # - # Returns an array of that form: ( processed text , remaining text ) - # - if ($text === '') return array('', ''); - - # Regex to match `markdown` attribute inside of a tag. - $markdown_attr_re = ' - { - \s* # Eat whitespace before the `markdown` attribute - markdown - \s*=\s* - (?> - (["\']) # $1: quote delimiter - (.*?) # $2: attribute value - \1 # matching delimiter - | - ([^\s>]*) # $3: unquoted attribute value - ) - () # $4: make $3 always defined (avoid warnings) - }xs'; - - # Regex to match any tag. - $tag_re = '{ - ( # $2: Capture whole tag. - </? # Any opening or closing tag. - [\w:$]+ # Tag name. - (?: - (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. - (?> - ".*?" | # Double quotes (can contain `>`) - \'.*?\' | # Single quotes (can contain `>`) - .+? # Anything but quotes and `>`. - )*? - )? - > # End of tag. - | - <!-- .*? --> # HTML Comment - | - <\?.*?\?> | <%.*?%> # Processing instruction - | - <!\[CDATA\[.*?\]\]> # CData Block - ) - }xs'; - - $original_text = $text; # Save original text in case of faliure. - - $depth = 0; # Current depth inside the tag tree. - $block_text = ""; # Temporary text holder for current text. - $parsed = ""; # Parsed text that will be returned. - - # - # Get the name of the starting tag. - # (This pattern makes $base_tag_name_re safe without quoting.) - # - if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) - $base_tag_name_re = $matches[1]; - - # - # Loop through every tag until we find the corresponding closing tag. - # - do { - # - # Split the text using the first $tag_match pattern found. - # Text before pattern will be first in the array, text after - # pattern will be at the end, and between will be any catches made - # by the pattern. - # - $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - - if (count($parts) < 3) { - # - # End of $text reached with unbalenced tag(s). - # In that case, we return original text unchanged and pass the - # first character as filtered to prevent an infinite loop in the - # parent function. - # - return array($original_text{0}, substr($original_text, 1)); - } - - $block_text .= $parts[0]; # Text before current tag. - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - - # - # Check for: Auto-close tag (like <hr/>) - # Comments and Processing Instructions. - # - if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') - { - # Just add the tag to the block as if it was text. - $block_text .= $tag; - } - else { - # - # Increase/decrease nested tag count. Only do so if - # the tag's name match base tag's. - # - if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) { - if ($tag{1} == '/') $depth--; - else if ($tag{strlen($tag)-2} != '/') $depth++; - } - - # - # Check for `markdown="1"` attribute and handle it. - # - if ($md_attr && - preg_match($markdown_attr_re, $tag, $attr_m) && - preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])) - { - # Remove `markdown` attribute from opening tag. - $tag = preg_replace($markdown_attr_re, '', $tag); - - # Check if text inside this tag must be parsed in span mode. - $this->mode = $attr_m[2] . $attr_m[3]; - $span_mode = $this->mode == 'span' || $this->mode != 'block' && - preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag); - - # Calculate indent before tag. - if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { - $strlen = $this->utf8_strlen; - $indent = $strlen($matches[1], 'UTF-8'); - } else { - $indent = 0; - } - - # End preceding block with this tag. - $block_text .= $tag; - $parsed .= $this->$hash_method($block_text); - - # Get enclosing tag name for the ParseMarkdown function. - # (This pattern makes $tag_name_re safe without quoting.) - preg_match('/^<([\w:$]*)\b/', $tag, $matches); - $tag_name_re = $matches[1]; - - # Parse the content using the HTML-in-Markdown parser. - list ($block_text, $text) - = $this->_hashHTMLBlocks_inMarkdown($text, $indent, - $tag_name_re, $span_mode); - - # Outdent markdown text. - if ($indent > 0) { - $block_text = preg_replace("/^[ ]{1,$indent}/m", "", - $block_text); - } - - # Append tag content to parsed text. - if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; - else $parsed .= "$block_text"; - - # Start over with a new block. - $block_text = ""; - } - else $block_text .= $tag; - } - - } while ($depth > 0); - - # - # Hash last block text that wasn't processed inside the loop. - # - $parsed .= $this->$hash_method($block_text); - - return array($parsed, $text); - } - - - function hashClean($text) { - # - # Called whenever a tag must be hashed when a function inserts a "clean" tag - # in $text, it passes through this function and is automaticaly escaped, - # blocking invalid nested overlap. - # - return $this->hashPart($text, 'C'); - } - - - function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML <a> tags. - # - if ($this->in_anchor) return $text; - $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }xs', - array(&$this, '_doAnchors_reference_callback'), $text); - - # - # Next, inline-style links: [link text](url "optional title") - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - \( # literal paren - [ \n]* - (?: - <(.+?)> # href = $3 - | - ('.$this->nested_url_parenthesis_re.') # href = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # Title = $7 - \6 # matching quote - [ \n]* # ignore any spaces/tabs between closing quote and ) - )? # title is optional - \) - (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes - ) - }xs', - array(&$this, '_doAnchors_inline_callback'), $text); - - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link text][1] - # or [link text](/foo) - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ([^\[\]]+) # link text = $2; can\'t contain [ or ] - \] - ) - }xs', - array(&$this, '_doAnchors_reference_callback'), $text); - - $this->in_anchor = false; - return $text; - } - function _doAnchors_reference_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $link_id =& $matches[3]; - - if ($link_id == "") { - # for shortcut links like [this][] or [this]. - $link_id = $link_text; - } - - # lower-case and turn embedded newlines into spaces - $link_id = strtolower($link_id); - $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); - - if (isset($this->urls[$link_id])) { - $url = $this->urls[$link_id]; - $url = $this->encodeAttribute($url); - - $result = "<a href=\"$url\""; - if ( isset( $this->titles[$link_id] ) ) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - if (isset($this->ref_attr[$link_id])) - $result .= $this->ref_attr[$link_id]; - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text</a>"; - $result = $this->hashPart($result); - } - else { - $result = $whole_match; - } - return $result; - } - function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; - $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); - - - $url = $this->encodeAttribute($url); - - $result = "<a href=\"$url\""; - if (isset($title)) { - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - $result .= $attr; - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text</a>"; - - return $this->hashPart($result); - } - - - function doImages($text) { - # - # Turn Markdown image shortcuts into <img> tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }xs', - array(&$this, '_doImages_reference_callback'), $text); - - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - \s? # One optional whitespace character - \( # literal paren - [ \n]* - (?: - <(\S*)> # src url = $3 - | - ('.$this->nested_url_parenthesis_re.') # src url = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # title = $7 - \6 # matching quote - [ \n]* - )? # title is optional - \) - (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes - ) - }xs', - array(&$this, '_doImages_inline_callback'), $text); - - return $text; - } - function _doImages_reference_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $link_id = strtolower($matches[3]); - - if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. - } - - $alt_text = $this->encodeAttribute($alt_text); - if (isset($this->urls[$link_id])) { - $url = $this->encodeAttribute($this->urls[$link_id]); - $result = "<img src=\"$url\" alt=\"$alt_text\""; - if (isset($this->titles[$link_id])) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - if (isset($this->ref_attr[$link_id])) - $result .= $this->ref_attr[$link_id]; - $result .= $this->empty_element_suffix; - $result = $this->hashPart($result); - } - else { - # If there's no such link ID, leave intact: - $result = $whole_match; - } - - return $result; - } - function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); - - $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeAttribute($url); - $result = "<img src=\"$url\" alt=\"$alt_text\""; - if (isset($title)) { - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted - } - $result .= $attr; - $result .= $this->empty_element_suffix; - - return $this->hashPart($result); - } - - - function doHeaders($text) { - # - # Redefined to add id and class attribute support. - # - # Setext-style headers: - # Header 1 {#header1} - # ======== - # - # Header 2 {#header2 .class1 .class2} - # -------- - # - $text = preg_replace_callback( - '{ - (^.+?) # $1: Header text - (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes - [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer - }mx', - array(&$this, '_doHeaders_callback_setext'), $text); - - # atx-style headers: - # # Header 1 {#header1} - # ## Header 2 {#header2} - # ## Header 2 with closing hashes ## {#header3.class1.class2} - # ... - # ###### Header 6 {.class2} - # - $text = preg_replace_callback('{ - ^(\#{1,6}) # $1 = string of #\'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #\'s (not counted) - (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes - [ ]* - \n+ - }xm', - array(&$this, '_doHeaders_callback_atx'), $text); - - return $text; - } - function _doHeaders_callback_setext($matches) { - if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) - return $matches[0]; - $level = $matches[3]{0} == '=' ? 1 : 2; - $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]); - $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>"; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - function _doHeaders_callback_atx($matches) { - $level = strlen($matches[1]); - $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]); - $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>"; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - - - function doTables($text) { - # - # Form HTML tables. - # - $less_than_tab = $this->tab_width - 1; - # - # Find tables with leading pipe. - # - # | Header 1 | Header 2 - # | -------- | -------- - # | Cell 1 | Cell 2 - # | Cell 3 | Cell 4 - # - $text = preg_replace_callback(' - { - ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - [|] # Optional leading pipe (present) - (.+) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline - - ( # $3: Cells - (?> - [ ]* # Allowed whitespace. - [|] .* \n # Row content. - )* - ) - (?=\n|\Z) # Stop at final double newline. - }xm', - array(&$this, '_doTable_leadingPipe_callback'), $text); - - # - # Find tables without leading pipe. - # - # Header 1 | Header 2 - # -------- | -------- - # Cell 1 | Cell 2 - # Cell 3 | Cell 4 - # - $text = preg_replace_callback(' - { - ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - (\S.*[|].*) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline - - ( # $3: Cells - (?> - .* [|] .* \n # Row content - )* - ) - (?=\n|\Z) # Stop at final double newline. - }xm', - array(&$this, '_DoTable_callback'), $text); - - return $text; - } - function _doTable_leadingPipe_callback($matches) { - $head = $matches[1]; - $underline = $matches[2]; - $content = $matches[3]; - - # Remove leading pipe for each row. - $content = preg_replace('/^ *[|]/m', '', $content); - - return $this->_doTable_callback(array($matches[0], $head, $underline, $content)); - } - function _doTable_callback($matches) { - $head = $matches[1]; - $underline = $matches[2]; - $content = $matches[3]; - - # Remove any tailing pipes for each line. - $head = preg_replace('/[|] *$/m', '', $head); - $underline = preg_replace('/[|] *$/m', '', $underline); - $content = preg_replace('/[|] *$/m', '', $content); - - # Reading alignement from header underline. - $separators = preg_split('/ *[|] */', $underline); - foreach ($separators as $n => $s) { - if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"'; - else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"'; - else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"'; - else $attr[$n] = ''; - } - - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. - $head = $this->parseSpan($head); - $headers = preg_split('/ *[|] */', $head); - $col_count = count($headers); - $attr = array_pad($attr, $col_count, ''); - - # Write column headers. - $text = "<table>\n"; - $text .= "<thead>\n"; - $text .= "<tr>\n"; - foreach ($headers as $n => $header) - $text .= " <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n"; - $text .= "</tr>\n"; - $text .= "</thead>\n"; - - # Split content by row. - $rows = explode("\n", trim($content, "\n")); - - $text .= "<tbody>\n"; - foreach ($rows as $row) { - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. - $row = $this->parseSpan($row); - - # Split row by cell. - $row_cells = preg_split('/ *[|] */', $row, $col_count); - $row_cells = array_pad($row_cells, $col_count, ''); - - $text .= "<tr>\n"; - foreach ($row_cells as $n => $cell) - $text .= " <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n"; - $text .= "</tr>\n"; - } - $text .= "</tbody>\n"; - $text .= "</table>"; - - return $this->hashBlock($text) . "\n"; - } - - - function doDefLists($text) { - # - # Form HTML definition lists. - # - $less_than_tab = $this->tab_width - 1; - - # Re-usable pattern to match any entire dl list: - $whole_list_re = '(?> - ( # $1 = whole list - ( # $2 - [ ]{0,'.$less_than_tab.'} - ((?>.*\S.*\n)+) # $3 = defined term - \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another term - [ ]{0,'.$less_than_tab.'} - (?: \S.*\n )+? # defined term - \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - (?! # Negative lookahead for another definition - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - ) - ) - )'; // mx - - $text = preg_replace_callback('{ - (?>\A\n?|(?<=\n\n)) - '.$whole_list_re.' - }mx', - array(&$this, '_doDefLists_callback'), $text); - - return $text; - } - function _doDefLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: - $list = $matches[1]; - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $result = trim($this->processDefListItems($list)); - $result = "<dl>\n" . $result . "\n</dl>"; - return $this->hashBlock($result) . "\n\n"; - } - - - function processDefListItems($list_str) { - # - # Process the contents of a single definition list, splitting it - # into individual term and definition list items. - # - $less_than_tab = $this->tab_width - 1; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - # Process definition terms. - $list_str = preg_replace_callback('{ - (?>\A\n?|\n\n+) # leading line - ( # definition terms = $1 - [ ]{0,'.$less_than_tab.'} # leading whitespace - (?!\:[ ]|[ ]) # negative lookahead for a definition - # mark (colon) or more whitespace. - (?> \S.* \n)+? # actual term (not whitespace). - ) - (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed - # with a definition mark. - }xm', - array(&$this, '_processDefListItems_callback_dt'), $list_str); - - # Process actual definitions. - $list_str = preg_replace_callback('{ - \n(\n+)? # leading line = $1 - ( # marker space = $2 - [ ]{0,'.$less_than_tab.'} # whitespace before colon - \:[ ]+ # definition mark (colon) - ) - ((?s:.+?)) # definition text = $3 - (?= \n+ # stop at next definition mark, - (?: # next term or end of text - [ ]{0,'.$less_than_tab.'} \:[ ] | - <dt> | \z - ) - ) - }xm', - array(&$this, '_processDefListItems_callback_dd'), $list_str); - - return $list_str; - } - function _processDefListItems_callback_dt($matches) { - $terms = explode("\n", trim($matches[1])); - $text = ''; - foreach ($terms as $term) { - $term = $this->runSpanGamut(trim($term)); - $text .= "\n<dt>" . $term . "</dt>"; - } - return $text . "\n"; - } - function _processDefListItems_callback_dd($matches) { - $leading_line = $matches[1]; - $marker_space = $matches[2]; - $def = $matches[3]; - - if ($leading_line || preg_match('/\n{2,}/', $def)) { - # Replace marker with the appropriate whitespace indentation - $def = str_repeat(' ', strlen($marker_space)) . $def; - $def = $this->runBlockGamut($this->outdent($def . "\n\n")); - $def = "\n". $def ."\n"; - } - else { - $def = rtrim($def); - $def = $this->runSpanGamut($this->outdent($def)); - } - - return "\n<dd>" . $def . "</dd>\n"; - } - - - function doFencedCodeBlocks($text) { - # - # Adding the fenced code block syntax to regular Markdown: - # - # ~~~ - # Code block - # ~~~ - # - $less_than_tab = $this->tab_width; - - $text = preg_replace_callback('{ - (?:\n|\A) - # 1: Opening marker - ( - (?:~{3,}|`{3,}) # 3 or more tildes/backticks. - ) - [ ]* - (?: - \.?([-_:a-zA-Z0-9]+) # 2: standalone class name - | - '.$this->id_class_attr_catch_re.' # 3: Extra attributes - )? - [ ]* \n # Whitespace and newline following marker. - - # 4: Content - ( - (?> - (?!\1 [ ]* \n) # Not a closing marker. - .*\n+ - )+ - ) - - # Closing marker. - \1 [ ]* (?= \n ) - }xm', - array(&$this, '_doFencedCodeBlocks_callback'), $text); - - return $text; - } - function _doFencedCodeBlocks_callback($matches) { - $classname =& $matches[2]; - $attrs =& $matches[3]; - $codeblock = $matches[4]; - $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); - $codeblock = preg_replace_callback('/^\n+/', - array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); - - if ($classname != "") { - if ($classname{0} == '.') - $classname = substr($classname, 1); - $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; - } else { - $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs); - } - $pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; - $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; - $codeblock = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>"; - - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - function _doFencedCodeBlocks_newlines($matches) { - return str_repeat("<br$this->empty_element_suffix", - strlen($matches[0])); - } - - - # - # Redefining emphasis markers so that emphasis by underscore does not - # work in the middle of a word. - # - public $em_relist = array( - '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)', - '*' => '(?<=\S|^)(?<!\*)\*(?!\*)', - '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])', - ); - public $strong_relist = array( - '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)', - '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)', - '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])', - ); - public $em_strong_relist = array( - '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)', - '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)', - '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])', - ); - - - function formParagraphs($text) { - # - # Params: - # $text - string to process with html <p> tags - # - # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap <p> tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - $value = trim($this->runSpanGamut($value)); - - # Check if this should be enclosed in a paragraph. - # Clean tag hashes & block tag hashes are left alone. - $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); - - if ($is_p) { - $value = "<p>$value</p>"; - } - $grafs[$key] = $value; - } - - # Join grafs in one text, then unhash HTML tags. - $text = implode("\n\n", $grafs); - - # Finish by removing any tag hashes still present in $text. - $text = $this->unhash($text); - - return $text; - } - - - ### Footnotes - - function stripFootnotes($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: [^id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 - [ ]* - \n? # maybe *one* newline - ( # text = $2 (no blank lines allowed) - (?: - .+ # actual text - | - \n # newlines but - (?!\[\^.+?\]:\s)# negative lookahead for footnote marker. - (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed - # by non-indented content - )* - ) - }xm', - array(&$this, '_stripFootnotes_callback'), - $text); - return $text; - } - function _stripFootnotes_callback($matches) { - $note_id = $this->fn_id_prefix . $matches[1]; - $this->footnotes[$note_id] = $this->outdent($matches[2]); - return ''; # String that will replace the block - } - - - function doFootnotes($text) { - # - # Replace footnote references in $text [^id] with a special text-token - # which will be replaced by the actual footnote marker in appendFootnotes. - # - if (!$this->in_anchor) { - $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); - } - return $text; - } - - - function appendFootnotes($text) { - # - # Append footnote list to text. - # - $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $text); - - if (!empty($this->footnotes_ordered)) { - $text .= "\n\n"; - $text .= "<div class=\"footnotes\">\n"; - $text .= "<hr". $this->empty_element_suffix ."\n"; - $text .= "<ol>\n\n"; - - $attr = ""; - if ($this->fn_backlink_class != "") { - $class = $this->fn_backlink_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; - } - if ($this->fn_backlink_title != "") { - $title = $this->fn_backlink_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - $num = 0; - - while (!empty($this->footnotes_ordered)) { - $footnote = reset($this->footnotes_ordered); - $note_id = key($this->footnotes_ordered); - unset($this->footnotes_ordered[$note_id]); - $ref_count = $this->footnotes_ref_count[$note_id]; - unset($this->footnotes_ref_count[$note_id]); - unset($this->footnotes[$note_id]); - - $footnote .= "\n"; # Need to append newline before parsing. - $footnote = $this->runBlockGamut("$footnote\n"); - $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $footnote); - - $attr = str_replace("%%", ++$num, $attr); - $note_id = $this->encodeAttribute($note_id); - - # Prepare backlink, multiple backlinks if multiple references - $backlink = "<a href=\"#fnref:$note_id\"$attr>↩</a>"; - for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { - $backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>↩</a>"; - } - # Add backlink to last paragraph; create new paragraph if needed. - if (preg_match('{</p>$}', $footnote)) { - $footnote = substr($footnote, 0, -4) . " $backlink</p>"; - } else { - $footnote .= "\n\n<p>$backlink</p>"; - } - - $text .= "<li id=\"fn:$note_id\">\n"; - $text .= $footnote . "\n"; - $text .= "</li>\n\n"; - } - - $text .= "</ol>\n"; - $text .= "</div>"; - } - return $text; - } - function _appendFootnotes_callback($matches) { - $node_id = $this->fn_id_prefix . $matches[1]; - - # Create footnote marker only if it has a corresponding footnote *and* - # the footnote hasn't been used by another marker. - if (isset($this->footnotes[$node_id])) { - $num =& $this->footnotes_numbers[$node_id]; - if (!isset($num)) { - # Transfer footnote content to the ordered list and give it its - # number - $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; - $this->footnotes_ref_count[$node_id] = 1; - $num = $this->footnote_counter++; - $ref_count_mark = ''; - } else { - $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1; - } - - $attr = ""; - if ($this->fn_link_class != "") { - $class = $this->fn_link_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; - } - if ($this->fn_link_title != "") { - $title = $this->fn_link_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - - $attr = str_replace("%%", $num, $attr); - $node_id = $this->encodeAttribute($node_id); - - return - "<sup id=\"fnref$ref_count_mark:$node_id\">". - "<a href=\"#fn:$node_id\"$attr>$num</a>". - "</sup>"; - } - - return "[^".$matches[1]."]"; - } - - - ### Abbreviations ### - - function stripAbbreviations($text) { - # - # Strips abbreviations from text, stores titles in hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: [id]*: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 - (.*) # text = $2 (no blank lines allowed) - }xm', - array(&$this, '_stripAbbreviations_callback'), - $text); - return $text; - } - function _stripAbbreviations_callback($matches) { - $abbr_word = $matches[1]; - $abbr_desc = $matches[2]; - if ($this->abbr_word_re) - $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - return ''; # String that will replace the block - } - - - function doAbbreviations($text) { - # - # Find defined abbreviations in text and wrap them in <abbr> elements. - # - if ($this->abbr_word_re) { - // cannot use the /x modifier because abbr_word_re may - // contain significant spaces: - $text = preg_replace_callback('{'. - '(?<![\w\x1A])'. - '(?:'.$this->abbr_word_re.')'. - '(?![\w\x1A])'. - '}', - array(&$this, '_doAbbreviations_callback'), $text); - } - return $text; - } - function _doAbbreviations_callback($matches) { - $abbr = $matches[0]; - if (isset($this->abbr_desciptions[$abbr])) { - $desc = $this->abbr_desciptions[$abbr]; - if (empty($desc)) { - return $this->hashPart("<abbr>$abbr</abbr>"); - } else { - $desc = $this->encodeAttribute($desc); - return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>"); - } - } else { - return $matches[0]; - } - } - -} - - -/* - -PHP Markdown Extra -================== - -Description ------------ - -This is a PHP port of the original Markdown formatter written in Perl -by John Gruber. This special "Extra" version of PHP Markdown features -further enhancements to the syntax for making additional constructs -such as tables and definition list. - -Markdown is a text-to-HTML filter; it translates an easy-to-read / -easy-to-write structured text format into HTML. Markdown's text format -is mostly similar to that of plain text email, and supports features such -as headers, *emphasis*, code blocks, blockquotes, and links. - -Markdown's syntax is designed not as a generic markup language, but -specifically to serve as a front-end to (X)HTML. You can use span-level -HTML tags anywhere in a Markdown document, and you can use block level -HTML tags (like <div> and <table> as well). - -For more information about Markdown's syntax, see: - -<http://daringfireball.net/projects/markdown/> - - -Bugs ----- - -To file bug reports please send email to: - -<michel.fortin@michelf.ca> - -Please include with your report: (1) the example input; (2) the output you -expected; (3) the output Markdown actually produced. - - -Version History ---------------- - -See the readme file for detailed release notes for this version. - - -Copyright and License ---------------------- - -PHP Markdown & Extra -Copyright (c) 2004-2013 Michel Fortin -<http://michelf.ca/> -All rights reserved. - -Based on Markdown -Copyright (c) 2003-2006 John Gruber -<http://daringfireball.net/> -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. - -*/ -?> diff --git a/plugins/jetpack/_inc/lib/markdown/gfm.php b/plugins/jetpack/_inc/lib/markdown/gfm.php deleted file mode 100644 index 081e1a11..00000000 --- a/plugins/jetpack/_inc/lib/markdown/gfm.php +++ /dev/null @@ -1,400 +0,0 @@ -<?php -/** - * GitHub-Flavoured Markdown. Inspired by Evan's plugin, but modified. - * - * @author Evan Solomon - * @author Matt Wiebe <wiebe@automattic.com> - * @link https://github.com/evansolomon/wp-github-flavored-markdown-comments - * - * Add a few extras from GitHub's Markdown implementation. Must be used in a WordPress environment. - */ - -class WPCom_GHF_Markdown_Parser extends MarkdownExtra_Parser { - - /** - * Hooray somewhat arbitrary numbers that are fearful of 1.0.x. - */ - const WPCOM_GHF_MARDOWN_VERSION = '0.9.0'; - - /** - * Use a [code] shortcode when encountering a fenced code block - * @var boolean - */ - public $use_code_shortcode = true; - - /** - * Preserve shortcodes, untouched by Markdown. - * This requires use within a WordPress installation. - * @var boolean - */ - public $preserve_shortcodes = true; - - /** - * Preserve the legacy $latex your-latex-code-here$ style - * LaTeX markup - */ - public $preserve_latex = true; - - /** - * Preserve single-line <code> blocks. - * @var boolean - */ - public $preserve_inline_code_blocks = true; - - /** - * Strip paragraphs from the output. This is the right default for WordPress, - * which generally wants to create its own paragraphs with `wpautop` - * @var boolean - */ - public $strip_paras = true; - - // Will run through sprintf - you can supply your own syntax if you want - public $shortcode_start = '[code lang=%s]'; - public $shortcode_end = '[/code]'; - - // Stores shortcodes we remove and then replace - protected $preserve_text_hash = array(); - - /** - * Set environment defaults based on presence of key functions/classes. - */ - public function __construct() { - $this->use_code_shortcode = class_exists( 'SyntaxHighlighter' ); - /** - * Allow processing shortcode contents. - * - * @module markdown - * - * @since 4.4.0 - * - * @param boolean $preserve_shortcodes Defaults to $this->preserve_shortcodes. - */ - $this->preserve_shortcodes = apply_filters( 'jetpack_markdown_preserve_shortcodes', $this->preserve_shortcodes ) && function_exists( 'get_shortcode_regex' ); - $this->preserve_latex = function_exists( 'latex_markup' ); - $this->strip_paras = function_exists( 'wpautop' ); - - parent::__construct(); - } - - /** - * Overload to specify heading styles only if the hash has space(s) after it. This is actually in keeping with - * the documentation and eases the semantic overload of the hash character. - * #Will Not Produce a Heading 1 - * # This Will Produce a Heading 1 - * - * @param string $text Markdown text - * @return string HTML-transformed text - */ - public function transform( $text ) { - // Preserve anything inside a single-line <code> element - if ( $this->preserve_inline_code_blocks ) { - $text = $this->single_line_code_preserve( $text ); - } - // Remove all shortcodes so their interiors are left intact - if ( $this->preserve_shortcodes ) { - $text = $this->shortcode_preserve( $text ); - } - // Remove legacy LaTeX so it's left intact - if ( $this->preserve_latex ) { - $text = $this->latex_preserve( $text ); - } - - // escape line-beginning # chars that do not have a space after them. - $text = preg_replace_callback( '|^#{1,6}( )?|um', array( $this, '_doEscapeForHashWithoutSpacing' ), $text ); - - /** - * Allow third-party plugins to define custom patterns that won't be processed by Markdown. - * - * @module markdown - * - * @since 3.9.2 - * - * @param array $custom_patterns Array of custom patterns to be ignored by Markdown. - */ - $custom_patterns = apply_filters( 'jetpack_markdown_preserve_pattern', array() ); - if ( is_array( $custom_patterns ) && ! empty( $custom_patterns ) ) { - foreach ( $custom_patterns as $pattern ) { - $text = preg_replace_callback( $pattern, array( $this, '_doRemoveText'), $text ); - } - } - - // run through core Markdown - $text = parent::transform( $text ); - - // Occasionally Markdown Extra chokes on a para structure, producing odd paragraphs. - $text = str_replace( "<p><</p>\n\n<p>p>", '<p>', $text ); - - // put start-of-line # chars back in place - $text = $this->restore_leading_hash( $text ); - - // Strip paras if set - if ( $this->strip_paras ) { - $text = $this->unp( $text ); - } - - // Restore preserved things like shortcodes/LaTeX - $text = $this->do_restore( $text ); - - return $text; - } - - /** - * Prevents blocks like <code>__this__</code> from turning into <code><strong>this</strong></code> - * @param string $text Text that may need preserving - * @return string Text that was preserved if needed - */ - public function single_line_code_preserve( $text ) { - return preg_replace_callback( '|<code\b[^>]*>(.*?)</code>|', array( $this, 'do_single_line_code_preserve' ), $text ); - } - - /** - * Regex callback for inline code presevation - * @param array $matches Regex matches - * @return string Hashed content for later restoration - */ - public function do_single_line_code_preserve( $matches ) { - return '<code>' . $this->hash_block( $matches[1] ) . '</code>'; - } - - /** - * Preserve code block contents by HTML encoding them. Useful before getting to KSES stripping. - * @param string $text Markdown/HTML content - * @return string Markdown/HTML content with escaped code blocks - */ - public function codeblock_preserve( $text ) { - return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_preserve' ), $text ); - } - - /** - * Regex callback for code block preservation. - * @param array $matches Regex matches - * @return string Codeblock with escaped interior - */ - public function do_codeblock_preserve( $matches ) { - $block = stripslashes( $matches[3] ); - $block = esc_html( $block ); - $block = str_replace( '\\', '\\\\', $block ); - $open = $matches[1] . $matches[2] . "\n"; - return $open . $block . $matches[4]; - } - - /** - * Restore previously preserved (i.e. escaped) code block contents. - * @param string $text Markdown/HTML content with escaped code blocks - * @return string Markdown/HTML content - */ - public function codeblock_restore( $text ) { - return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_restore' ), $text ); - } - - /** - * Regex callback for code block restoration (unescaping). - * @param array $matches Regex matches - * @return string Codeblock with unescaped interior - */ - public function do_codeblock_restore( $matches ) { - $block = html_entity_decode( $matches[3], ENT_QUOTES ); - $open = $matches[1] . $matches[2] . "\n"; - return $open . $block . $matches[4]; - } - - /** - * Called to preserve legacy LaTeX like $latex some-latex-text $ - * @param string $text Text in which to preserve LaTeX - * @return string Text with LaTeX replaced by a hash that will be restored later - */ - protected function latex_preserve( $text ) { - // regex from latex_remove() - $regex = '% - \$latex(?:=\s*|\s+) - ((?: - [^$]+ # Not a dollar - | - (?<=(?<!\\\\)\\\\)\$ # Dollar preceded by exactly one slash - )+) - (?<!\\\\)\$ # Dollar preceded by zero slashes - %ix'; - $text = preg_replace_callback( $regex, array( $this, '_doRemoveText'), $text ); - return $text; - } - - /** - * Called to preserve WP shortcodes from being formatted by Markdown in any way. - * @param string $text Text in which to preserve shortcodes - * @return string Text with shortcodes replaced by a hash that will be restored later - */ - protected function shortcode_preserve( $text ) { - $text = preg_replace_callback( $this->get_shortcode_regex(), array( $this, '_doRemoveText' ), $text ); - return $text; - } - - /** - * Restores any text preserved by $this->hash_block() - * @param string $text Text that may have hashed preservation placeholders - * @return string Text with hashed preseravtion placeholders replaced by original text - */ - protected function do_restore( $text ) { - // Reverse hashes to ensure nested blocks are restored. - $hashes = array_reverse( $this->preserve_text_hash, true ); - foreach( $hashes as $hash => $value ) { - $placeholder = $this->hash_maker( $hash ); - $text = str_replace( $placeholder, $value, $text ); - } - // reset the hash - $this->preserve_text_hash = array(); - return $text; - } - - /** - * Regex callback for text preservation - * @param array $m Regex $matches array - * @return string A placeholder that will later be replaced by the original text - */ - protected function _doRemoveText( $m ) { - return $this->hash_block( $m[0] ); - } - - /** - * Call this to store a text block for later restoration. - * @param string $text Text to preserve for later - * @return string Placeholder that will be swapped out later for the original text - */ - protected function hash_block( $text ) { - $hash = md5( $text ); - $this->preserve_text_hash[ $hash ] = $text; - $placeholder = $this->hash_maker( $hash ); - return $placeholder; - } - - /** - * Less glamorous than the Keymaker - * @param string $hash An md5 hash - * @return string A placeholder hash - */ - protected function hash_maker( $hash ) { - return 'MARKDOWN_HASH' . $hash . 'MARKDOWN_HASH'; - } - - /** - * Remove bare <p> elements. <p>s with attributes will be preserved. - * @param string $text HTML content - * @return string <p>-less content - */ - public function unp( $text ) { - return preg_replace( "#<p>(.*?)</p>(\n|$)#ums", '$1$2', $text ); - } - - /** - * A regex of all shortcodes currently registered by the current - * WordPress installation - * @uses get_shortcode_regex() - * @return string A regex for grabbing shortcodes. - */ - protected function get_shortcode_regex() { - $pattern = get_shortcode_regex(); - - // don't match markdown link anchors that could be mistaken for shortcodes. - $pattern .= '(?!\()'; - - return "/$pattern/s"; - } - - /** - * Since we escape unspaced #Headings, put things back later. - * @param string $text text with a leading escaped hash - * @return string text with leading hashes unescaped - */ - protected function restore_leading_hash( $text ) { - return preg_replace( "/^(<p>)?(#|\\\\#)/um", "$1#", $text ); - } - - /** - * Overload to support ```-fenced code blocks for pre-Markdown Extra 1.2.8 - * https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks - */ - public function doFencedCodeBlocks( $text ) { - // If we're at least at 1.2.8, native fenced code blocks are in. - // Below is just copied from it in case we somehow got loaded on - // top of someone else's Markdown Extra - if ( version_compare( MARKDOWNEXTRA_VERSION, '1.2.8', '>=' ) ) - return parent::doFencedCodeBlocks( $text ); - - # - # Adding the fenced code block syntax to regular Markdown: - # - # ~~~ - # Code block - # ~~~ - # - $less_than_tab = $this->tab_width; - - $text = preg_replace_callback('{ - (?:\n|\A) - # 1: Opening marker - ( - (?:~{3,}|`{3,}) # 3 or more tildes/backticks. - ) - [ ]* - (?: - \.?([-_:a-zA-Z0-9]+) # 2: standalone class name - | - '.$this->id_class_attr_catch_re.' # 3: Extra attributes - )? - [ ]* \n # Whitespace and newline following marker. - - # 4: Content - ( - (?> - (?!\1 [ ]* \n) # Not a closing marker. - .*\n+ - )+ - ) - - # Closing marker. - \1 [ ]* (?= \n ) - }xm', - array($this, '_doFencedCodeBlocks_callback'), $text); - - return $text; - } - - /** - * Callback for pre-processing start of line hashes to slyly escape headings that don't - * have a leading space - * @param array $m preg_match matches - * @return string possibly escaped start of line hash - */ - public function _doEscapeForHashWithoutSpacing( $m ) { - if ( ! isset( $m[1] ) ) - $m[0] = '\\' . $m[0]; - return $m[0]; - } - - /** - * Overload to support Viper's [code] shortcode. Because awesome. - */ - public function _doFencedCodeBlocks_callback( $matches ) { - // in case we have some escaped leading hashes right at the start of the block - $matches[4] = $this->restore_leading_hash( $matches[4] ); - // just MarkdownExtra_Parser if we're not going ultra-deluxe - if ( ! $this->use_code_shortcode ) { - return parent::_doFencedCodeBlocks_callback( $matches ); - } - - // default to a "text" class if one wasn't passed. Helps with encoding issues later. - if ( empty( $matches[2] ) ) { - $matches[2] = 'text'; - } - - $classname =& $matches[2]; - $codeblock = preg_replace_callback('/^\n+/', array( $this, '_doFencedCodeBlocks_newlines' ), $matches[4] ); - - if ( $classname{0} == '.' ) - $classname = substr( $classname, 1 ); - - $codeblock = esc_html( $codeblock ); - $codeblock = sprintf( $this->shortcode_start, $classname ) . "\n{$codeblock}" . $this->shortcode_end; - return "\n\n" . $this->hashBlock( $codeblock ). "\n\n"; - } - -} diff --git a/plugins/jetpack/_inc/lib/plugins.php b/plugins/jetpack/_inc/lib/plugins.php deleted file mode 100644 index 9c8e3bc4..00000000 --- a/plugins/jetpack/_inc/lib/plugins.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php -/** - * Plugins Library - * - * Helper functions for installing and activating plugins. - * - * Used by the REST API - * - * @autounit api plugins - */ - -include_once( 'class.jetpack-automatic-install-skin.php' ); - -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 ); - } else if ( 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 ); - - $result = $upgrader->install( $zip_url ); - - if ( is_wp_error( $result ) ) { - return $result; - } - - $plugin = Jetpack_Plugins::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'; - } - - return new WP_Error( $error_code, $error, 400 ); - } - - return (array) $upgrader->skin->get_upgrade_messages(); - } - - protected static function generate_wordpress_org_plugin_download_link( $plugin_slug ) { - return "https://downloads.wordpress.org/plugin/$plugin_slug.latest-stable.zip"; - } - - 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; - } - - 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; - } -} diff --git a/plugins/jetpack/_inc/lib/tonesque.php b/plugins/jetpack/_inc/lib/tonesque.php deleted file mode 100644 index 17158e3d..00000000 --- a/plugins/jetpack/_inc/lib/tonesque.php +++ /dev/null @@ -1,237 +0,0 @@ -<?php -/* -Plugin Name: Tonesque -Plugin URI: http://automattic.com/ -Description: Grab an average color representation from an image. -Version: 1.0 -Author: Automattic, Matias Ventura -Author URI: http://automattic.com/ -License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html -*/ - -class Tonesque { - - private $image_url = ''; - private $image_obj = NULL; - private $color = ''; - - function __construct( $image_url ) { - if ( ! class_exists( 'Jetpack_Color' ) ) { - jetpack_require_lib( 'class.color' ); - } - - $this->image_url = esc_url_raw( $image_url ); - $this->image_url = trim( $this->image_url ); - /** - * Allows any image URL to be passed in for $this->image_url. - * - * @module theme-tools - * - * @since 2.5.0 - * - * @param string $image_url The URL to any image - */ - $this->image_url = apply_filters( 'tonesque_image_url', $this->image_url ); - - $this->image_obj = self::imagecreatefromurl( $this->image_url ); - } - - public static function imagecreatefromurl( $image_url ) { - $data = null; - - // If it's a URL: - if ( preg_match( '#^https?://#i', $image_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']; - - if ( wp_startswith( $type, 'image/' ) ) { - $data = file_get_contents( $_image_path ); - } - } - } - - if ( empty( $data ) ) { - $response = wp_remote_get( $image_url ); - if ( is_wp_error( $response ) ) { - return false; - } - $data = wp_remote_retrieve_body( $response ); - } - } - - // 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']; - - if ( wp_startswith( $type, 'image/' ) ) { - $data = file_get_contents( $image_url ); - } - } - - // Now turn it into an image and return it. - return imagecreatefromstring( $data ); - } - - /** - * - * Construct object from image. - * - * @param optional $type (hex, rgb, hsv) - * @return color as a string formatted as $type - * - */ - function color( $type = 'hex' ) { - // Bail if there is no image to work with - if ( ! $this->image_obj ) - return false; - - // Finds dominant color - $color = self::grab_color(); - // 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 - * - */ - function grab_points( $type = 'index' ) { - $img = $this->image_obj; - if ( ! $img ) - return false; - - $height = imagesy( $img ); - $width = imagesx( $img ); - - // 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 ); - $rightx = round( ( $width / 3 ) * 2 ); - $centery = round( $height / 2 ); - $centerx = round( $width / 2 ); - - // Cast those colors into an array - $points = array( - imagecolorat( $img, $leftx, $topy ), - imagecolorat( $img, $rightx, $topy ), - imagecolorat( $img, $leftx, $bottomy ), - imagecolorat( $img, $rightx, $bottomy ), - imagecolorat( $img, $centerx, $centery ), - ); - - 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' ); - } - } - - return $points; - } - - /** - * - * Finds the average color of the image based on five sample points - * - * @param $image - * @return array() with rgb color - * - */ - function grab_color() { - $img = $this->image_obj; - if ( ! $img ) - return false; - - $rgb = self::grab_points(); - - // 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 ); - } - - // The average color of the image as rgb array - $color = array( - 'r' => $red, - 'g' => $green, - 'b' => $blue, - ); - - return $color; - } - - /** - * - * Get a Color object using /lib class.color - * Convert to appropriate type - * - * @return string - * - */ - function get_color( $color, $type ) { - $c = new Jetpack_Color( $color, 'rgb' ); - $this->color = $c; - - switch ( $type ) { - case 'rgb' : - $color = implode( $c->toRgbInt(), ',' ); - break; - case 'hex' : - $color = $c->toHex(); - break; - case 'hsv' : - $color = implode( $c->toHsvInt(), ',' ); - break; - default: - return $color = $c->toHex(); - } - - return $color; - } - - /** - * - * Checks contrast against main color - * Gives either black or white for using with opacity - * - * @return string - * - */ - function contrast() { - if ( ! $this->color ) - return false; - - $c = $this->color->getMaxContrastColor(); - return implode( $c->toRgbInt(), ',' ); - } - -}; diff --git a/plugins/jetpack/_inc/lib/tracks/class.tracks-client.php b/plugins/jetpack/_inc/lib/tracks/class.tracks-client.php deleted file mode 100644 index b83c94f1..00000000 --- a/plugins/jetpack/_inc/lib/tracks/class.tracks-client.php +++ /dev/null @@ -1,191 +0,0 @@ -<?php - -/** - * Jetpack_Tracks_Client - * @autounit nosara tracks-client - * - * Send Tracks events on behalf of a user - * - * Example Usage: -```php - require( dirname(__FILE__).'path/to/tracks/class.tracks-client' ); - - $result = Jetpack_Tracks_Client::record_event( array( - '_en' => $event_name, // required - '_ui' => $user_id, // required unless _ul is provided - '_ul' => $user_login, // required unless _ui is provided - - // Optional, but recommended - '_ts' => $ts_in_ms, // Default: now - '_via_ip' => $client_ip, // we use it for geo, etc. - - // Possibly useful to set some context for the event - '_via_ua' => $client_user_agent, - '_via_url' => $client_url, - '_via_ref' => $client_referrer, - - // For user-targeted tests - 'abtest_name' => $abtest_name, - 'abtest_variation' => $abtest_variation, - - // Your application-specific properties - 'custom_property' => $some_value, - ) ); - - if ( is_wp_error( $result ) ) { - // Handle the error in your app - } -``` - */ - -require_once( dirname(__FILE__).'/class.tracks-client.php' ); - -class Jetpack_Tracks_Client { - const PIXEL = 'https://pixel.wp.com/t.gif'; - const BROWSER_TYPE = 'php-agent'; - const USER_AGENT_SLUG = 'tracks-client'; - const VERSION = '0.3'; - - /** - * record_event - * @param mixed $event Event object to send to Tracks. An array will be cast to object. Required. - * Properties are included directly in the pixel query string after light validation. - * @return mixed True on success, WP_Error on failure - */ - static function record_event( $event ) { - if ( ! Jetpack::jetpack_tos_agreed() || ! empty( $_COOKIE['tk_opt-out'] ) ) { - return false; - } - - if ( ! $event instanceof Jetpack_Tracks_Event ) { - $event = new Jetpack_Tracks_Event( $event ); - } - if ( is_wp_error( $event ) ) { - return $event; - } - - $pixel = $event->build_pixel_url( $event ); - - if ( ! $pixel ) { - return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 ); - } - - return self::record_pixel( $pixel ); - } - - /** - * Synchronously request the pixel - */ - static function record_pixel( $pixel ) { - // Add the Request Timestamp and URL terminator just before the HTTP request. - $pixel .= '&_rt=' . self::build_timestamp() . '&_=_'; - - $response = wp_remote_get( $pixel, array( - 'blocking' => true, // The default, but being explicit here :) - 'timeout' => 1, - 'redirection' => 2, - 'httpversion' => '1.1', - 'user-agent' => self::get_user_agent(), - ) ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = isset( $response['response']['code'] ) ? $response['response']['code'] : 0; - - if ( $code !== 200 ) { - return new WP_Error( 'request_failed', 'Tracks pixel request failed', $code ); - } - - return true; - } - - static function get_user_agent() { - return Jetpack_Tracks_Client::USER_AGENT_SLUG . '-v' . Jetpack_Tracks_Client::VERSION; - } - - /** - * Build an event and return its tracking URL - * @deprecated Call the `build_pixel_url` method on a Jetpack_Tracks_Event object instead. - * @param array $event Event keys and values - * @return string URL of a tracking pixel - */ - static function build_pixel_url( $event ) { - $_event = new Jetpack_Tracks_Event( $event ); - return $_event->build_pixel_url(); - } - - /** - * Validate input for a tracks event. - * @deprecated Instantiate a Jetpack_Tracks_Event object instead - * @param array $event Event keys and values - * @return mixed Validated keys and values or WP_Error on failure - */ - private static function validate_and_sanitize( $event ) { - $_event = new Jetpack_Tracks_Event( $event ); - if ( is_wp_error( $_event ) ) { - return $_event; - } - return get_object_vars( $_event ); - } - - // Milliseconds since 1970-01-01 - static function build_timestamp() { - $ts = round( microtime( true ) * 1000 ); - return number_format( $ts, 0, '', '' ); - } - - /** - * Grabs the user's anon id from cookies, or generates and sets a new one - * - * @return string An anon id for the user - */ - static function get_anon_id() { - static $anon_id = null; - - if ( ! isset( $anon_id ) ) { - - // Did the browser send us a cookie? - if ( isset( $_COOKIE[ 'tk_ai' ] ) && preg_match( '#^[A-Za-z0-9+/=]{24}$#', $_COOKIE[ 'tk_ai' ] ) ) { - $anon_id = $_COOKIE[ 'tk_ai' ]; - } else { - - $binary = ''; - - // Generate a new anonId and try to save it in the browser's cookies - // Note that base64-encoding an 18 character string generates a 24-character anon id - for ( $i = 0; $i < 18; ++$i ) { - $binary .= chr( mt_rand( 0, 255 ) ); - } - - $anon_id = 'jetpack:' . base64_encode( $binary ); - - if ( ! headers_sent() - && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) - && ! ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) - ) { - setcookie( 'tk_ai', $anon_id ); - } - } - } - - return $anon_id; - } - - /** - * Gets the WordPress.com user's Tracks identity, if connected. - * - * @return array|bool - */ - static function get_connected_user_tracks_identity() { - if ( ! $user_data = Jetpack::get_connected_user_data() ) { - return false; - } - - return array( - 'userid' => $user_data['ID'], - 'username' => $user_data['login'], - ); - } -} diff --git a/plugins/jetpack/_inc/lib/tracks/class.tracks-event.php b/plugins/jetpack/_inc/lib/tracks/class.tracks-event.php deleted file mode 100644 index fb86e0ba..00000000 --- a/plugins/jetpack/_inc/lib/tracks/class.tracks-event.php +++ /dev/null @@ -1,149 +0,0 @@ -<?php - -/** - * @autounit nosara tracks-client - * - * Example Usage: -```php - require_once( dirname(__FILE__) . 'path/to/tracks/class.tracks-event' ); - - $event = new Jetpack_Tracks_Event( array( - '_en' => $event_name, // required - '_ui' => $user_id, // required unless _ul is provided - '_ul' => $user_login, // required unless _ui is provided - - // Optional, but recommended - '_via_ip' => $client_ip, // for geo, etc. - - // Possibly useful to set some context for the event - '_via_ua' => $client_user_agent, - '_via_url' => $client_url, - '_via_ref' => $client_referrer, - - // For user-targeted tests - 'abtest_name' => $abtest_name, - 'abtest_variation' => $abtest_variation, - - // Your application-specific properties - 'custom_property' => $some_value, - ) ); - - if ( is_wp_error( $event->error ) ) { - // Handle the error in your app - } - - $bump_and_redirect_pixel = $event->build_signed_pixel_url(); -``` - */ - -require_once( dirname(__FILE__) . '/class.tracks-client.php' ); - -class Jetpack_Tracks_Event { - const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/'; - const PROP_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/'; - public $error; - - function __construct( $event ) { - $_event = self::validate_and_sanitize( $event ); - if ( is_wp_error( $_event ) ) { - $this->error = $_event; - return; - } - - foreach( $_event as $key => $value ) { - $this->{$key} = $value; - } - } - - function record() { - return Jetpack_Tracks_Client::record_event( $this ); - } - - /** - * Annotate the event with all relevant info. - * @param mixed $event Object or (flat) array - * @return mixed The transformed event array or WP_Error on failure. - */ - static function validate_and_sanitize( $event ) { - $event = (object) $event; - - // Required - if ( ! $event->_en ) { - return new WP_Error( 'invalid_event', 'A valid event must be specified via `_en`', 400 ); - } - - // delete non-routable addresses otherwise geoip will discard the record entirely - if ( property_exists( $event, '_via_ip' ) && preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) { - unset($event->_via_ip); - } - - $validated = array( - 'browser_type' => Jetpack_Tracks_Client::BROWSER_TYPE, - '_aua' => Jetpack_Tracks_Client::get_user_agent(), - ); - - $_event = (object) array_merge( (array) $event, $validated ); - - // If you want to blacklist property names, do it here. - - // Make sure we have an event timestamp. - if ( ! isset( $_event->_ts ) ) { - $_event->_ts = Jetpack_Tracks_Client::build_timestamp(); - } - - return $_event; - } - - /** - * Build a pixel URL that will send a Tracks event when fired. - * On error, returns an empty string (''). - * - * @return string A pixel URL or empty string ('') if there were invalid args. - */ - function build_pixel_url() { - if ( $this->error ) { - return ''; - } - - $args = get_object_vars( $this ); - - // Request Timestamp and URL Terminator must be added just before the HTTP request or not at all. - unset( $args['_rt'] ); - unset( $args['_'] ); - - $validated = self::validate_and_sanitize( $args ); - - if ( is_wp_error( $validated ) ) - return ''; - - return Jetpack_Tracks_Client::PIXEL . '?' . http_build_query( $validated ); - } - - static function event_name_is_valid( $name ) { - return preg_match( Jetpack_Tracks_Event::EVENT_NAME_REGEX, $name ); - } - - static function prop_name_is_valid( $name ) { - return preg_match( Jetpack_Tracks_Event::PROP_NAME_REGEX, $name ); - } - - static function scrutinize_event_names( $event ) { - if ( ! Jetpack_Tracks_Event::event_name_is_valid( $event->_en ) ) { - return; - } - - $whitelisted_key_names = array( - 'anonId', - 'Browser_Type', - ); - - foreach ( array_keys( (array) $event ) as $key ) { - if ( in_array( $key, $whitelisted_key_names ) ) { - continue; - } - if ( ! Jetpack_Tracks_Event::prop_name_is_valid( $key ) ) { - return; - } - } - } -} diff --git a/plugins/jetpack/_inc/lib/tracks/client.php b/plugins/jetpack/_inc/lib/tracks/client.php deleted file mode 100644 index ef7cfaea..00000000 --- a/plugins/jetpack/_inc/lib/tracks/client.php +++ /dev/null @@ -1,130 +0,0 @@ -<?php -/** - * PHP Tracks Client - * @autounit nosara tracks-client - * Example Usage: - * -```php - include( plugin_dir_path( __FILE__ ) . 'lib/tracks/client.php'); - $result = jetpack_tracks_record_event( $user, $event_name, $properties ); - - if ( is_wp_error( $result ) ) { - // Handle the error in your app - } -``` - */ - -// Load the client classes -require_once( dirname(__FILE__) . '/class.tracks-event.php' ); -require_once( dirname(__FILE__) . '/class.tracks-client.php' ); - -// Now, let's export a sprinkling of syntactic sugar! - -/** - * Procedurally (vs. Object-oriented), track an event object (or flat array) - * NOTE: Use this only when the simpler jetpack_tracks_record_event() function won't work for you. - * @param \Jetpack_Tracks_Event $event The event object. - * @return \Jetpack_Tracks_Event|\WP_Error - */ -function jetpack_tracks_record_event_raw( $event ) { - return Jetpack_Tracks_Client::record_event( $event ); -} - -/** - * Procedurally build a Tracks Event Object. - * NOTE: Use this only when the simpler jetpack_tracks_record_event() function won't work for you. - * @param $identity WP_user object - * @param string $event_name The name of the event - * @param array $properties Custom properties to send with the event - * @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred - * @return \Jetpack_Tracks_Event|\WP_Error - */ -function jetpack_tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) { - - $identity = jetpack_tracks_get_identity( $user->ID ); - - $properties['user_lang'] = $user->get( 'WPLANG' ); - - $blog_details = array( - 'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ) - ); - - $timestamp = ( $event_timestamp_millis !== false ) ? $event_timestamp_millis : round( microtime( true ) * 1000 ); - $timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' ); - - return new Jetpack_Tracks_Event( array_merge( $blog_details, (array) $properties, $identity, array( - '_en' => $event_name, - '_ts' => $timestamp_string - ) ) ); -} - -/* - * Get the identity to send to tracks. - * - * @param int $user_id The user id of the local user - * @return array $identity - */ -function jetpack_tracks_get_identity( $user_id ) { - - // Meta is set, and user is still connected. Use WPCOM ID - $wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true ); - if ( $wpcom_id && Jetpack::is_user_connected( $user_id ) ) { - return array( - '_ut' => 'wpcom:user_id', - '_ui' => $wpcom_id - ); - } - - // User is connected, but no meta is set yet. Use WPCOM ID and set meta. - if ( Jetpack::is_user_connected( $user_id ) ) { - $wpcom_user_data = Jetpack::get_connected_user_data( $user_id ); - add_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'], true ); - - return array( - '_ut' => 'wpcom:user_id', - '_ui' => $wpcom_user_data['ID'] - ); - } - - // User isn't linked at all. Fall back to anonymous ID. - $anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true ); - if ( ! $anon_id ) { - $anon_id = Jetpack_Tracks_Client::get_anon_id(); - add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false ); - } - - if ( ! isset( $_COOKIE[ 'tk_ai' ] ) && ! headers_sent() ) { - setcookie( 'tk_ai', $anon_id ); - } - - return array( - '_ut' => 'anon', - '_ui' => $anon_id - ); - -} - -/** - * Record an event in Tracks - this is the preferred way to record events from PHP. - * - * @param mixed $identity username, user_id, or WP_user object - * @param string $event_name The name of the event - * @param array $properties Custom properties to send with the event - * @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred - * @return bool true for success | \WP_Error if the event pixel could not be fired - */ -function jetpack_tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) { - - // We don't want to track user events during unit tests/CI runs. - if ( $user instanceof WP_User && 'wptests_capabilities' === $user->cap_key ) { - return false; - } - - $event_obj = jetpack_tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis ); - - if ( is_wp_error( $event_obj->error ) ) { - return $event_obj->error; - } - - return $event_obj->record(); -} 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 f9ed5cfd..00000000 --- a/plugins/jetpack/_inc/lib/tracks/tracks-ajax.js +++ /dev/null @@ -1,63 +0,0 @@ -/* global jpTracksAJAX, jQuery */ -(function( $, jpTracksAJAX ) { - window.jpTracksAJAX = window.jpTracksAJAX || {}; - const 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 ) { - // 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 ) { - 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 7ce54a94..00000000 --- a/plugins/jetpack/_inc/lib/tracks/tracks-callables.js +++ /dev/null @@ -1,72 +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 || []; - -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 deleted file mode 100644 index 3f072b75..00000000 --- a/plugins/jetpack/_inc/lib/widgets.php +++ /dev/null @@ -1,776 +0,0 @@ -<?php -/** - * Widgets and Sidebars Library - * - * Helper functions for manipulating widgets on a per-blog basis. - * Only helpful on `wp_loaded` or later (currently requires widgets to be registered and the theme context to already be loaded). - * - * Used by the REST API - * - * @autounit api widgets - */ - -class Jetpack_Widgets { - - /** - * Returns the `sidebars_widgets` option with the `array_version` element removed. - * - * @return array The current value of sidebars_widgets - */ - public static function get_sidebars_widgets() { - $sidebars = get_option( 'sidebars_widgets', array() ); - if ( isset( $sidebars['array_version'] ) ) { - unset( $sidebars['array_version'] ); - } - return $sidebars; - } - - /** - * Format widget data for output and for use by other widget functions. - * - * The output looks like: - * - * array( - * '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 array (Optional) $settings The settings for the widget. - * - * @return array A normalized array representing this widget. - */ - 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]; - } - $widget = array(); - - $widget['id'] = $widget_id; - $widget['id_base'] = self::get_widget_id_base( $widget_id ); - $widget['settings'] = $settings; - $widget['sidebar'] = $sidebar; - $widget['position'] = $position; - - return $widget; - } - - /** - * Return a widget's id_base from its id. - * - * @param string $widget_id The id of a widget. (eg: 'text-3') - * - * @return string The id_base of a widget (eg: 'text'). - */ - public static function get_widget_id_base( $widget_id ) { - // Grab what's before the hyphen. - return substr( $widget_id, 0, strrpos( $widget_id, '-' ) ); - } - - /** - * 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') - * - * @return string The option name of the widget's settings. (eg: 'widget_text') - */ - public static function get_widget_option_name( $widget_id ) { - return 'widget_' . self::get_widget_id_base( $widget_id ); - } - - /** - * Determine a widget instance key from its ID. (eg: 'text-3' becomes '3'). - * Used to access the widget's settings. - * - * @param string $widget_id The id of a widget. - * - * @return integer The instance key of that widget. - */ - public static function get_widget_instance_key( $widget_id ) { - // Grab all numbers from the end of the id. - preg_match('/(\d+)$/', $widget_id, $matches ); - - return intval( $matches[0] ); - } - - /** - * Return a widget by ID (formatted for output) or null if nothing is found. - * - * @param string $widget_id The id of a widget to look for. - * - * @return array|null The matching formatted widget (see format_widget). - */ - public static function get_widget_by_id( $widget_id ) { - $found = null; - foreach ( self::get_all_widgets() as $widget ) { - if ( $widget['id'] === $widget_id ) { - $found = $widget; - } - } - return $found; - } - - /** - * Return an array of all widgets (active and inactive) formatted for output. - * - * @return array An array of all widgets (see format_widget). - */ - public static function get_all_widgets() { - $all_widgets = array(); - $sidebars_widgets = self::get_all_sidebars(); - - foreach ( $sidebars_widgets as $sidebar => $widgets ) { - if ( ! is_array( $widgets ) ) { - continue; - } - foreach ( $widgets as $key => $widget_id ) { - array_push( $all_widgets, self::format_widget( $key, $widget_id, $sidebar ) ); - } - } - - return $all_widgets; - } - - /** - * Return an array of all active widgets formatted for output. - * - * @return array An array of all active widgets (see format_widget). - */ - public static function get_active_widgets() { - $active_widgets = array(); - $all_widgets = self::get_all_widgets(); - foreach( $all_widgets as $widget ) { - if ( 'wp_inactive_widgets' === $widget['sidebar'] ) { - continue; - } - array_push( $active_widgets, $widget ); - } - return $active_widgets; - } - - /** - * Return an array of all widget IDs (active and inactive) - * - * @return array An array of all widget IDs. - */ - public static function get_all_widget_ids() { - $all_widgets = array(); - $sidebars_widgets = self::get_all_sidebars(); - foreach ( array_values( $sidebars_widgets ) as $widgets ) { - if ( ! is_array( $widgets ) ) { - continue; - } - foreach ( array_values( $widgets ) as $widget_id ) { - array_push( $all_widgets, $widget_id ); - } - } - return $all_widgets; - } - - /** - * Return an array of widgets with a specific id_base (eg: `text`). - * - * @param string $id_base The id_base of a widget type. - * - * @return array All the formatted widgets matching that widget type (see format_widget). - */ - public static function get_widgets_with_id_base( $id_base ) { - $matching_widgets = array(); - foreach ( self::get_all_widgets() as $widget ) { - if ( self::get_widget_id_base( $widget['id'] ) === $id_base ) { - array_push( $matching_widgets, $widget ); - } - } - return $matching_widgets; - } - - /** - * Return the array of widget IDs in a sidebar or null if that sidebar does - * not exist. Will return an empty array for an existing empty sidebar. - * - * @param string $sidebar The id of a sidebar. - * - * @return array|null The array of widget IDs in the sidebar. - */ - public static function get_widgets_in_sidebar( $sidebar ) { - $sidebars = self::get_all_sidebars(); - - - if ( ! $sidebars || ! is_array( $sidebars ) ) { - return null; - } - if ( ! $sidebars[ $sidebar ] && array_key_exists( $sidebar, $sidebars ) ) { - return array(); - } - return $sidebars[ $sidebar ]; - } - - /** - * Return an associative array of all registered sidebars for this theme, - * active and inactive, including the hidden disabled widgets sidebar (keyed - * by `wp_inactive_widgets`). Each sidebar is keyed by the ID of the sidebar - * and its value is an array of widget IDs for that sidebar. - * - * @return array An associative array of all sidebars and their widget IDs. - */ - public static function get_all_sidebars() { - $sidebars_widgets = self::get_sidebars_widgets(); - - if ( ! is_array( $sidebars_widgets ) ) { - return array(); - } - return $sidebars_widgets; - } - - /** - * Return an associative array of all active sidebars for this theme, Each - * sidebar is keyed by the ID of the sidebar and its value is an array of - * widget IDs for that sidebar. - * - * @return array An associative array of all active sidebars and their widget IDs. - */ - public static function get_active_sidebars() { - $sidebars = array(); - foreach ( self::get_all_sidebars() as $sidebar => $widgets ) { - if ( 'wp_inactive_widgets' === $sidebar || ! isset( $widgets ) || ! is_array( $widgets ) ) { - continue; - } - $sidebars[ $sidebar ] = $widgets; - } - return $sidebars; - } - - /** - * Activates a widget in a sidebar. Does not validate that the sidebar exists, - * so please do that first. Also does not save the widget's settings. Please - * do that with `set_widget_settings`. - * - * If position is not set, it will be set to the next available position. - * - * @param string $widget_id The newly-formed id of the widget to be added. - * @param string $sidebar The id of the sidebar where the widget will be added. - * @param string|integer $position (Optional) The position within the sidebar where the widget will be added. - * - * @return bool - */ - public static function add_widget_to_sidebar( $widget_id, $sidebar, $position ) { - return self::move_widget_to_sidebar( array( 'id' => $widget_id ), $sidebar, $position ); - } - - /** - * Removes a widget from a sidebar. Does not validate that the sidebar exists - * or remove any settings from the widget, so please do that separately. - * - * @param array $widget The widget to be removed. - */ - public static function remove_widget_from_sidebar( $widget ) { - $sidebars_widgets = self::get_sidebars_widgets(); - // Remove the widget from its old location and reflow the positions of the remaining widgets. - array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 ); - - update_option( 'sidebars_widgets', $sidebars_widgets ); - } - - /** - * Moves a widget to a sidebar. Does not validate that the sidebar exists, - * so please do that first. Also does not save the widget's settings. Please - * do that with `set_widget_settings`. The first argument should be a - * widget as returned by `format_widget` including `id`, `sidebar`, and - * `position`. - * - * If $position is not set, it will be set to the next available position. - * - * Can be used to add a new widget to a sidebar if - * $widget['sidebar'] === NULL - * - * Can be used to move a widget within a sidebar as well if - * $widget['sidebar'] === $sidebar. - * - * @param array $widget The widget to be moved (see format_widget). - * @param string $sidebar The sidebar where this widget will be moved. - * @param string|integer $position (Optional) The position where this widget will be moved in the sidebar. - * - * @return bool - */ - 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 ( isset( $widget['sidebar'] ) && isset( $widget['position'] ) ) { - array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 ); - } - - // Sometimes an existing empty sidebar is NULL, so initialize it. - if ( array_key_exists( $sidebar, $sidebars_widgets ) && ! is_array( $sidebars_widgets[ $sidebar ] ) ) { - $sidebars_widgets[ $sidebar ] = array(); - } - - // If no position is passed, set one from items in sidebar - if ( ! isset( $position ) ) { - $position = 0; - $last_position = self::get_last_position_in_sidebar( $sidebar ); - if ( isset( $last_position ) && is_numeric( $last_position ) ) { - $position = $last_position + 1; - } - } - - // Add the widget to the sidebar and reflow the positions of the other widgets. - if ( empty( $sidebars_widgets[ $sidebar ] ) ) { - $sidebars_widgets[ $sidebar ][] = $widget['id']; - } else { - array_splice( $sidebars_widgets[ $sidebar ], (int)$position, 0, $widget['id'] ); - } - - set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $sidebars_widgets ) ); - return update_option( 'sidebars_widgets', $sidebars_widgets ); - } - - /** - * Return an integer containing the largest position number in a sidebar or - * null if there are no widgets in that sidebar. - * - * @param string $sidebar The id of a sidebar. - * - * @return integer|null The last index position of a widget in that sidebar. - */ - public static function get_last_position_in_sidebar( $sidebar ) { - $widgets = self::get_widgets_in_sidebar( $sidebar ); - if ( ! $widgets ) { - return null; - } - $last_position = 0; - foreach ( $widgets as $widget_id ) { - $widget = self::get_widget_by_id( $widget_id ); - if ( intval( $widget['position'] ) > intval( $last_position ) ) { - $last_position = intval( $widget['position'] ); - } - } - return $last_position; - } - - /** - * Saves settings for a widget. Does not add that widget to a sidebar. Please - * do that with `move_widget_to_sidebar` first. Will merge the settings of - * 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. - * - * @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 ]; - - if ( ! $settings = self::sanitize_widget_settings( $widget_id, $settings, $old_settings ) ) { - return new WP_Error( 'invalid_data', 'Update failed.', 500 ); - } - if ( is_array( $old_settings ) ) { - // array_filter prevents empty arguments from replacing existing ones - $settings = wp_parse_args( array_filter( $settings ), $old_settings ); - } - - $widget_settings[ $instance_key ] = $settings; - - return update_option( $widget_option_name, $widget_settings ); - } - - /** - * 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. - * - * @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 ) ) ) { - return false; - } - $new_settings = $widget->update( $settings, $old_settings ); - if ( ! is_array( $new_settings ) ) { - return false; - } - return $new_settings; - } - - /** - * Deletes settings for a widget. Does not remove that widget to a sidebar. Please - * do that with `remove_widget_from_sidebar` first. - * - * @param array $widget The widget which will have its settings removed (see format_widget). - */ - public static function remove_widget_settings( $widget ) { - $widget_option_name = self::get_widget_option_name( $widget['id'] ); - $widget_settings = get_option( $widget_option_name ); - unset( $widget_settings[ self::get_widget_instance_key( $widget['id'] ) ] ); - update_option( $widget_option_name, $widget_settings ); - } - - /** - * 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 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. - */ - public static function update_widget( $widget_id, $sidebar, $position, $settings ) { - $settings = self::decode_settings( $settings ); - if ( isset( $settings ) && ! is_array( $settings ) ) { - return new WP_Error( 'invalid_data', 'Invalid settings', 400 ); - } - // Default to an empty array if nothing is specified. - if ( ! is_array( $settings ) ) { - $settings = array(); - } - $widget = self::get_widget_by_id( $widget_id ); - if ( ! $widget ) { - return new WP_Error( 'not_found', 'No widget found.', 400 ); - } - if ( ! $sidebar ) { - $sidebar = $widget['sidebar']; - } - if ( ! isset( $position ) ) { - $position = $widget['position']; - } - if ( ! is_numeric( $position ) ) { - return new WP_Error( 'invalid_data', 'Invalid position', 400 ); - } - $widgets_in_sidebar = self::get_widgets_in_sidebar( $sidebar ); - if ( ! isset( $widgets_in_sidebar ) ) { - return new WP_Error( 'invalid_data', 'No such sidebar exists', 400 ); - } - self::move_widget_to_sidebar( $widget, $sidebar, $position ); - $widget_save_status = self::set_widget_settings( $widget_id, $settings ); - if ( is_wp_error( $widget_save_status ) ) { - return $widget_save_status; - } - return self::get_widget_by_id( $widget_id ); - } - - /** - * 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') - * - * @return array|WP_Error An empty array if successful. - */ - public static function delete_widget( $widget_id ) { - $widget = self::get_widget_by_id( $widget_id ); - if ( ! $widget ) { - return new WP_Error( 'not_found', 'No widget found.', 400 ); - } - self::remove_widget_from_sidebar( $widget ); - self::remove_widget_settings( $widget ); - return array(); - } - - /** - * Return an array of settings. The input can be either an object, a JSON - * string, or an array. - * - * @param array|string|object $settings The settings of a widget as passed into the API. - * - * @return array Decoded associative array of settings. - */ - public static function decode_settings( $settings ) { - // 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 - if ( is_string( $settings ) ) { - $settings = (array) json_decode( $settings ); - } - return $settings; - } - - /** - * 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 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'. - */ - public static function activate_widget( $id_base, $sidebar, $position, $settings ) { - if ( ! isset( $id_base ) || ! self::validate_id_base( $id_base ) ) { - return new WP_Error( 'invalid_data', 'Invalid ID base', 400 ); - } - - if ( ! isset( $sidebar ) ) { - return new WP_Error( 'invalid_data', 'No sidebar provided', 400 ); - } - - if ( isset( $position ) && ! is_numeric( $position ) ) { - return new WP_Error( 'invalid_data', 'Invalid position', 400 ); - } - - $settings = self::decode_settings( $settings ); - if ( isset( $settings ) && ! is_array( $settings ) ) { - return new WP_Error( 'invalid_data', 'Invalid settings', 400 ); - } - - // Default to an empty array if nothing is specified. - if ( ! is_array( $settings ) ) { - $settings = array(); - } - - $widget_counter = 1 + self::get_last_widget_instance_key_with_id_base( $id_base ); - $widget_id = $id_base . '-' . $widget_counter; - if ( 0 >= $widget_counter ) { - return new WP_Error( 'invalid_data', 'Error creating widget ID' . $widget_id, 500 ); - } - if ( self::get_widget_by_id( $widget_id ) ) { - return new WP_Error( 'invalid_data', 'Widget ID already exists', 500 ); - } - - self::add_widget_to_sidebar( $widget_id, $sidebar, $position ); - $widget_save_status = self::set_widget_settings( $widget_id, $settings ); - if ( is_wp_error( $widget_save_status ) ) { - return $widget_save_status; - } - - // Add a Tracks event for non-Headstart activity. - if ( ! defined( 'HEADSTART' ) ) { - jetpack_require_lib( 'tracks/client' ); - jetpack_tracks_record_event( wp_get_current_user(), 'wpcom_widgets_activate_widget', array( - 'widget' => $id_base, - 'settings' => json_encode( $settings ), - ) ); - } - - return self::get_widget_by_id( $widget_id ); - } - - /** - * Activate an array of new widgets. Like calling `activate_widget` multiple times. - * - * @param array $widgets An array of widget arrays. Each sub-array must be of the format required by `activate_widget`. - * - * @return array|WP_Error The newly added widgets in the form returned by `get_all_widgets`. - */ - public static function activate_widgets( $widgets ) { - if ( ! is_array( $widgets ) ) { - return new WP_Error( 'invalid_data', 'Invalid widgets', 400 ); - } - - $added_widgets = array(); - - foreach( $widgets as $widget ) { - $added_widgets[] = self::activate_widget( $widget['id_base'], $widget['sidebar'], $widget['position'], $widget['settings'] ); - } - - return $added_widgets; - } - - /** - * Return the last instance key (integer) of an existing widget matching - * `$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') - * - * @return integer The last instance key of that type of widget. - */ - public static function get_last_widget_instance_key_with_id_base( $id_base ) { - $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` - usort( $similar_widgets, __CLASS__ . '::sort_widgets' ); - - $last_widget = array_pop( $similar_widgets ); - $last_val = intval( self::get_widget_instance_key( $last_widget['id'] ) ); - - return $last_val; - } - - return 0; - } - - /** - * Method used to sort widgets - * - * @since 5.4 - * - * @param array $a - * @param array $b - * - * @return int - */ - public static function sort_widgets( $a, $b ) { - $a_val = intval( self::get_widget_instance_key( $a['id'] ) ); - $b_val = intval( self::get_widget_instance_key( $b['id'] ) ); - if ( $a_val > $b_val ) { - return 1; - } - if ( $a_val < $b_val ) { - return -1; - } - return 0; - } - - /** - * Retrieve a given widget object instance by ID base (eg. 'text' or 'archives'). - * - * @param string $id_base The id_base of a type of widget. - * - * @return WP_Widget|false The found widget object or false if the id_base was not found. - */ - public static function get_registered_widget_object( $id_base ) { - if ( ! $id_base ) { - return false; - } - - // Get all of the registered widgets. - global $wp_widget_factory; - if ( ! isset( $wp_widget_factory ) ) { - return false; - } - - $registered_widgets = $wp_widget_factory->widgets; - if ( empty( $registered_widgets ) ) { - return false; - } - - foreach ( array_values( $registered_widgets ) as $registered_widget_object ) { - if ( $registered_widget_object->id_base === $id_base ) { - return $registered_widget_object; - } - } - return false; - } - - /** - * Validate a given widget ID base (eg. 'text' or 'archives'). - * - * @param string $id_base The id_base of a type of widget. - * - * @return boolean True if the widget is of a known type. - */ - public static function validate_id_base( $id_base ) { - return ( false !== self::get_registered_widget_object( $id_base ) ); - } - - /** - * 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 - $sidebars_widgets = get_option( 'sidebars_widgets', array() ); - $widget_instances = get_option( 'widget_' . $widget_id, array() ); - - // 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; - - // 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 - $widget_instances[ $next_key ] = $widget_options; - - // 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; - } - - /** - * 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 - $sidebars_widgets = get_option( 'sidebars_widgets', array() ); - $widget_instances = get_option( 'widget_' . $widget_id, array() ); - - // Retrieve index of first widget instance in that sidebar - $widget_key = false; - foreach ( $sidebars_widgets[ $sidebar ] as $widget ) { - if ( strpos( $widget, $widget_id ) !== false ) { - $widget_key = absint( str_replace( $widget_id . '-', '', $widget ) ); - break; - } - } - - // 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 - 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 - if ( ! ( update_option( 'widget_' . $widget_id, $widget_instances ) ) ) { - return new WP_Error( 'widget_update_failed', 'Failed to update widget.', 400 ); - }; - }; - return true; - } - - /** - * Retrieve the first active sidebar. - * - * @return string|WP_Error First active sidebar, error if none exists. - */ - static function get_first_sidebar() { - $active_sidebars = get_option( 'sidebars_widgets', array() ); - unset( $active_sidebars[ 'wp_inactive_widgets' ], $active_sidebars[ 'array_version' ] ); - - if ( empty( $active_sidebars ) ) { - return false; - } - $active_sidebars_keys = array_keys( $active_sidebars ); - return array_shift( $active_sidebars_keys ); - } -} |