diff options
Diffstat (limited to 'plugins/jetpack/_inc/lib')
37 files changed, 1356 insertions, 1255 deletions
diff --git a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php index b9987a0f..5f519ff2 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class-jetpack-about-page.php @@ -27,6 +27,13 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { protected $dont_show_if_not_active = true; /** + * Anonymous info about a12s. The method fetch_a8c_data() stores the response from wpcom here. + * + * @var array + */ + private $a8c_data = null; + + /** * Add a submenu item to the Jetpack admin menu. * * @return string @@ -34,9 +41,9 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { public function get_page_hook() { // Add the main admin Jetpack menu. return add_submenu_page( - 'jetpack', - esc_html__( 'About Jetpack', 'jetpack' ), + null, esc_html__( 'About Jetpack', 'jetpack' ), + '', 'jetpack_admin_page', 'jetpack_about', array( $this, 'render' ) @@ -49,9 +56,7 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { * @param string $hook Hook of current page, unused. */ public function add_page_actions( $hook ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // 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, 'submenu_order' ) ); + $this->a8c_data = $this->fetch_a8c_data(); } /** @@ -82,37 +87,6 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { } /** - * Change order of menu item so the About page menu item is below Site Stats. - * - * @param array $menu_order List of menu slugs. It's unaffected. This filter is used to reorder the Jetpack submenu items. - * - * @return array - */ - public function submenu_order( $menu_order ) { - global $submenu; - - $stats_key = null; - $about_key = null; - - foreach ( $submenu['jetpack'] as $index => $menu_item ) { - if ( false !== array_search( 'stats', $menu_item, true ) ) { - $stats_key = $index; - } - if ( false !== array_search( 'jetpack_about', $menu_item, true ) ) { - $about_key = $index; - } - } - - if ( $stats_key && $about_key ) { - $temp = $submenu['jetpack'][ $stats_key ]; - $submenu['jetpack'][ $stats_key ] = $submenu['jetpack'][ $about_key ]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $submenu['jetpack'][ $about_key ] = $temp; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - } - - return $menu_order; - } - - /** * Render the page content */ public function page_render() { @@ -171,7 +145,17 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { </a> </p> <p> - <?php esc_html_e( 'We’re a distributed company with over 875 Automatticians in more than 67 countries speaking at least 83 different languages. Our common goal is to democratize publishing so that anyone with a story can tell it, regardless of income, gender, politics, language, or where they live in the world.', 'jetpack' ); ?> + <?php + echo esc_html( + sprintf( + /* translators: first placeholder is the number of Automattic employees. The second is the number of countries of origin*/ + __( 'We’re a distributed company with over %1$s Automatticians in more than %2$s countries speaking at least %3$s different languages. Our common goal is to democratize publishing so that anyone with a story can tell it, regardless of income, gender, politics, language, or where they live in the world.', 'jetpack' ), + $this->a8c_data['a12s'], + $this->a8c_data['countries'], + $this->a8c_data['languages'] + ) + ); + ?> </p> <p> <?php esc_html_e( 'We believe in Open Source and the vast majority of our work is available under the GPL.', 'jetpack' ); ?> @@ -253,12 +237,7 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { ); // slugs for plugins we want to display. - $a8c_plugins = array( - 'woocommerce', - 'wp-super-cache', - 'wp-job-manager', - 'co-authors-plus', - ); + $a8c_plugins = $this->a8c_data['featured_plugins']; // need this to access the plugins_api() function. include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; @@ -433,9 +412,9 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { /* translators: 1: "Update WordPress" screen URL, 2: "Update PHP" page URL */ ' ' . wp_kses( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ), esc_url( self_admin_url( 'update-core.php' ) ), - esc_url( $this->jp_get_update_php_url() ) + esc_url( wp_get_update_php_url() ) ); - $this->jp_update_php_annotation(); + wp_update_php_annotation(); } elseif ( current_user_can( 'update_core' ) ) { printf( /* translators: %s: "Update WordPress" screen URL */ @@ -446,9 +425,9 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { printf( /* translators: %s: "Update PHP" page URL */ ' ' . wp_kses( __( '<a href="%s">Learn more about updating PHP</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ), - esc_url( $this->jp_get_update_php_url() ) + esc_url( wp_get_update_php_url() ) ); - $this->jp_update_php_annotation(); + wp_update_php_annotation(); } } elseif ( ! $compatible_wp ) { esc_html_e( 'This plugin doesn’t work with your version of WordPress.', 'jetpack' ); @@ -465,9 +444,9 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { printf( /* translators: %s: "Update PHP" page URL */ ' ' . wp_kses( __( '<a href="%s">Learn more about updating PHP</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ), - esc_url( $this->jp_get_update_php_url() ) + esc_url( wp_get_update_php_url() ) ); - $this->jp_update_php_annotation(); + wp_update_php_annotation(); } } echo '</p></div>'; @@ -479,7 +458,7 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { <h3> <a href="<?php echo esc_url( $details_link ); ?>" class="jptracks thickbox open-plugin-details-modal" data-jptracks-name="jetpack_about_plugin_modal" data-jptracks-prop="<?php echo esc_attr( $plugin['slug'] ); ?>"> <?php echo esc_html( $title ); ?> - <img src="<?php echo esc_attr( $plugin_icon_url ); ?>" class="plugin-icon" alt=""> + <img src="<?php echo esc_url( $plugin_icon_url ); ?>" class="plugin-icon" alt="<?php esc_attr_e( 'Plugin icon', 'jetpack' ); ?>" aria-hidden="true"> </a> </h3> </div> @@ -542,7 +521,43 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { } /** - * Fetch Gravatar hashes for public A12s from wpcom and display them as a list. + * Fetch anonymous data about A12s from wpcom: total count, number of countries, languages spoken. + * + * @since 7.4 + * + * @return array $data + */ + private function fetch_a8c_data() { + $data = get_transient( 'jetpack_a8c_data' ); + if ( false === $data ) { + $data = json_decode( + wp_remote_retrieve_body( + wp_remote_get( 'https://public-api.wordpress.com/wpcom/v2/jetpack-about' ) + ), + true + ); + if ( ! empty( $data ) && is_array( $data ) ) { + set_transient( 'jetpack_a8c_data', $data, DAY_IN_SECONDS ); + } else { + // Fallback if everything fails. + $data = array( + 'a12s' => 888, + 'countries' => 69, + 'languages' => 83, + 'featured_plugins' => array( + 'woocommerce', + 'wp-super-cache', + 'wp-job-manager', + 'co-authors-plus', + ), + ); + } + } + return $data; + } + + /** + * Compile and display a list of avatars for A12s that gave their permission. * * @since 7.3 */ @@ -556,7 +571,7 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { 'https://1.gravatar.com/avatar/c510e69d83c7d10be4df64feeff4e46a', 'https://0.gravatar.com/avatar/88ec0dcadea38adf5f30a17e54e9b248', 'https://1.gravatar.com/avatar/bc45834430c5b0936d76e3f468f9ca57', - 'https://0.gravatar.com/avatar/032677e4115f3a38dc7785529e8cc4d9', + 'https://0.gravatar.com/avatar/0619d4de8aef78c81b2194ff1d164d85', 'https://0.gravatar.com/avatar/72a638c2520ea177976e8eafb201a82f', 'https://0.gravatar.com/avatar/b3618d70c63bbc5cc7caee0beded5ff0', 'https://1.gravatar.com/avatar/4d346581a3340e32cf93703c9ce46bd4', @@ -579,101 +594,4 @@ class Jetpack_About_Page extends Jetpack_Admin_Page { ) ); } - - // The following methods jp_get_update_php_url, jp_get_default_update_php_url, and jp_update_php_annotation, - // are copies of functions introduced in WP 5.1 - // At the time of releasing this, we're still supporting WP 5.0, so we needed - // to have them here to avoid fatal errors in old installations. - - /** - * Gets the URL to learn more about updating the PHP version the site is running on. - * - * This URL can be overridden by specifying an environment variable `WP_UPDATE_PHP_URL` or by using the - * {@see 'wp_update_php_url'} filter. Providing an empty string is not allowed and will result in the - * default URL being used. Furthermore the page the URL links to should preferably be localized in the - * site language. - * - * @todo: Remove when 5.1 is minimum WP version. - * @since 5.1.0 - * - * @return string URL to learn more about updating PHP. - */ - private function jp_get_update_php_url() { - $default_url = $this->jp_get_default_update_php_url(); - - $update_url = $default_url; - if ( false !== getenv( 'WP_UPDATE_PHP_URL' ) ) { - $update_url = getenv( 'WP_UPDATE_PHP_URL' ); - } - - /** - * Filters the URL to learn more about updating the PHP version the site is running on. - * - * Providing an empty string is not allowed and will result in the default URL being used. Furthermore - * the page the URL links to should preferably be localized in the site language. - * - * @since 5.1.0 - * - * @param string $update_url URL to learn more about updating PHP. - */ - $update_url = apply_filters( 'wp_update_php_url', $update_url ); - - if ( empty( $update_url ) ) { - $update_url = $default_url; - } - - return $update_url; - } - - /** - * Gets the default URL to learn more about updating the PHP version the site is running on. - * - * Do not use this function to retrieve this URL. Instead, use {@see wp_get_update_php_url()} when relying on the URL. - * This function does not allow modifying the returned URL, and is only used to compare the actually used URL with the - * default one. - * - * @todo: Remove when 5.1 is minimum WP version. - * @since 5.1.0 - * @access private - * - * @return string Default URL to learn more about updating PHP. - */ - private function jp_get_default_update_php_url() { - return _x( 'https://wordpress.org/support/update-php/', 'localized PHP upgrade information page', 'jetpack' ); - } - - /** - * Prints the default annotation for the web host altering the "Update PHP" page URL. - * - * This function is to be used after {@see wp_get_update_php_url()} to display a consistent - * annotation if the web host has altered the default "Update PHP" page URL. - * - * @todo: Remove when 5.1 is minimum WP version. - * @since 5.1.0 - */ - private function jp_update_php_annotation() { - $update_url = $this->jp_get_update_php_url(); - $default_url = $this->jp_get_default_update_php_url(); - - if ( $update_url === $default_url ) { - return; - } - - echo '<p class="description">'; - printf( - wp_kses( - /* translators: %s: default Update PHP page URL */ - __( 'This resource is provided by your web host, and is specific to your site. For more information, <a href="%s" target="_blank" rel="noopener noreferrer">see the official WordPress documentation</a>.', 'jetpack' ), - array( - 'a' => array( - 'href' => true, - 'rel' => true, - ), - ) - ), - esc_url( $default_url ) - ); - echo '</p>'; - } - } diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php index a6822678..9baa3edb 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php @@ -1,5 +1,7 @@ <?php +use Automattic\Jetpack\Status; + // Shared logic between Jetpack admin pages abstract class Jetpack_Admin_Page { // Add page specific actions given the page hook @@ -38,13 +40,14 @@ abstract class Jetpack_Admin_Page { 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() ) { + $is_development_mode = ( new Status() )->is_development_mode(); + // If user is not an admin and site is in Dev Mode or not connected yet then don't do anything. + if ( ! current_user_can( 'manage_options' ) && ( $is_development_mode || ! Jetpack::is_active() ) ) { 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() ) { + if ( $this->dont_show_if_not_active && ! Jetpack::is_active() && ! $is_development_mode ) { return; } @@ -66,14 +69,25 @@ abstract class Jetpack_Admin_Page { ( 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) && ! Jetpack::is_active() && current_user_can( 'jetpack_connect' ) - && ! Jetpack::is_development_mode() + && ! $is_development_mode ) { add_action( 'admin_enqueue_scripts', array( 'Jetpack_Connection_Banner', 'enqueue_banner_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( 'Jetpack_Connection_Banner', 'enqueue_connect_button_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' ); } + // If Jetpack not yet connected, but user is viewing one of the pages with a Jetpack connection banner. + if ( + ( 'index.php' === $pagenow || 'plugins.php' === $pagenow ) + && ! Jetpack::is_active() + && current_user_can( 'jetpack_connect' ) + && ! $is_development_mode + ) { + add_action( 'admin_enqueue_scripts', array( 'Jetpack_Connection_Banner', 'enqueue_connect_button_scripts' ) ); + } + // Check if the site plan changed and deactivate modules accordingly. add_action( 'current_screen', array( $this, 'check_plan_deactivate_modules' ) ); @@ -147,7 +161,7 @@ abstract class Jetpack_Admin_Page { */ function check_plan_deactivate_modules( $page ) { if ( - Jetpack::is_development_mode() + ( new Status() )->is_development_mode() || ! in_array( $page->base, array( @@ -202,7 +216,7 @@ abstract class Jetpack_Admin_Page { 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( 'dops-css', plugins_url( "_inc/build/admin{$rtl}.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION ); wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min{$rtl}.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION ); $custom_css = ' #wpcontent { @@ -271,7 +285,6 @@ abstract class Jetpack_Admin_Page { <?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' : '' ); ?> @@ -314,40 +327,135 @@ abstract class Jetpack_Admin_Page { 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_modules' ); ?>" title="<?php esc_html_e( "Access the full list of Jetpack modules available on your site.", 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Modules', 'Navigation item', 'jetpack' ); ?></a> - </li> - <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> + <?php self::render_footer(); ?> </div> <?php return; } + + /** + * Output a list item with a link. + * + * @param string $url URL. + * @param string $title Link title attribute. + * @param string $text Link text. + * @param bool $is_internal Is the link linking to an internal or external domain. + */ + public static function the_footer_link( $url, $title, $text, $is_internal = true ) { + printf( + '<li class="jp-footer__link-item"><a class="jp-footer__link" href="%1$s" title="%2$s" %4$s>%3$s</a></li>', + esc_url( $url ), + esc_attr( $title ), + esc_html( $text ), + ( $is_internal ? '' : 'target="_blank" rel="noopener noreferrer"' ) + ); + } + + /** + * Render the footer of the jetpack dashboard. For admin pages. + * + * Note that the Jetpack Dashboard may append additional links to that list. + */ + public static function render_footer() { + $admin_url = admin_url( 'admin.php?page=jetpack' ); + + $is_dev_mode_or_connected = Jetpack::is_active() || ( new Status() )->is_development_mode(); + + $privacy_url = ( $is_dev_mode_or_connected ) + ? $admin_url . '#/privacy' + : 'https://automattic.com/privacy/'; + + $about_url = ( $is_dev_mode_or_connected ) + ? admin_url( 'admin.php?page=jetpack_about' ) + : 'https://jetpack.com'; + + ?> + <div class="jp-footer"> + <div class="jp-footer__a8c-attr-container"> + <a href="<?php echo esc_url( $about_url ); ?>"> + <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" id="jp-footer__links-id"> + <?php + // Version number. + self::the_footer_link( + 'https://jetpack.com', + __( 'Jetpack version', 'jetpack' ), + sprintf( + /* Translators: placeholder is a number. */ + __( 'Jetpack version %s', 'jetpack' ), + JETPACK__VERSION + ), + false + ); + + // About page. + self::the_footer_link( + $about_url, + __( 'About Jetpack', 'jetpack' ), + __( 'About', 'jetpack' ), + $is_dev_mode_or_connected + ); + + // TOS. + self::the_footer_link( + 'https://wordpress.com/tos/', + __( 'WordPress.com Terms of Service', 'jetpack' ), + _x( 'Terms', 'Navigation item', 'jetpack' ), + false + ); + + // Privacy policy. + self::the_footer_link( + $privacy_url, + __( "Automattic's Privacy Policy", 'jetpack' ), + _x( 'Privacy', 'Navigation item', 'jetpack' ), + $is_dev_mode_or_connected + ); + + // Network Admin Jetpack dashboard. + if ( is_multisite() && current_user_can( 'jetpack_network_sites_page' ) ) { + self::the_footer_link( + network_admin_url( 'admin.php?page=jetpack' ), + __( "Manage your network's Jetpack Sites", 'jetpack' ), + _x( 'Network Sites', 'Navigation item', 'jetpack' ), + true + ); + } + + // Network Admin Jetpack settings. + if ( is_multisite() && current_user_can( 'jetpack_network_settings_page' ) ) { + self::the_footer_link( + network_admin_url( 'admin.php?page=jetpack-settings' ), + __( "Manage your network's Jetpack settings", 'jetpack' ), + _x( 'Network Settings', 'Navigation item', 'jetpack' ), + true + ); + } + + // Legacy Modules page. + if ( current_user_can( 'manage_options' ) && $is_dev_mode_or_connected ) { + self::the_footer_link( + admin_url( 'admin.php?page=jetpack_modules' ), + __( 'Access the full list of Jetpack modules available on your site', 'jetpack' ), + _x( 'Modules', 'Navigation item', 'jetpack' ), + true + ); + } + + // Debugger. + if ( current_user_can( 'manage_options' ) ) { + self::the_footer_link( + admin_url( 'admin.php?page=jetpack-debugger' ), + __( "Test your site's compatibility with Jetpack", 'jetpack' ), + _x( 'Debug', 'Navigation item', 'jetpack' ), + true + ); + } + ?> + </ul> + </div> + <?php + } } diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php index e5a423f0..9d3ce44c 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php @@ -1,4 +1,6 @@ <?php +use Automattic\Jetpack\Status; + include_once( 'class.jetpack-admin-page.php' ); // Builds the landing page and its menu @@ -51,7 +53,7 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { * @since 4.3.0 */ function jetpack_add_dashboard_sub_nav_item() { - if ( Jetpack::is_development_mode() || Jetpack::is_active() ) { + if ( ( new Status() )->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' ); @@ -65,7 +67,7 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { * @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' ) ) { + if ( ( ( new Status() )->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' ); } @@ -113,6 +115,8 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { // We got the static.html so let's display it echo $static_html; + self::render_footer(); + } } @@ -144,21 +148,26 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { return; // No need for scripts on a fallback page } - $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.deps.json'; - $script_dependencies = file_exists( $script_deps_path ) - ? json_decode( file_get_contents( $script_deps_path ) ) - : array(); - $script_dependencies[] = 'wp-polyfill'; - - wp_enqueue_script( - 'react-plugin', - plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ), - $script_dependencies, - JETPACK__VERSION, - true - ); + $is_development_mode = ( new Status() )->is_development_mode(); + $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.asset.php'; + $script_dependencies = array( 'wp-polyfill' ); + if ( file_exists( $script_deps_path ) ) { + $asset_manifest = include $script_deps_path; + $script_dependencies = $asset_manifest['dependencies']; + } + + if ( Jetpack::is_active() || $is_development_mode ) { + wp_enqueue_script( + 'react-plugin', + plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ), + $script_dependencies, + JETPACK__VERSION, + true + ); + } + - if ( ! Jetpack::is_development_mode() && Jetpack::is_active() ) { + if ( ! $is_development_mode && Jetpack::is_active() ) { // Required for Analytics. wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true ); } @@ -219,6 +228,8 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { require_once JETPACK__PLUGIN_DIR . 'class.jetpack-affiliate.php'; } + $current_user_data = jetpack_current_user_data(); + return array( 'WP_API_root' => esc_url_raw( rest_url() ), 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ), @@ -227,7 +238,7 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { 'isActive' => Jetpack::is_active(), 'isStaging' => Jetpack::is_staging_site(), 'devMode' => array( - 'isActive' => Jetpack::is_development_mode(), + 'isActive' => ( new Status() )->is_development_mode(), 'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG, 'url' => site_url() && false === strpos( site_url(), '.' ), 'filter' => apply_filters( 'jetpack_development_mode', false ), @@ -236,13 +247,12 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { 'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(), 'sandboxDomain' => JETPACK__SANDBOX_DOMAIN, ), - 'connectUrl' => Jetpack::init()->build_connect_url( true, false, false ), + 'connectUrl' => $current_user_data['isConnected'] == false ? 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( @@ -259,13 +269,13 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { 'settings' => $this->get_flattened_settings( $modules ), 'userData' => array( // 'othersLinked' => Jetpack::get_other_linked_admins(), - 'currentUser' => jetpack_current_user_data(), + 'currentUser' => $current_user_data, ), 'siteData' => array( - 'icon' => has_site_icon() + 'icon' => has_site_icon() ? apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) ) : '', - 'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), + 'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison /** * Whether promotions are visible or not. * @@ -273,10 +283,11 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { * * @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(), + 'showPromotions' => apply_filters( 'jetpack_show_promotions', true ), + 'isAtomicSite' => jetpack_is_atomic_site(), + 'plan' => Jetpack_Plan::get(), + 'showBackups' => Jetpack::show_backups_ui(), + 'isMultisite' => is_multisite(), ), 'themeData' => array( 'name' => $current_theme->get( 'Name' ), @@ -295,7 +306,8 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { '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() + 'externalServicesConnectUrls' => $this->get_external_services_connect_urls(), + 'calypsoEnv' => Jetpack::get_calypso_env(), ); } @@ -322,40 +334,6 @@ class Jetpack_React_Page extends Jetpack_Admin_Page { } } -/* - * 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 - * jumpstart_dismissed : Manual dismissal of Jump Start - dismiss - * jetpack_action_taken: Deprecated since 7.3 But still listed here to respect behaviour for old versions. - * Manual activation of a module already happened - 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. * diff --git a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php index 69991c70..551b9f71 100644 --- a/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php +++ b/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php @@ -1,4 +1,8 @@ <?php + +use Automattic\Jetpack\Tracking; +use Automattic\Jetpack\Assets; + include_once( 'class.jetpack-admin-page.php' ); include_once( JETPACK__PLUGIN_DIR . 'class.jetpack-modules-list-table.php' ); @@ -117,7 +121,8 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { </div><!-- /.content --> <?php - JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_settings' ) ); + $tracking = new Tracking(); + $tracking->record_user_event( 'wpa_page_view', array( 'path' => 'old_settings' ) ); } /** @@ -133,7 +138,7 @@ class Jetpack_Settings_Page extends Jetpack_Admin_Page { 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' ), + Assets::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 index a57f2009..e145298c 100644 --- a/plugins/jetpack/_inc/lib/class.color.php +++ b/plugins/jetpack/_inc/lib/class.color.php @@ -9,7 +9,7 @@ * * @author Harold Asbridge <hasbridge@gmail.com> * @author Matt Wiebe <wiebe@automattic.com> - * @license http://www.opensource.org/licenses/MIT + * @license https://www.opensource.org/licenses/MIT */ class Jetpack_Color { @@ -125,7 +125,7 @@ class Jetpack_Color { /** * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * adapted from https://en.wikipedia.org/wiki/HSL_color_space. * @param int $h Hue. [0-360] * @param in $s Saturation [0, 100] * @param int $l Lightness [0, 100] @@ -321,13 +321,13 @@ class Jetpack_Color { } /** - * 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 - */ + * Converts an RGB color value to HSL. Conversion formula + * adapted from https://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; diff --git a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php index a0f0bf44..30a53865 100644 --- a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php +++ b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php @@ -1,4 +1,11 @@ <?php + +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\JITM; +use Automattic\Jetpack\Tracking; +use Automattic\Jetpack\Status; + /** * Register WP REST API endpoints for Jetpack. * @@ -61,7 +68,6 @@ class Jetpack_Core_Json_Api_Endpoints { 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(); @@ -74,7 +80,18 @@ class Jetpack_Core_Json_Api_Endpoints { 'methods' => WP_REST_Server::READABLE, 'callback' => __CLASS__ . '::get_plans', 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', + ) ); + register_rest_route( 'jetpack/v4', 'products', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_products', + 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', + ) ); + + register_rest_route( 'jetpack/v4', 'marketing/survey', array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => __CLASS__ . '::submit_survey', + 'permission_callback' => __CLASS__ . '::disconnect_site_permission_callback', ) ); register_rest_route( 'jetpack/v4', '/jitm', array( @@ -87,12 +104,6 @@ class Jetpack_Core_Json_Api_Endpoints { '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, @@ -139,6 +150,16 @@ class Jetpack_Core_Json_Api_Endpoints { 'permission_callback' => __CLASS__ . '::get_user_connection_data_permission_callback', ) ); + // Start the connection process by registering the site on WordPress.com servers. + register_rest_route( 'jetpack/v4', '/connection/register', array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::register_site', + 'permission_callback' => __CLASS__ . '::connect_url_permission_callback', + 'args' => array( + 'registration_nonce' => array( 'type' => 'string' ), + ), + ) ); + // Set the connection owner register_rest_route( 'jetpack/v4', '/connection/owner', array( 'methods' => WP_REST_Server::EDITABLE, @@ -191,6 +212,31 @@ class Jetpack_Core_Json_Api_Endpoints { 'permission_callback' => array( $site_endpoint , 'can_request' ), ) ); + // Get current site purchases. + register_rest_route( + 'jetpack/v4', + '/site/purchases', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $site_endpoint, 'get_purchases' ), + 'permission_callback' => array( $site_endpoint, 'can_request' ), + ) + ); + + // Get current site benefits + register_rest_route( 'jetpack/v4', '/site/benefits', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $site_endpoint, 'get_benefits' ), + 'permission_callback' => array( $site_endpoint, 'can_request' ), + ) ); + + // Get Activity Log data for this site. + register_rest_route( 'jetpack/v4', '/site/activity', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_site_activity', + 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', + ) ); + // Confirm that a site in identity crisis should be in staging mode register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array( 'methods' => WP_REST_Server::EDITABLE, @@ -341,26 +387,6 @@ class Jetpack_Core_Json_Api_Endpoints { '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, @@ -382,6 +408,12 @@ class Jetpack_Core_Json_Api_Endpoints { 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', ) ); + register_rest_route( 'jetpack/v4', '/plugins/akismet/activate', array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::activate_akismet', + 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check', + ) ); + // Plugins: check if the plugin is active. register_rest_route( 'jetpack/v4', '/plugin/(?P<plugin>[a-z\/\.\-_]+)', array( 'methods' => WP_REST_Server::READABLE, @@ -452,10 +484,20 @@ class Jetpack_Core_Json_Api_Endpoints { ), ) ); + + register_rest_route( + 'jetpack/v4', + '/mobile/send-login-email', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::send_mobile_magic_link', + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', + ) + ); } public static function get_plans( $request ) { - $request = Jetpack_Client::wpcom_json_api_request_as_user( + $request = Client::wpcom_json_api_request_as_user( '/plans?_locale=' . get_user_locale(), '2', array( @@ -466,7 +508,7 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); - $body = wp_remote_retrieve_body( $request ); + $body = json_decode( wp_remote_retrieve_body( $request ) ); if ( 200 === wp_remote_retrieve_response_code( $request ) ) { $data = $body; } else { @@ -478,6 +520,65 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Gets the WP.com products that are in use on wpcom. + * Similar to the WP.com plans that we currently in user on WPCOM. + * + * @param WP_REST_Request $request The request. + * + * @return string|WP_Error A JSON object of wpcom products if the request was successful, or a WP_Error otherwise. + */ + public static function get_products( $request ) { + $wpcom_request = Client::wpcom_json_api_request_as_user( + '/products?_locale=' . get_user_locale() . '&type=jetpack', + '2', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), + ) + ); + + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); + if ( 200 === $response_code ) { + return json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + } else { + // Something went wrong so we'll just return the response without caching. + return new WP_Error( + 'failed_to_fetch_data', + esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), + array( 'status' => $response_code ) + ); + } + } + + public static function submit_survey( $request ) { + + $wpcom_request = Client::wpcom_json_api_request_as_user( + '/marketing/survey', + 'v2', + array( + 'method' => 'POST', + 'headers' => array( + 'Content-Type' => 'application/json', + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), + ), + $request->get_json_params() + ); + + $wpcom_request_body = json_decode( wp_remote_retrieve_body( $wpcom_request ) ); + if ( 200 === wp_remote_retrieve_response_code( $wpcom_request ) ) { + $data = $wpcom_request_body; + } else { + // something went wrong so we'll just return the response without caching + return $wpcom_request_body; + } + + return $data; + } + + /** * Asks for a jitm, unless they've been disabled, in which case it returns an empty array * * @param $request WP_REST_Request @@ -485,11 +586,9 @@ class Jetpack_Core_Json_Api_Endpoints { * @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(); + $jitm = new JITM(); - if ( ! $jitm ) { + if ( ! $jitm->register() ) { return array(); } @@ -503,11 +602,9 @@ class Jetpack_Core_Json_Api_Endpoints { * @return bool Always True */ public static function delete_jitm_message( $request ) { - require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-jitm.php' ); + $jitm = new JITM(); - $jitm = Jetpack_JITM::init(); - - if ( ! $jitm ) { + if ( ! $jitm->register() ) { return true; } @@ -515,28 +612,6 @@ class Jetpack_Core_Json_Api_Endpoints { } /** - * Handles verification that a site is registered - * - * @since 5.4.0 - * - * @param WP_REST_Request $request The request sent to the WP REST API. - * - * @return array|wp-error - */ - public static function 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 * @@ -586,7 +661,6 @@ class Jetpack_Core_Json_Api_Endpoints { 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(), ) ); @@ -612,7 +686,6 @@ class Jetpack_Core_Json_Api_Endpoints { public static function verify_site( $request ) { - Jetpack::load_xml_rpc_client(); $xml = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id(), ) ); @@ -651,7 +724,6 @@ class Jetpack_Core_Json_Api_Endpoints { * @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 ); @@ -723,7 +795,7 @@ class Jetpack_Core_Json_Api_Endpoints { return true; } - return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); + return new WP_Error( 'invalid_user_permission_jetpack_connect', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) ); } @@ -749,11 +821,12 @@ class Jetpack_Core_Json_Api_Endpoints { * Check that user has permission to change the master user. * * @since 6.2.0 + * @since 7.7.0 Update so that any user with jetpack_disconnect privs can set owner. * * @return bool|WP_Error True if user is able to change master user. */ public static function set_connection_owner_permission_callback() { - if ( get_current_user_id() === Jetpack_Options::get_option( 'master_user' ) ) { + if ( current_user_can( 'jetpack_disconnect' ) ) { return true; } @@ -903,10 +976,11 @@ class Jetpack_Core_Json_Api_Endpoints { */ 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(), + 'isActive' => Jetpack::is_active(), + 'isStaging' => Jetpack::is_staging_site(), + 'isRegistered' => Jetpack::connection()->is_registered(), + 'devMode' => array( + 'isActive' => ( new Status() )->is_development_mode(), 'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG, 'url' => site_url() && false === strpos( site_url(), '.' ), 'filter' => apply_filters( 'jetpack_development_mode', false ), @@ -1050,7 +1124,7 @@ class Jetpack_Core_Json_Api_Endpoints { 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' ); + $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) .'?force=wpcom', '2', array(), null, 'wpcom' ); if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { return new WP_Error( 'rewind_data_fetch_failed' ); @@ -1120,6 +1194,33 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Registers the Jetpack site + * + * @uses Jetpack::try_registration(); + * @since 7.7.0 + * + * @param WP_REST_Request $request The request sent to the WP REST API. + * + * @return bool|WP_Error True if Jetpack successfully registered + */ + public static function register_site( $request ) { + if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) { + return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack' ), array( 'status' => 403 ) ); + } + + $response = Jetpack::try_registration(); + + if ( is_wp_error( $response ) ) { + return $response; + } + + return rest_ensure_response( + array( + 'authorizeUrl' => Jetpack::build_authorize_url( false, true ) + ) ); + } + + /** * Gets a new connect raw URL with fresh nonce. * * @uses Jetpack::disconnect(); @@ -1206,7 +1307,6 @@ class Jetpack_Core_Json_Api_Endpoints { $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(), ) ); @@ -1215,6 +1315,13 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); if ( $updated && ! $xml->isError() ) { + + // Track it + if ( class_exists( 'Automattic\Jetpack\Tracking' ) ) { + $tracking = new Tracking(); + $tracking->record_user_event( 'set_connection_owner_success' ); + } + return rest_ensure_response( array( 'code' => 'success', @@ -1232,7 +1339,7 @@ class Jetpack_Core_Json_Api_Endpoints { * Unlinks current user from the WordPress.com Servers. * * @since 4.3.0 - * @uses Jetpack::unlink_user + * @uses Automattic\Jetpack\Connection\Manager::disconnect_user * * @param WP_REST_Request $request The request sent to the WP REST API. * @@ -1244,7 +1351,7 @@ class Jetpack_Core_Json_Api_Endpoints { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } - if ( Jetpack::unlink_user() ) { + if ( Connection_Manager::disconnect_user() ) { return rest_ensure_response( array( 'code' => 'success' @@ -1270,7 +1377,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. ); } else { - $response = Jetpack_Client::wpcom_json_api_request_as_user( + $response = Client::wpcom_json_api_request_as_user( '/jetpack-user-tracking', 'v2', array( @@ -1303,7 +1410,7 @@ class Jetpack_Core_Json_Api_Endpoints { 'tracks_opt_out' => true, // Default to opt-out if not connected to wp.com. ); } else { - $response = Jetpack_Client::wpcom_json_api_request_as_user( + $response = Client::wpcom_json_api_request_as_user( '/jetpack-user-tracking', 'v2', array( @@ -1334,10 +1441,18 @@ class Jetpack_Core_Json_Api_Endpoints { $site_id = Jetpack_Options::get_option( 'id' ); if ( ! $site_id ) { - new WP_Error( 'site_id_missing' ); + new WP_Error( 'site_id_missing' ); + } + + $args = array( 'headers' => array() ); + + // Allow use a store sandbox. Internal ref: PCYsg-IA-p2. + if ( isset( $_COOKIE ) && isset( $_COOKIE['store_sandbox'] ) ) { + $secret = $_COOKIE['store_sandbox']; + $args['headers']['Cookie'] = "store_sandbox=$secret;"; } - $response = Jetpack_Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1' ); + $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d', $site_id ) .'?force=wpcom', '1.1', $args ); if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { return new WP_Error( 'site_data_fetch_failed' ); @@ -1377,6 +1492,57 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Fetch AL data for this site and return it. + * + * @since 7.4 + * + * @return array|WP_Error + */ + public static function get_site_activity() { + $site_id = Jetpack_Options::get_option( 'id' ); + + if ( ! $site_id ) { + return new WP_Error( + 'site_id_missing', + esc_html__( 'Site ID is missing.', 'jetpack' ), + array( 'status' => 400 ) + ); + } + + $response = Client::wpcom_json_api_request_as_user( "/sites/$site_id/activity", '2', array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), + ), null, 'wpcom' ); + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( 200 !== $response_code ) { + return new WP_Error( + 'activity_fetch_failed', + esc_html__( 'Could not retrieve site activity.', 'jetpack' ), + array( 'status' => $response_code ) + ); + } + + $data = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( ! isset( $data->current->orderedItems ) ) { + return new WP_Error( + 'activity_not_found', + esc_html__( 'No activity found', 'jetpack' ), + array( 'status' => 204 ) // no content + ); + } + + return rest_ensure_response( array( + 'code' => 'success', + 'data' => $data->current->orderedItems, + ) + ); + } + + /** * Handles identity crisis mitigation, confirming safe mode for this site. * * @since 4.4.0 @@ -1484,8 +1650,6 @@ class Jetpack_Core_Json_Api_Endpoints { $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' ), @@ -1510,148 +1674,6 @@ class Jetpack_Core_Json_Api_Endpoints { } /** - * 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 @@ -1705,7 +1727,7 @@ class Jetpack_Core_Json_Api_Endpoints { '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 ) ) ), + 'description' => wp_kses( sprintf( __( 'Show photo metadata (<a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) ), array( 'a' => array( 'href' => true, 'target' => true ) ) ), 'type' => 'boolean', 'default' => 0, 'validate_callback' => __CLASS__ . '::validate_boolean', @@ -2149,7 +2171,7 @@ class Jetpack_Core_Json_Api_Endpoints { // Stats 'admin_bar' => array( - 'description' => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ), + 'description' => esc_html__( 'Include a small chart in your admin bar with a 48-hour traffic snapshot.', 'jetpack' ), 'type' => 'boolean', 'default' => 1, 'validate_callback' => __CLASS__ . '::validate_boolean', @@ -3156,6 +3178,30 @@ class Jetpack_Core_Json_Api_Endpoints { } /** + * Ensures that Akismet is installed and activated. + * + * @since 7.7 + * + * @return WP_REST_Response A response indicating whether or not the installation was successful. + */ + public static function activate_akismet() { + jetpack_require_lib( 'plugins' ); + $result = Jetpack_Plugins::install_and_activate_plugin('akismet'); + + if ( is_wp_error( $result ) ) { + return rest_ensure_response( array( + 'code' => 'failure', + 'message' => esc_html__( 'Unable to activate Akismet', 'jetpack' ) + ) ); + } else { + return rest_ensure_response( array( + 'code' => 'success', + 'message' => esc_html__( 'Activated Akismet', 'jetpack' ) + ) ); + } + } + + /** * Get data about the queried plugin. Currently it only returns whether the plugin is active or not. * * @since 4.2.0 @@ -3193,4 +3239,38 @@ class Jetpack_Core_Json_Api_Endpoints { ) ); } + /** + * Proxies a request to WordPress.com to request that a magic link be sent to the current user + * to log this user in to the mobile app via email. + * + * @param WP_REST_REQUEST $request The request parameters. + * @return bool|WP_Error + */ + public static function send_mobile_magic_link( $request ) { + $xml = new Jetpack_IXR_Client( + array( + 'user_id' => get_current_user_id(), + ) + ); + + $xml->query( 'jetpack.sendMobileMagicLink', array() ); + if ( $xml->isError() ) { + return new WP_Error( + 'error_sending_mobile_magic_link', + sprintf( + '%s: %s', + $xml->getErrorCode(), + $xml->getErrorMessage() + ) + ); + } + + $response = $xml->getResponse(); + + return rest_ensure_response( + array( + 'code' => 'success', + ) + ); + } } // class end diff --git a/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php b/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php index 228c6b2c..00afeb01 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-automatic-install-skin.php @@ -65,8 +65,11 @@ class Jetpack_Automatic_Install_Skin extends Automatic_Upgrader_Skin { /** * Overwrites the feedback function + * + * @param string|array|WP_Error $data Data. + * @param mixed ...$args Optional text replacements. */ - public function feedback( $data ) { + public function feedback( $data, ...$args ) { $current_error = null; if ( is_wp_error( $data ) ) { @@ -86,8 +89,6 @@ class Jetpack_Automatic_Install_Skin extends Automatic_Upgrader_Skin { } if ( strpos( $string, '%' ) !== false ) { - $args = func_get_args(); - $args = array_splice( $args, 1 ); if ( ! empty( $args ) ) { $string = vsprintf( $string, $args ); } diff --git a/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php b/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php index c8005ea1..720f59a9 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php @@ -38,16 +38,43 @@ class Jetpack_Keyring_Service_Helper { ) ); + /** + * Constructor + */ private function __construct() { + add_action( 'admin_menu', array( __CLASS__, 'add_sharing_menu' ), 21 ); + add_action( 'load-settings_page_sharing', array( __CLASS__, 'admin_page_load' ), 9 ); } - function get_services( $filter = 'all' ) { - $services = array( + /** + * We need a `sharing` submenu page to be able to connect and disconnect services. + */ + public static function add_sharing_menu() { + global $submenu; + + if ( + ! isset( $submenu['options-general.php'] ) + || ! is_array( $submenu['options-general.php'] ) + ) { + return; + } + $general_settings_names = array_map( + function ( $menu ) { + return array_values( $menu )[0]; + }, + $submenu['options-general.php'] ); + if ( ! in_array( 'Sharing', $general_settings_names, true ) ) { + add_submenu_page( 'options-general.php', '', '', 'manage_options', 'sharing', '__return_empty_string' ); + } + } + + function get_services( $filter = 'all' ) { + $services = array(); - if ( 'all' == $filter ) { + if ( 'all' === $filter ) { return $services; } else { $connected_services = array(); @@ -161,7 +188,6 @@ class Jetpack_Keyring_Service_Helper { break; case 'completed': - Jetpack::load_xml_rpc_client(); $xml = new Jetpack_IXR_Client(); $xml->query( 'jetpack.fetchPublicizeConnections' ); @@ -190,7 +216,6 @@ class Jetpack_Keyring_Service_Helper { * 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 ); diff --git a/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php b/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php index 14de6053..01b9c61c 100644 --- a/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php +++ b/plugins/jetpack/_inc/lib/class.jetpack-password-checker.php @@ -518,7 +518,7 @@ class Jetpack_Password_Checker { * L = String length (the for iterator) * N = Our charset size, via get_charset_size() * - * @see http://en.wikipedia.org/wiki/Password_strength#Random_passwords + * @see https://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) diff --git a/plugins/jetpack/_inc/lib/class.media-extractor.php b/plugins/jetpack/_inc/lib/class.media-extractor.php index 6acf34db..ba00ff97 100644 --- a/plugins/jetpack/_inc/lib/class.media-extractor.php +++ b/plugins/jetpack/_inc/lib/class.media-extractor.php @@ -206,7 +206,7 @@ class Jetpack_Media_Meta_Extractor { if ( preg_match_all( '#(?:^|\s|"|\')(https?://([^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))))#', $content, $matches ) ) { foreach ( $matches[1] as $link_raw ) { - $url = parse_url( $link_raw ); + $url = wp_parse_url( $link_raw ); // Data URI links if ( isset( $url['scheme'] ) && 'data' === $url['scheme'] ) @@ -286,7 +286,7 @@ class Jetpack_Media_Meta_Extractor { $embeds = array(); foreach ( $matches[1] as $link_raw ) { - $url = parse_url( $link_raw ); + $url = wp_parse_url( $link_raw ); list( $proto, $link_all_but_proto ) = explode( '://', $link_raw ); @@ -402,11 +402,11 @@ class Jetpack_Media_Meta_Extractor { 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'] ) ) { + if ( ( $src = wp_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 + // If wp_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! diff --git a/plugins/jetpack/_inc/lib/class.media-summary.php b/plugins/jetpack/_inc/lib/class.media-summary.php index 732706d4..6d5f2d48 100644 --- a/plugins/jetpack/_inc/lib/class.media-summary.php +++ b/plugins/jetpack/_inc/lib/class.media-summary.php @@ -131,7 +131,7 @@ class Jetpack_Media_Summary { $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 ); + $poster_url_parts = wp_parse_url( $poster_image ); $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path']; } } @@ -162,12 +162,12 @@ class Jetpack_Media_Summary { $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 ); + $poster_url_parts = wp_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['image'] = wp_parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image']; $return['secure']['image'] = self::https( $return['image'] ); } @@ -348,8 +348,8 @@ class Jetpack_Media_Summary { static function split_content_in_words( $text ) { $words = preg_split( '/[\s!?;,.]+/', $text, null, PREG_SPLIT_NO_EMPTY ); - // Return an empty array if the split above fails. - return $words ? $words : array(); + // Return an empty array if the split above fails. + return $words ? $words : array(); } static function get_word_count( $post_content ) { @@ -358,7 +358,7 @@ class Jetpack_Media_Summary { static function get_word_remaining_count( $post_content, $excerpt_content ) { $content_word_count = count( self::split_content_in_words( self::clean_text( $post_content ) ) ); - $excerpt_word_count = count( self::split_content_in_words( self::clean_text( $excerpt_content ) ) ); + $excerpt_word_count = count( self::split_content_in_words( self::clean_text( $excerpt_content ) ) ); return (int) $content_word_count - $excerpt_word_count; } diff --git a/plugins/jetpack/_inc/lib/class.media.php b/plugins/jetpack/_inc/lib/class.media.php index e48c4aad..9e419580 100644 --- a/plugins/jetpack/_inc/lib/class.media.php +++ b/plugins/jetpack/_inc/lib/class.media.php @@ -16,7 +16,7 @@ class Jetpack_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. @@ -85,7 +85,7 @@ class Jetpack_Media { /** * 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 ) { @@ -110,58 +110,12 @@ class Jetpack_Media { * @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 ); + return jetpack_is_file_supported_for_sideloading( $file ); } /** * 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. */ @@ -173,11 +127,11 @@ class Jetpack_Media { } /** - * Save the given temporal file considering file type, + * 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. @@ -228,9 +182,9 @@ class Jetpack_Media { /** * Return an object with an snapshot of a revision item. - * + * * @param object $media_item - media post object - * @return object a revision item + * @return object a revision item */ public static function get_snapshot( $media_item ) { $current_file = get_attached_file( $media_item->ID ); @@ -242,7 +196,7 @@ class Jetpack_Media { 'file' => (string) $file_paths['basename'], 'extension' => (string) $file_paths['extension'], 'mime_type' => (string) $media_item->post_mime_type, - 'size' => (int) filesize( $current_file ) + 'size' => (int) filesize( $current_file ), ); return (object) $snapshot; @@ -250,7 +204,7 @@ class Jetpack_Media { /** * Add a new item into revision_history array. - * + * * @param object $media_item - media post object * @param file $file - file recently added * @param bool $has_original_media - condition is the original media has been already added @@ -265,7 +219,7 @@ class Jetpack_Media { } /** * Return the `revision_history` of the given media. - * + * * @param number $media_id - media post ID * @return array `revision_history` array */ @@ -294,7 +248,7 @@ class Jetpack_Media { /** * Try to delete a file according to the dirname of * the media attached file and the filename. - * + * * @param number $media_id - media post ID * @param string $filename - basename of the file ( name-of-file.ext ) * @return bool `true` is the file has been removed, `false` if not. @@ -325,7 +279,7 @@ class Jetpack_Media { * 'from' => (int) <from>, * 'to' => (int) <to>, * ) - * + * * Also, it removes the file defined in each item. * * @param number $media_id - media post ID @@ -364,7 +318,7 @@ class Jetpack_Media { /** * Limit the number of items of the `revision_history` array. * When the stack is overflowing the oldest item is remove from there (FIFO). - * + * * @param number $media_id - media post ID * @param number [$limit] - maximun amount of items. 20 as default. * @return array items removed from `revision_history` @@ -393,7 +347,7 @@ class Jetpack_Media { /** * Remove the original file and clean the post metadata. - * + * * @param number $media_id - media post ID */ public static function clean_original_media( $media_id ) { @@ -412,7 +366,7 @@ class Jetpack_Media { * - 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 */ @@ -442,7 +396,7 @@ class Jetpack_Media { * - 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. diff --git a/plugins/jetpack/_inc/lib/components.php b/plugins/jetpack/_inc/lib/components.php new file mode 100644 index 00000000..9c8a4fe2 --- /dev/null +++ b/plugins/jetpack/_inc/lib/components.php @@ -0,0 +1,109 @@ +<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Components Library + * + * Load and display a pre-rendered component + */ +class Jetpack_Components { + /** + * Load and display a pre-rendered component + * + * @since 7.7.0 + * + * @param string $name Component name. + * @param array $props Component properties. + * + * @return string The component markup + */ + public static function render_component( $name, $props ) { + + $rtl = is_rtl() ? '.rtl' : ''; + wp_enqueue_style( 'jetpack-components', plugins_url( "_inc/blocks/components{$rtl}.css", JETPACK__PLUGIN_FILE ), array( 'wp-components' ), JETPACK__VERSION ); + + ob_start(); + // `include` fails gracefully and throws a warning, but doesn't halt execution. + include JETPACK__PLUGIN_DIR . "_inc/blocks/$name.html"; + $markup = ob_get_clean(); + + foreach ( $props as $key => $value ) { + $markup = str_replace( + "#$key#", + $value, + $markup + ); + + // Workaround, required to replace strings in `sprintf`-expressions. + // See extensions/i18n-to-php.js for more information. + $markup = str_replace( + "%($key)s", + $value, + $markup + ); + } + + return $markup; + } + + /** + * Load and display a pre-rendered component + * + * @since 7.7.0 + * + * @param array $props Component properties. + * + * @return string The component markup + */ + public static function render_upgrade_nudge( $props ) { + $plan_slug = $props['plan']; + jetpack_require_lib( 'plans' ); + $plan = Jetpack_Plans::get_plan( $plan_slug ); + + if ( ! $plan ) { + return self::render_component( + 'upgrade-nudge', + array( + 'planName' => __( 'a paid plan', 'jetpack' ), + 'upgradeUrl' => '', + ) + ); + } + + // WP.com plan objects have a dedicated `path_slug` field, Jetpack plan objects don't + // For Jetpack, we thus use the plan slug with the 'jetpack_' prefix removed. + $plan_path_slug = wp_startswith( $plan_slug, 'jetpack_' ) + ? substr( $plan_slug, strlen( 'jetpack_' ) ) + : $plan->path_slug; + + $post_id = get_the_ID(); + + if ( method_exists( 'Jetpack', 'build_raw_urls' ) ) { + $site_slug = Jetpack::build_raw_urls( home_url() ); + } elseif ( class_exists( 'WPCOM_Masterbar' ) && method_exists( 'WPCOM_Masterbar', 'get_calypso_site_slug' ) ) { + $site_slug = WPCOM_Masterbar::get_calypso_site_slug( get_current_blog_id() ); + } + + // Post-checkout: redirect back to the editor. + $redirect_to = add_query_arg( + array( + 'plan_upgraded' => 1, + ), + get_edit_post_link( $post_id ) + ); + + $upgrade_url = + $plan_path_slug + ? add_query_arg( + 'redirect_to', + $redirect_to, + "https://wordpress.com/checkout/${site_slug}/${plan_path_slug}" + ) : ''; + + return self::render_component( + 'upgrade-nudge', + array( + 'planName' => $plan->product_name, + 'upgradeUrl' => $upgrade_url, + ) + ); + } +} diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php index 96a47a08..3ade1c34 100644 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php +++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php @@ -1,4 +1,7 @@ <?php + +use Automattic\Jetpack\Status; + /** * This is the base class for every Core API endpoint Jetpack uses. * @@ -196,7 +199,7 @@ class Jetpack_Core_API_Module_List_Endpoint { if ( isset( $modules[ $slug ]['requires_connection'] ) && $modules[ $slug ]['requires_connection'] - && Jetpack::is_development_mode() + && ( new Status() )->is_development_mode() ) { $modules[ $slug ]['activated'] = false; } @@ -363,7 +366,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { if ( isset( $module['requires_connection'] ) && $module['requires_connection'] - && Jetpack::is_development_mode() + && ( new Status() )->is_development_mode() ) { $module['activated'] = false; } @@ -420,19 +423,16 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { 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' ); + $value = get_option( 'WPLANG', '' ); + if ( empty( $value ) && defined( 'WPLANG' ) ) { + $value = WPLANG; + } $response[ $setting ] = empty( $value ) ? 'en_US' : $value; break; @@ -645,7 +645,7 @@ class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint { switch ( $option ) { case 'lang_id': - if ( defined( 'WPLANG' ) || ! current_user_can( 'install_languages' ) ) { + if ( ! current_user_can( 'install_languages' ) ) { // We can't affect this setting $updated = false; break; @@ -1646,7 +1646,8 @@ class Jetpack_Core_API_Module_Data_Endpoint { 'code' => 'success', 'message' => esc_html( sprintf( - __( 'Your site was successfully backed-up %s ago.', 'jetpack' ), + /* translators: placeholder is a unit of time (1 hour, 5 days, ...) */ + esc_html__( 'Your site was successfully backed up %s ago.', 'jetpack' ), human_time_diff( $data->backups->last_backup, current_time( 'timestamp' ) diff --git a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php index 68327f51..c321f6ae 100644 --- a/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php +++ b/plugins/jetpack/_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php @@ -1,12 +1,21 @@ -<?php +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName /** - * This is the endpoint class for `/site` endpoints. + * List of /site core REST API endpoints used in Jetpack's dashboard. * + * @package Jetpack + */ + +use Automattic\Jetpack\Connection\Client; + +/** + * 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 @@ -14,11 +23,11 @@ class Jetpack_Core_API_Site_Endpoint { */ 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' ); + // Make the API request. + $request = sprintf( '/sites/%d/features', Jetpack_Options::get_option( 'id' ) ); + $response = Client::wpcom_json_api_request_as_blog( $request, '1.1' ); - // Bail if there was an error or malformed response + // 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', @@ -27,10 +36,10 @@ class Jetpack_Core_API_Site_Endpoint { ); } - // Decode the results + // Decode the results. $results = json_decode( $response['body'], true ); - // Bail if there were no results or plan details returned + // Bail if there were no results or plan details returned. if ( ! is_array( $results ) ) { return new WP_Error( 'failed_to_fetch_data', @@ -39,10 +48,52 @@ class Jetpack_Core_API_Site_Endpoint { ); } - return rest_ensure_response( array( - 'code' => 'success', + return rest_ensure_response( + array( + 'code' => 'success', 'message' => esc_html__( 'Site features correctly received.', 'jetpack' ), - 'data' => wp_remote_retrieve_body( $response ), + 'data' => wp_remote_retrieve_body( $response ), + ) + ); + } + + + /** + * Returns the result of `/sites/%s/purchases` endpoint call. + * + * @return array of site purchases. + */ + public static function get_purchases() { + // Make the API request. + $request = sprintf( '/sites/%d/purchases', Jetpack_Options::get_option( 'id' ) ); + $response = 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 purchase 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 purchases correctly received.', 'jetpack' ), + 'data' => wp_remote_retrieve_body( $response ), ) ); } @@ -57,4 +108,159 @@ class Jetpack_Core_API_Site_Endpoint { public static function can_request() { return current_user_can( 'jetpack_manage_modules' ); } + + /** + * Gets an array of data that show how Jetpack is currently being used to benefit the site. + * + * @since 7.7 + * + * @return WP_REST_Response + */ + public static function get_benefits() { + $benefits = array(); + + /* + * We get different benefits from Stats: + * - this year's visitors + * - Followers (only if subs module is active) + * - Sharing counts (not currently supported in Jetpack -- https://github.com/Automattic/jetpack/issues/844 ) + */ + $stats = null; + if ( function_exists( 'stats_get_from_restapi' ) ) { + $stats = stats_get_from_restapi( array( 'fields' => 'stats' ) ); + } + + // Yearly visitors. + if ( null !== $stats && $stats->stats->visitors > 0 ) { + $benefits[] = array( + 'name' => 'jetpack-stats', + 'title' => esc_html__( 'Site Stats', 'jetpack' ), + 'description' => esc_html__( 'Visitors tracked by Jetpack', 'jetpack' ), + 'value' => absint( $stats->stats->visitors ), + ); + } + + // Protect blocked logins. + if ( Jetpack::is_module_active( 'protect' ) ) { + $protect = get_site_option( 'jetpack_protect_blocked_attempts' ); + if ( $protect > 0 ) { + $benefits[] = array( + 'name' => 'protect', + 'title' => esc_html__( 'Brute force protection', 'jetpack' ), + 'description' => esc_html__( 'The number of malicious login attempts blocked by Jetpack', 'jetpack' ), + 'value' => absint( $protect ), + ); + } + } + + // Number of followers. + if ( null !== $stats && $stats->stats->followers_blog > 0 && Jetpack::is_module_active( 'subscriptions' ) ) { + $benefits[] = array( + 'name' => 'subscribers', + 'title' => esc_html__( 'Subscribers', 'jetpack' ), + 'description' => esc_html__( 'People subscribed to your updates through Jetpack', 'jetpack' ), + 'value' => absint( $stats->stats->followers_blog ), + ); + } + + // VaultPress backups. + if ( Jetpack::is_plugin_active( 'vaultpress/vaultpress.php' ) && class_exists( 'VaultPress' ) ) { + $vaultpress = new VaultPress(); + if ( $vaultpress->is_registered() ) { + $data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + if ( $data && $data->features->backups && ! empty( $data->backups->stats ) && $data->backups->stats->revisions > 0 ) { + $benefits[] = array( + 'name' => 'jetpack-backup', + 'title' => esc_html__( 'Jetpack Backup', 'jetpack' ), + 'description' => esc_html__( 'The number of times Jetpack has backed up your site and kept it safe', 'jetpack' ), + 'value' => absint( $data->backups->stats->revisions ), + ); + } + } + } + + // Number of forms sent via a Jetpack contact form. + if ( Jetpack::is_module_active( 'contact-form' ) ) { + $contact_form_count = array_sum( get_object_vars( wp_count_posts( 'feedback' ) ) ); + if ( $contact_form_count > 0 ) { + $benefits[] = array( + 'name' => 'contact-form-feedback', + 'title' => esc_html__( 'Contact Form Feedback', 'jetpack' ), + 'description' => esc_html__( 'Form submissions stored by Jetpack', 'jetpack' ), + 'value' => absint( $contact_form_count ), + ); + } + } + + // Number of images in the library if Photon is active. + if ( Jetpack::is_module_active( 'photon' ) ) { + $photon_count = array_reduce( + get_object_vars( wp_count_attachments( array( 'image/jpeg', 'image/png', 'image/gif', 'image/bmp' ) ) ), + function ( $i, $j ) { + return $i + $j; + } + ); + if ( $photon_count > 0 ) { + $benefits[] = array( + 'name' => 'image-hosting', + 'title' => esc_html__( 'Image Hosting', 'jetpack' ), + 'description' => esc_html__( 'Super-fast, mobile-ready images served by Jetpack', 'jetpack' ), + 'value' => absint( $photon_count ), + ); + } + } + + // Number of VideoPress videos on the site. + $videopress_attachments = wp_count_attachments( 'video/videopress' ); + if ( + isset( $videopress_attachments->{'video/videopress'} ) + && $videopress_attachments->{'video/videopress'} > 0 + ) { + $benefits[] = array( + 'name' => 'video-hosting', + 'title' => esc_html__( 'Video Hosting', 'jetpack' ), + 'description' => esc_html__( 'Ad-free, lightning-fast videos delivered by Jetpack', 'jetpack' ), + 'value' => absint( $videopress_attachments->{'video/videopress'} ), + ); + } + + // Number of active Publicize connections. + if ( Jetpack::is_module_active( 'publicize' ) && class_exists( 'Publicize' ) ) { + $publicize = new Publicize(); + $connections = $publicize->get_all_connections(); + + $number_of_connections = 0; + if ( is_array( $connections ) && ! empty( $connections ) ) { + $number_of_connections = count( $connections ); + } + + if ( $number_of_connections > 0 ) { + $benefits[] = array( + 'name' => 'publicize', + 'title' => esc_html__( 'Publicize', 'jetpack' ), + 'description' => esc_html__( 'Live social media site connections, powered by Jetpack', 'jetpack' ), + 'value' => absint( $number_of_connections ), + ); + } + } + + // Total number of shares. + if ( null !== $stats && $stats->stats->shares > 0 ) { + $benefits[] = array( + 'name' => 'sharing', + 'title' => esc_html__( 'Sharing', 'jetpack' ), + 'description' => esc_html__( 'The number of times visitors have shared your posts with the world using Jetpack', 'jetpack' ), + 'value' => absint( $stats->stats->shares ), + ); + } + + // Finally, return the whole list of benefits. + return rest_ensure_response( + array( + 'code' => 'success', + 'message' => esc_html__( 'Site benefits correctly received.', 'jetpack' ), + 'data' => wp_json_encode( $benefits ), + ) + ); + } } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php index 354880ed..a6612b37 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php @@ -1,5 +1,7 @@ <?php +use Automattic\Jetpack\Connection\Client; + /** * Mailchimp: Get Mailchimp Status. * API to determine if current site has linked Mailchimp account and mailing list selected. @@ -14,6 +16,7 @@ class WPCOM_REST_API_V2_Endpoint_Mailchimp extends WP_REST_Controller { $this->wpcom_is_wpcom_only_endpoint = true; add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } /** @@ -30,6 +33,16 @@ class WPCOM_REST_API_V2_Endpoint_Mailchimp extends WP_REST_Controller { ), ) ); + register_rest_route( + $this->namespace, + $this->rest_base . '/groups', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_mailchimp_groups' ), + ), + ) + ); } /** @@ -67,13 +80,36 @@ class WPCOM_REST_API_V2_Endpoint_Mailchimp extends WP_REST_Controller { 403 ); } - $connect_url = sprintf( 'https://wordpress.com/marketing/connections/%s', rawurlencode( $site_id ) ); + $connect_url = sprintf( 'https://wordpress.com/marketing/connections/%s?mailchimp', rawurlencode( $site_id ) ); return array( 'code' => $this->is_connected() ? 'connected' : 'not_connected', 'connect_url' => $connect_url, 'site_id' => $site_id, ); } + + /** + * Get all Mailchimp groups for the accounted connected to the current blog + * + * @return mixed + * groups:array + * site_id:int + */ + public function get_mailchimp_groups() { + $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 + ); + } + $path = sprintf( '/sites/%d/mailchimp/groups', absint( $site_id ) ); + $request = Client::wpcom_json_api_request_as_blog( $path ); + $body = wp_remote_retrieve_body( $request ); + return json_decode( $body ); + } } wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Mailchimp' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php new file mode 100644 index 00000000..442a2efa --- /dev/null +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php @@ -0,0 +1,94 @@ +<?php +/** + * REST API endpoint for resolving URL redirects. + * + * @package Jetpack + * @since 8.0.0 + */ + +/** + * Resolve URL redirects. + * + * @since 8.0.0 + */ +class WPCOM_REST_API_V2_Endpoint_Resolve_Redirect extends WP_REST_Controller { + /** + * Constructor. + */ + public function __construct() { + $this->namespace = 'wpcom/v2'; + $this->rest_base = 'resolve-redirect'; + // This endpoint *does not* need to connect directly to Jetpack sites. + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } + + /** + * Register the route. + */ + public function register_routes() { + // GET /sites/<blog_id>/resolve-redirect/<url> - Follow 301/302 redirects on a URL, and return the final destination. + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P<url>.+)', + array( + 'args' => array( + 'url' => array( + 'description' => __( 'The URL to check for redirects.', 'jetpack' ), + 'type' => 'string', + 'required' => 'true', + 'validate_callback' => 'wp_http_validate_url', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'follow_redirect' ), + 'permission_callback' => 'is_user_logged_in', + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Follows 301/302 redirect for the passed URL, and returns the final destination. + * + * @param WP_REST_Request $request The REST API request data. + * @return WP_REST_Response The REST API response. + */ + public function follow_redirect( $request ) { + $response = wp_safe_remote_get( $request['url'] ); + if ( is_wp_error( $response ) ) { + return rest_ensure_response( '' ); + } + + $history = $response['http_response']->get_response_object()->history; + if ( ! $history ) { + return response_ensure_response( $request['url'] ); + } + + $location = $history[0]->headers->getValues( 'location' ); + if ( ! $location ) { + return response_ensure_response( $request['url'] ); + } + + return rest_ensure_response( $location[0] ); + } + + /** + * Retrieves the comment's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'resolve-redirect', + 'type' => 'string', + 'description' => __( 'The final destination of the URL being checked for redirects.', 'jetpack' ), + ); + + return $schema; + } +} + +wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Resolve_Redirect' ); diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php index ec997739..028568db 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php @@ -6,6 +6,8 @@ * @since 7.3.0 */ +use Automattic\Jetpack\Connection\Client; + /** * Class WPCOM_REST_API_V2_Endpoint_Memberships * This introduces V2 endpoints. @@ -108,7 +110,7 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { return $product->to_array(); } else { $blog_id = Jetpack_Options::get_option( 'id' ); - $response = Jetpack_Client::wpcom_json_api_request_as_user( + $response = Client::wpcom_json_api_request_as_user( "/sites/$blog_id/{$this->rest_base}/product", 'v2', array( @@ -141,21 +143,16 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { /** * Get a status of connection for the site. If this is Jetpack, pass the request to wpcom. * - * @return array|WP_Error + * @return WP_Error|array ['products','connected_account_id','connect_url','should_upgrade_to_access_memberships','upgrade_url'] */ public function get_status() { - $connected_account_id = Jetpack_Memberships::get_connected_account_id(); - $connect_url = ''; if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { require_lib( 'memberships' ); $blog_id = get_current_blog_id(); - if ( ! $connected_account_id ) { - $connect_url = get_memberships_connected_account_redirect( get_current_user_id(), $blog_id ); - } - $products = get_memberships_plans( $blog_id ); + return (array) get_memberships_settings_for_site( $blog_id ); } else { $blog_id = Jetpack_Options::get_option( 'id' ); - $response = Jetpack_Client::wpcom_json_api_request_as_user( + $response = Client::wpcom_json_api_request_as_user( "/sites/$blog_id/{$this->rest_base}/status", 'v2', array(), @@ -168,16 +165,11 @@ class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller { return new WP_Error( 'wpcom_connection_error', __( 'Could not connect to WordPress.com', 'jetpack' ), 404 ); } $data = isset( $response['body'] ) ? json_decode( $response['body'], true ) : null; - if ( ! $connected_account_id ) { - $connect_url = empty( $data['connect_url'] ) ? '' : $data['connect_url']; + if ( 200 !== $response['response']['code'] && $data['code'] && $data['message'] ) { + return new WP_Error( $data['code'], $data['message'], 401 ); } - $products = empty( $data['products'] ) ? array() : $data['products']; + return $data; } - return array( - 'connected_account_id' => $connected_account_id, - 'connect_url' => $connect_url, - 'products' => $products, - ); } } diff --git a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php index c1a712bd..47c95b26 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php @@ -1,5 +1,7 @@ <?php +use Automattic\Jetpack\Constants; + /** * Subscribers: Get subscriber count * @@ -41,7 +43,7 @@ class WPCOM_REST_API_V2_Endpoint_Subscribers extends WP_REST_Controller { */ 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' ) ) { + if ( ! Constants::is_defined( 'TESTING_IN_JETPACK' ) ) { delete_transient( 'wpcom_subscribers_total' ); } @@ -56,7 +58,7 @@ class WPCOM_REST_API_V2_Endpoint_Subscribers extends WP_REST_Controller { if ( Jetpack::is_module_active( 'subscriptions' ) || - ( Jetpack_Constants::is_defined( 'TESTING_IN_JETPACK' ) && Jetpack_Constants::get_constant( 'TESTING_IN_JETPACK' ) ) + ( Constants::is_defined( 'TESTING_IN_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/post-fields-publicize-connections.php b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php index c4254a9d..21181d2c 100644 --- a/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php +++ b/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php @@ -23,9 +23,11 @@ * @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 $object_type = array( 'post' ); protected $field_name = 'jetpack_publicize_connections'; + private $_meta_saved = array(); + public $memoized_updates = array(); /** @@ -34,7 +36,6 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ */ public function register_fields() { $this->object_type = get_post_types_by_support( 'publicize' ); - foreach ( $this->object_type as $post_type ) { // 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. @@ -208,14 +209,18 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ } $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 ); + if ( isset( $post->ID ) ) { + // Set the meta before we mark the post as published so that publicize works as expected. + // If this is not the case post end up on social media when they are marked as skipped. + $this->update( $request['jetpack_publicize_connections'], $post, $request ); + } + return $post; } @@ -338,6 +343,9 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ * @param WP_REST_Request */ public function update( $requested_connections, $post, $request ) { + if ( isset( $this->_meta_saved[ $post->ID ] ) ) { // Make sure we only save it once - per request. + return; + } foreach ( $this->get_meta_to_update( $requested_connections, $post->ID ) as $meta_key => $meta_value ) { if ( is_null( $meta_value ) ) { delete_post_meta( $post->ID, $meta_key ); @@ -345,6 +353,7 @@ class WPCOM_REST_API_V2_Post_Publicize_Connections_Field extends WPCOM_REST_API_ update_post_meta( $post->ID, $meta_key, $meta_value ); } } + $this->_meta_saved[ $post->ID ] = true; } } diff --git a/plugins/jetpack/_inc/lib/debugger/0-load.php b/plugins/jetpack/_inc/lib/debugger/0-load.php index a3eaf4e6..ad069244 100644 --- a/plugins/jetpack/_inc/lib/debugger/0-load.php +++ b/plugins/jetpack/_inc/lib/debugger/0-load.php @@ -5,8 +5,6 @@ * @package Jetpack. */ -global $wp_version; - /* Jetpack Connection Testing Framework */ require_once 'class-jetpack-cxn-test-base.php'; /* Jetpack Connection Tests */ @@ -15,10 +13,9 @@ require_once 'class-jetpack-cxn-tests.php'; require_once 'class-jetpack-debug-data.php'; /* The "In-Plugin Debugger" admin page. */ require_once 'class-jetpack-debugger.php'; +/* General Debugging Functions */ +require_once 'debug-functions.php'; -if ( version_compare( $wp_version, '5.2-alpha', 'ge' ) ) { - require_once 'debug-functions-for-php53.php'; - add_filter( 'debug_information', array( 'Jetpack_Debug_Data', 'core_debug_data' ) ); - add_filter( 'site_status_tests', 'jetpack_debugger_site_status_tests' ); - add_action( 'wp_ajax_health-check-jetpack-local_testing_suite', 'jetpack_debugger_ajax_local_testing_suite' ); -} +add_filter( 'debug_information', array( 'Jetpack_Debug_Data', 'core_debug_data' ) ); +add_filter( 'site_status_tests', 'jetpack_debugger_site_status_tests' ); +add_action( 'wp_ajax_health-check-jetpack-local_testing_suite', 'jetpack_debugger_ajax_local_testing_suite' ); diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php index 56f21bc4..85da12d8 100644 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php +++ b/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-test-base.php @@ -1,4 +1,6 @@ <?php +use Automattic\Jetpack\Status; + /** * Jetpack Connection Testing * @@ -319,7 +321,7 @@ class Jetpack_Cxn_Test_Base { */ public function output_results_for_cli( $type = 'all', $group = 'all' ) { if ( defined( 'WP_CLI' ) && WP_CLI ) { - if ( Jetpack::is_development_mode() ) { + if ( ( new Status() )->is_development_mode() ) { WP_CLI::line( __( 'Jetpack is in Development Mode:', 'jetpack' ) ); WP_CLI::line( Jetpack::development_mode_trigger_text() ); } diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php index 274d032b..6e3cccd6 100644 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php +++ b/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php @@ -5,6 +5,10 @@ * @package Jetpack */ +use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\Status; +use Automattic\Jetpack\Connection\Utils as Connection_Utils; + /** * Class Jetpack_Cxn_Tests contains all of the actual tests. */ @@ -65,7 +69,19 @@ class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base { * 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() ); + return ( Jetpack::is_active() && ! ( new Status() )->is_development_mode() ); + } + + /** + * 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 increase_timeout() { + return 30; // seconds. } /** @@ -75,7 +91,7 @@ class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base { $name = __FUNCTION__; if ( $this->helper_is_jetpack_connected() ) { $result = self::passing_test( $name ); - } elseif ( Jetpack::is_development_mode() ) { + } elseif ( ( new Status() )->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' ); @@ -221,14 +237,16 @@ class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base { protected function test__wpcom_connection_test() { $name = __FUNCTION__; - if ( ! Jetpack::is_active() || Jetpack::is_development_mode() || Jetpack::is_staging_site() || ! $this->pass ) { + if ( ! Jetpack::is_active() || ( new Status() )->is_development_mode() || Jetpack::is_staging_site() || ! $this->pass ) { return self::skipped_test( $name ); } - $response = Jetpack_Client::wpcom_json_api_request_as_blog( + add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) ); + $response = Client::wpcom_json_api_request_as_blog( sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ), - Jetpack_Client::WPCOM_JSON_API_VERSION + Client::WPCOM_JSON_API_VERSION ); + remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) ); if ( is_wp_error( $response ) ) { /* translators: %1$s is the error code, %2$s is the error message */ @@ -242,6 +260,10 @@ class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base { return self::failing_test( $name, $message ); } + if ( 404 === wp_remote_retrieve_response_code( $response ) ) { + return self::skipped_test( $name, __( 'The WordPress.com API returned a 404 error.', 'jetpack' ) ); + } + $result = json_decode( $body ); $is_connected = (bool) $result->connected; $message = $result->message . ': ' . wp_remote_retrieve_response_code( $response ); @@ -313,28 +335,52 @@ class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base { * * Intentionally added last as it will be skipped if any local failed conditions exist. * + * @since 7.1.0 + * @since 7.9.0 Timeout waiting for a WP.com response no longer fails the test. Test is marked skipped instead. + * * @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 ) { + + if ( ! Jetpack::is_active() || ( new Status() )->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=' ); + $testsite_url = Connection_Utils::fix_url_for_bad_hosts( JETPACK__API_BASE . 'testsite/1/?url=' ); - add_filter( 'http_request_timeout', array( 'Jetpack_Debugger', 'jetpack_increase_timeout' ) ); + add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) ); $response = wp_remote_get( $testsite_url . $self_xml_rpc_url ); - remove_filter( 'http_request_timeout', array( 'Jetpack_Debugger', 'jetpack_increase_timeout' ) ); + remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) ); + + $error_msg = wp_kses( + sprintf( + /* translators: Placeholder is a link to site's Jetpack debug page. */ + __( + '<a target="_blank" rel="noopener noreferrer" href="%s">Visit the Jetpack.com debug page</a> for more information or <a target="_blank" rel="noopener noreferrer" href="https://jetpack.com/contact-support/">contact support</a>.', + 'jetpack' + ), + esc_url( add_query_arg( 'url', rawurlencode( site_url() ), 'https://jetpack.com/support/debug/' ) ) + ), + array( + 'a' => array( + 'href' => array(), + 'target' => array(), + 'rel' => array(), + ), + ) + ); if ( 200 === wp_remote_retrieve_response_code( $response ) ) { return self::passing_test( $name ); + } elseif ( is_wp_error( $response ) && false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout. + return self::skipped_test( $name, __( 'The test timed out which may sometimes indicate a failure or may be a false failure.', 'jetpack' ) ); } 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. + return self::failing_test( $name, __( 'Jetpack.com detected an error on the WPcom Self Test.', 'jetpack' ), $error_msg ); } } } diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php index 31e38790..f128d38d 100644 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php +++ b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php @@ -5,6 +5,11 @@ * @package jetpack */ +use Automattic\Jetpack\Constants; +use Automattic\Jetpack\Sync\Modules; +use Automattic\Jetpack\Sync\Functions; +use Automattic\Jetpack\Sync\Sender; + /** * Class Jetpack_Debug_Data * @@ -89,6 +94,10 @@ class Jetpack_Debug_Data { * @return array $args Debug information in the same format as the initial argument. */ public static function core_debug_data( $debug ) { + $support_url = Jetpack::is_development_version() + ? 'https://jetpack.com/contact-support/beta-group/' + : 'https://jetpack.com/contact-support/'; + $jetpack = array( 'jetpack' => array( 'label' => __( 'Jetpack', 'jetpack' ), @@ -98,7 +107,7 @@ class Jetpack_Debug_Data { 'Diagnostic information helpful to <a href="%1$s" target="_blank" rel="noopener noreferrer">your Jetpack Happiness team<span class="screen-reader-text">%2$s</span></a>', 'jetpack' ), - esc_html( 'https://jetpack.com/contact-support/' ), + esc_url( $support_url ), __( '(opens in a new tab)', 'jetpack' ) ), 'fields' => self::debug_data(), @@ -169,25 +178,20 @@ class Jetpack_Debug_Data { * * If a token does not contain a period, then it is malformed and we report it as such. */ - $user_id = get_current_user_id(); - $user_tokens = Jetpack_Options::get_option( 'user_tokens' ); - $blog_token = Jetpack_Options::get_option( 'blog_token' ); - $user_token = null; - if ( is_array( $user_tokens ) && array_key_exists( $user_id, $user_tokens ) ) { - $user_token = $user_tokens[ $user_id ]; - } - unset( $user_tokens ); + $user_id = get_current_user_id(); + $blog_token = Jetpack_Data::get_access_token(); + $user_token = Jetpack_Data::get_access_token( $user_id ); $tokenset = ''; if ( $blog_token ) { $tokenset = 'Blog '; - $blog_key = substr( $blog_token, 0, strpos( $blog_token, '.' ) ); + $blog_key = substr( $blog_token->secret, 0, strpos( $blog_token->secret, '.' ) ); // Intentionally not translated since this is helpful when sent to Happiness. $blog_key = ( $blog_key ) ? $blog_key : 'Potentially Malformed Token.'; } if ( $user_token ) { $tokenset .= 'User'; - $user_key = substr( $user_token, 0, strpos( $user_token, '.' ) ); + $user_key = substr( $user_token->secret, 0, strpos( $user_token->secret, '.' ) ); // Intentionally not translated since this is helpful when sent to Happiness. $user_key = ( $user_key ) ? $user_key : 'Potentially Malformed Token.'; } @@ -271,14 +275,7 @@ class Jetpack_Debug_Data { ); /** Sync Debug Information */ - /** Load Sync modules */ - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-modules.php'; - /** Load Sync sender */ - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php'; - /** Load Sync functions */ - require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php'; - - $sync_module = Jetpack_Sync_Modules::get_module( 'full-sync' ); + $sync_module = Modules::get_module( 'full-sync' ); if ( $sync_module ) { $sync_statuses = $sync_module->get_status(); $human_readable_sync_status = array(); @@ -294,7 +291,7 @@ class Jetpack_Debug_Data { ); } - $queue = Jetpack_Sync_Sender::get_instance()->get_sync_queue(); + $queue = Sender::get_instance()->get_sync_queue(); $debug_info['sync_size'] = array( 'label' => 'Sync Queue Size', @@ -307,7 +304,7 @@ class Jetpack_Debug_Data { 'private' => false, ); - $full_sync_queue = Jetpack_Sync_Sender::get_instance()->get_full_sync_queue(); + $full_sync_queue = Sender::get_instance()->get_full_sync_queue(); $debug_info['full_sync_size'] = array( 'label' => 'Full Sync Queue Size', @@ -326,10 +323,10 @@ class Jetpack_Debug_Data { * Must follow sync debug since it depends on sync functionality. */ $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' ) : '', + 'home' => Functions::home_url(), + 'siteurl' => Functions::site_url(), + 'WP_HOME' => Constants::is_defined( 'WP_HOME' ) ? Constants::get_constant( 'WP_HOME' ) : '', + 'WP_SITEURL' => Constants::is_defined( 'WP_SITEURL' ) ? Constants::get_constant( 'WP_SITEURL' ) : '', ); $debug_info['idc_urls'] = array( diff --git a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php index e7038902..afbbb4d1 100644 --- a/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php +++ b/plugins/jetpack/_inc/lib/debugger/class-jetpack-debugger.php @@ -1,60 +1,38 @@ <?php + +use Automattic\Jetpack\Status; + /** * Jetpack Debugger functionality allowing for self-service diagnostic information via the legacy jetpack debugger. * * @package jetpack */ -/** Ensure the Jetpack_Debug_Data class is available. It should be via the library loaded, but defense is good. */ -require_once 'class-jetpack-debug-data.php'; - /** * Class Jetpack_Debugger * * A namespacing class for functionality related to the legacy 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() { - // Specifically not deprecating this function since it modifies the output of the Jetpack_Debug_Data::what_jetpack_plan return. - return 'JetpackPlan' . Jetpack_Debug_Data::what_jetpack_plan(); - } - - /** - * Convert seconds to human readable time. - * - * A dedication function instead of using Core functionality to allow for output in seconds. - * - * @deprecated 7.3.0 - * - * @param int $seconds Number of seconds to convert to human time. - * - * @return string Human readable time. - */ - public static function seconds_to_time( $seconds ) { - _deprecated_function( 'Jetpack_Debugger::seconds_to_time', 'Jetpack 7.3.0', 'Jeptack_Debug_Data::seconds_to_time' ); - return Jetpack_Debug_Data::seconds_to_time( $seconds ); - } - /** * 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. * + * @deprecated 8.0.0 + * * @return int 30 */ public static function jetpack_increase_timeout() { + _deprecated_function( __METHOD__, 'jetpack-8.0', 'Jetpack_Cxn_Tests::increase_timeout' ); return 30; // seconds. } /** * Disconnect Jetpack and redirect user to connection flow. + * + * Used in class.jetpack-admin.php. */ public static function disconnect_and_redirect() { if ( ! ( isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'jp_disconnect' ) ) ) { @@ -74,23 +52,13 @@ class Jetpack_Debugger { * Handles output to the browser for the in-plugin debugger. */ public static function jetpack_debug_display_handler() { - global $wp_version; if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'jetpack' ) ); } - $data = Jetpack_Debug_Data::debug_data(); - $debug_info = ''; - foreach ( $data as $datum ) { - $debug_info .= $datum['label'] . ': ' . $datum['value'] . "\r\n"; - } - - $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( 'SITE_URL: ' . site_url() ); - $debug_info .= "\r\n" . esc_html( 'HOME_URL: ' . home_url() ); - - $debug_info .= "\r\n\r\nTEST RESULTS:\r\n\r\n"; + $support_url = Jetpack::is_development_version() + ? 'https://jetpack.com/contact-support/beta-group/' + : 'https://jetpack.com/contact-support/'; $cxntests = new Jetpack_Cxn_Tests(); ?> @@ -101,20 +69,23 @@ class Jetpack_Debugger { <?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 '<p class="jetpack-test-details">' . wp_kses( + $fail['resolution'], + array( + 'a' => array( + 'href' => array(), + 'target' => array(), + 'rel' => array(), + ), + ) + ) . '</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 } } ?> @@ -140,9 +111,9 @@ class Jetpack_Debugger { ), ) ), - '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://jetpack.com/support/getting-started-with-jetpack/known-issues/', + 'https://jetpack.com/support/getting-started-with-jetpack/known-issues/', + 'https://jetpack.com/support/', 'https://wordpress.org/support/plugin/jetpack' ); ?> @@ -214,32 +185,17 @@ class Jetpack_Debugger { <p><b><em><?php esc_html_e( 'Ask us for help!', 'jetpack' ); ?></em></b> <?php /** - * Offload to new WordPress debug data in WP 5.2+ - * - * @todo remove fallback when 5.2 is the minimum supported. + * Offload to new WordPress debug data. */ - if ( version_compare( $wp_version, '5.2-alpha', '>=' ) ) { echo sprintf( wp_kses( /* translators: URL for Jetpack support. URL for WordPress's Site Health */ __( '<a href="%1$s">Contact our Happiness team</a>. When you do, please include the <a href="%2$s">full debug information from your site</a>.', 'jetpack' ), array( 'a' => array( 'href' => array() ) ) ), - 'https://jetpack.com/contact-support/', + esc_url( $support_url ), esc_url( admin_url() . 'site-health.php?tab=debug' ) ); - $hide_debug = true; - } else { // Versions before 5.2, fallback. - 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/' - ); - $hide_debug = false; - } ?> </p> <hr /> @@ -278,7 +234,7 @@ class Jetpack_Debugger { <?php if ( current_user_can( 'jetpack_manage_modules' ) - && ( Jetpack::is_development_mode() || Jetpack::is_active() ) + && ( ( new Status() )->is_development_mode() || Jetpack::is_active() ) ) { printf( wp_kses( @@ -294,18 +250,6 @@ class Jetpack_Debugger { } ?> </div> - <hr /> - <?php - if ( ! $hide_debug ) { - ?> - <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> - <?php - } - ?> </div> <?php } diff --git a/plugins/jetpack/_inc/lib/debugger/debug-functions-for-php53.php b/plugins/jetpack/_inc/lib/debugger/debug-functions.php index a32d9fee..a32d9fee 100644 --- a/plugins/jetpack/_inc/lib/debugger/debug-functions-for-php53.php +++ b/plugins/jetpack/_inc/lib/debugger/debug-functions.php diff --git a/plugins/jetpack/_inc/lib/icalendar-reader.php b/plugins/jetpack/_inc/lib/icalendar-reader.php index f7e047f9..998f4c13 100644 --- a/plugins/jetpack/_inc/lib/icalendar-reader.php +++ b/plugins/jetpack/_inc/lib/icalendar-reader.php @@ -90,17 +90,7 @@ class iCalendarReader { } // 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' ); - } + $timezone = wp_timezone(); $offsetted_events = array(); @@ -115,11 +105,6 @@ class iCalendarReader { $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' ); } @@ -895,7 +880,7 @@ class iCalendarReader { * @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 + // Find your calendar's address https://support.google.com/calendar/bin/answer.py?hl=en&answer=37103 $ical = new iCalendarReader(); return $ical->get_events( $url, $count ); } diff --git a/plugins/jetpack/_inc/lib/markdown/extra.php b/plugins/jetpack/_inc/lib/markdown/extra.php index fd85a3c8..1f8f854d 100644 --- a/plugins/jetpack/_inc/lib/markdown/extra.php +++ b/plugins/jetpack/_inc/lib/markdown/extra.php @@ -806,7 +806,7 @@ class Markdown_Parser { if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) return $matches[0]; - $level = $matches[2]{0} == '=' ? 1 : 2; + $level = $matches[2][0] == '=' ? 1 : 2; $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>"; return "\n" . $this->hashBlock($block) . "\n\n"; } @@ -1102,7 +1102,7 @@ class Markdown_Parser { } 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); + $token_stack[0] = str_repeat($token[0], 3-$token_len); $tag = $token_len == 2 ? "strong" : "em"; $span = $text_stack[0]; $span = $this->runSpanGamut($span); @@ -1127,7 +1127,7 @@ class Markdown_Parser { } else { # Reached opening three-char emphasis marker. Push on token # stack; will be handled by the special condition above. - $em = $token{0}; + $em = $token[0]; $strong = "$em$em"; array_unshift($token_stack, $token); array_unshift($text_stack, ''); @@ -1467,9 +1467,9 @@ class Markdown_Parser { # Handle $token provided by parseSpan by determining its nature and # returning the corresponding value that should replace it. # - switch ($token{0}) { + switch ($token[0]) { case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); + return $this->hashPart("&#". ord($token[1]). ";"); case "`": # Search for end marker in remaining text. if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', @@ -1690,9 +1690,9 @@ class MarkdownExtra_Parser extends Markdown_Parser { $classes = array(); $id = false; foreach ($elements as $element) { - if ($element{0} == '.') { + if ($element[0] == '.') { $classes[] = substr($element, 1); - } else if ($element{0} == '#') { + } else if ($element[0] == '#') { if ($id === false) $id = substr($element, 1); } } @@ -1955,7 +1955,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # # Check for: Indented code block. # - else if ($tag{0} == "\n" || $tag{0} == " ") { + else if ($tag[0] == "\n" || $tag[0] == " ") { # Indented code block: pass it unchanged, will be handled # later. $parsed .= $tag; @@ -1964,7 +1964,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Check for: Code span marker # Note: need to check this after backtick fenced code blocks # - else if ($tag{0} == "`") { + else if ($tag[0] == "`") { # Find corresponding end marker. $tag_re = preg_quote($tag); if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}', @@ -2002,7 +2002,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # HTML Comments, processing instructions. # else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + $tag[1] == '!' || $tag[1] == '?') { # Need to parse tag and following text using the HTML parser. # (don't check for markdown attribute) @@ -2021,8 +2021,8 @@ class MarkdownExtra_Parser extends Markdown_Parser { # # Increase/decrease nested tag count. # - if ($tag{1} == '/') $depth--; - else if ($tag{strlen($tag)-2} != '/') $depth++; + if ($tag[1] == '/') $depth--; + else if ($tag[strlen($tag)-2] != '/') $depth++; if ($depth < 0) { # @@ -2126,7 +2126,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # first character as filtered to prevent an infinite loop in the # parent function. # - return array($original_text{0}, substr($original_text, 1)); + return array($original_text[0], substr($original_text, 1)); } $block_text .= $parts[0]; # Text before current tag. @@ -2138,7 +2138,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Comments and Processing Instructions. # if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + $tag[1] == '!' || $tag[1] == '?') { # Just add the tag to the block as if it was text. $block_text .= $tag; @@ -2149,8 +2149,8 @@ class MarkdownExtra_Parser extends Markdown_Parser { # 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++; + if ($tag[1] == '/') $depth--; + else if ($tag[strlen($tag)-2] != '/') $depth++; } # @@ -2508,7 +2508,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { function _doHeaders_callback_setext($matches) { if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) return $matches[0]; - $level = $matches[3]{0} == '=' ? 1 : 2; + $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"; @@ -2826,7 +2826,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); if ($classname != "") { - if ($classname{0} == '.') + if ($classname[0] == '.') $classname = substr($classname, 1); $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; } else { diff --git a/plugins/jetpack/_inc/lib/markdown/gfm.php b/plugins/jetpack/_inc/lib/markdown/gfm.php index 081e1a11..c382b427 100644 --- a/plugins/jetpack/_inc/lib/markdown/gfm.php +++ b/plugins/jetpack/_inc/lib/markdown/gfm.php @@ -389,7 +389,7 @@ class WPCom_GHF_Markdown_Parser extends MarkdownExtra_Parser { $classname =& $matches[2]; $codeblock = preg_replace_callback('/^\n+/', array( $this, '_doFencedCodeBlocks_newlines' ), $matches[4] ); - if ( $classname{0} == '.' ) + if ( $classname[0] == '.' ) $classname = substr( $classname, 1 ); $codeblock = esc_html( $codeblock ); diff --git a/plugins/jetpack/_inc/lib/plans.php b/plugins/jetpack/_inc/lib/plans.php new file mode 100644 index 00000000..1fa3503e --- /dev/null +++ b/plugins/jetpack/_inc/lib/plans.php @@ -0,0 +1,75 @@ +<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName +/** + * Plans Library + * + * Fetch plans data from WordPress.com. + * + * Not to be confused with the `Jetpack_Plan` (singular) + * class, which stores and syncs data about the site's _current_ plan. + * + * @package Jetpack + */ +class Jetpack_Plans { + /** + * Get a list of all available plans from WordPress.com + * + * @since 7.7.0 + * + * @return array The plans list + */ + public static function get_plans() { + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + if ( ! class_exists( 'Store_Product_List' ) ) { + require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php'; + } + + return Store_Product_List::get_active_plans_v1_5(); + } + + // We're on Jetpack, so it's safe to use this namespace. + $request = Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user( + '/plans?_locale=' . get_user_locale(), + // We're using version 1.5 of the endpoint rather than the default version 2 + // since the latter only returns Jetpack Plans, but we're also interested in + // WordPress.com plans, for consumers of this method that run on WP.com. + '1.5', + array( + 'method' => 'GET', + 'headers' => array( + 'X-Forwarded-For' => Jetpack::current_user_ip( true ), + ), + ), + null, + 'rest' + ); + + $body = wp_remote_retrieve_body( $request ); + if ( 200 === wp_remote_retrieve_response_code( $request ) ) { + return json_decode( $body ); + } else { + return $body; + } + } + + /** + * Get plan information for a plan given its slug + * + * @since 7.7.0 + * + * @param string $plan_slug Plan slug. + * + * @return object The plan object + */ + public static function get_plan( $plan_slug ) { + $plans = self::get_plans(); + if ( ! is_array( $plans ) ) { + return; + } + + foreach ( $plans as $plan ) { + if ( $plan_slug === $plan->product_slug ) { + return $plan; + } + } + } +} diff --git a/plugins/jetpack/_inc/lib/tonesque.php b/plugins/jetpack/_inc/lib/tonesque.php index 17158e3d..0e148e5c 100644 --- a/plugins/jetpack/_inc/lib/tonesque.php +++ b/plugins/jetpack/_inc/lib/tonesque.php @@ -1,13 +1,13 @@ <?php /* Plugin Name: Tonesque -Plugin URI: http://automattic.com/ +Plugin URI: https://automattic.com/ Description: Grab an average color representation from an image. Version: 1.0 Author: Automattic, Matias Ventura -Author URI: http://automattic.com/ +Author URI: https://automattic.com/ License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html +License URI: https://www.gnu.org/licenses/gpl-2.0.html */ class Tonesque { @@ -203,13 +203,13 @@ class Tonesque { switch ( $type ) { case 'rgb' : - $color = implode( $c->toRgbInt(), ',' ); + $color = implode( ',', $c->toRgbInt() ); break; case 'hex' : $color = $c->toHex(); break; case 'hsv' : - $color = implode( $c->toHsvInt(), ',' ); + $color = implode( ',', $c->toHsvInt() ); break; default: return $color = $c->toHex(); @@ -231,7 +231,7 @@ class Tonesque { return false; $c = $this->color->getMaxContrastColor(); - return implode( $c->toRgbInt(), ',' ); + 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 bd92d272..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 ); - update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] ); - - 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 index 911275bd..98a9aaac 100644 --- a/plugins/jetpack/_inc/lib/tracks/tracks-ajax.js +++ b/plugins/jetpack/_inc/lib/tracks/tracks-ajax.js @@ -1,7 +1,7 @@ /* global jpTracksAJAX, jQuery */ ( function( $, jpTracksAJAX ) { window.jpTracksAJAX = window.jpTracksAJAX || {}; - const debugSet = localStorage.getItem( 'debug' ) === 'dops:analytics'; + var debugSet = localStorage.getItem( 'debug' ) === 'dops:analytics'; window.jpTracksAJAX.record_ajax_event = function( eventName, eventType, eventProp ) { var data = { diff --git a/plugins/jetpack/_inc/lib/tracks/tracks-callables.js b/plugins/jetpack/_inc/lib/tracks/tracks-callables.js index d4e53af6..4e033d2c 100644 --- a/plugins/jetpack/_inc/lib/tracks/tracks-callables.js +++ b/plugins/jetpack/_inc/lib/tracks/tracks-callables.js @@ -8,6 +8,9 @@ // Load tracking scripts window._tkq = window._tkq || []; +var _user; +var debug = console.error; // eslint-disable-line no-console + function buildQuerystring( group, name ) { var uriComponent = ''; diff --git a/plugins/jetpack/_inc/lib/widgets.php b/plugins/jetpack/_inc/lib/widgets.php index 3f072b75..8bdd7b76 100644 --- a/plugins/jetpack/_inc/lib/widgets.php +++ b/plugins/jetpack/_inc/lib/widgets.php @@ -557,11 +557,15 @@ class Jetpack_Widgets { // 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 ), - ) ); + $tracking = new Automattic\Jetpack\Tracking(); + $tracking->tracks_record_event( + wp_get_current_user(), + 'wpcom_widgets_activate_widget', + array( + 'widget' => $id_base, + 'settings' => wp_json_encode( $settings ), + ) + ); } return self::get_widget_by_id( $widget_id ); |