PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | modules/stats.php +91 -455 13.5.216.3-a.1 View file →
@@ -1,8 +1,8 @@
1 1 <?php
2 2 /**
3 3 * Module Name: Jetpack Stats
4 - * Module Description: Collect valuable traffic stats and insights.
4 + * Module Description: Clear, concise traffic insights right in your WordPress dashboard.
5 5 * Sort Order: 1
6 6 * Recommendation Order: 2
7 7 * First Introduced: 1.1
8 8 * Requires Connection: Yes
@@ -15,10 +15,8 @@
15 15 */
16 16
17 17 use Automattic\Jetpack\Admin_UI\Admin_Menu;
18 18 use Automattic\Jetpack\Connection\Client;
19 -use Automattic\Jetpack\Connection\Manager as Connection_Manager;
20 -use Automattic\Jetpack\Connection\XMLRPC_Async_Call;
21 19 use Automattic\Jetpack\Redirect;
22 20 use Automattic\Jetpack\Stats\Main as Stats;
23 21 use Automattic\Jetpack\Stats\Options as Stats_Options;
24 22 use Automattic\Jetpack\Stats\Tracking_Pixel as Stats_Tracking_Pixel;
@@ -23,14 +21,18 @@
23 21 use Automattic\Jetpack\Stats\Options as Stats_Options;
24 22 use Automattic\Jetpack\Stats\Tracking_Pixel as Stats_Tracking_Pixel;
25 23 use Automattic\Jetpack\Stats\WPCOM_Stats;
26 24 use Automattic\Jetpack\Stats\XMLRPC_Provider as Stats_XMLRPC;
25 +use Automattic\Jetpack\Stats_Admin\Admin_Post_List_Column;
27 26 use Automattic\Jetpack\Stats_Admin\Dashboard as Stats_Dashboard;
28 27 use Automattic\Jetpack\Stats_Admin\Main as Stats_Main;
29 -use Automattic\Jetpack\Stats_Admin\Notices as Stats_Notices;
30 28 use Automattic\Jetpack\Status\Host;
31 29 use Automattic\Jetpack\Tracking;
32 30
31 +if ( ! defined( 'ABSPATH' ) ) {
32 + exit( 0 );
33 +}
34 +
33 35 if ( defined( 'STATS_DASHBOARD_SERVER' ) ) {
34 36 return;
35 37 }
36 38
@@ -53,20 +55,21 @@
53 55 */
54 56 function stats_load() {
55 57 Jetpack::enable_module_configurable( __FILE__ );
56 58
57 - add_action( 'wp_head', 'stats_admin_bar_head', 100 );
59 + // Only run the callback for those who can see the stats.
60 + if ( is_user_logged_in() && current_user_can( 'view_stats' ) ) {
61 + add_action( 'admin_head', 'stats_admin_bar_head', 100 );
62 + add_action( 'wp_head', 'stats_admin_bar_head', 100 );
63 + }
58 64
65 + Admin_Post_List_Column::register();
66 +
59 67 add_action( 'jetpack_admin_menu', 'stats_admin_menu' );
68 + add_action( 'wp_before_admin_bar_render', 'stats_add_link_to_admin_bar_site_menu' );
60 69
61 70 add_filter( 'pre_option_db_version', 'stats_ignore_db_version' );
62 71
63 - // Add an icon to see stats in WordPress.com for a particular post.
64 - add_action( 'admin_print_styles-edit.php', 'jetpack_stats_load_admin_css' );
65 - add_filter( 'manage_posts_columns', 'jetpack_stats_post_table' );
66 - add_filter( 'manage_pages_columns', 'jetpack_stats_post_table' );
67 - add_action( 'manage_posts_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
68 - add_action( 'manage_pages_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
69 72 // Filter for adding the Jetpack plugin version to tracking stats.
70 73 add_filter( 'stats_array', 'filter_stats_array_add_jp_version' );
71 74
72 75 require_once __DIR__ . '/stats/class-jetpack-stats-upgrade-nudges.php';
@@ -229,9 +232,9 @@
229 232 $redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : null );
230 233 $relative_pos = strpos( $redirect_url, '/wp-admin/' );
231 234 if ( false !== $relative_pos ) {
232 235 wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
233 - exit;
236 + exit( 0 );
234 237 }
235 238 }
236 239
237 240 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
@@ -245,15 +248,24 @@
245 248 if ( ! Stats_Options::get_option( 'enable_odyssey_stats' ) || isset( $_GET['noheader'] ) ) {
246 249 // Show old Jetpack Stats interface for:
247 250 // - When the "enable_odyssey_stats" option is disabled.
248 251 // - When being shown in the adminbar outside of wp-admin.
249 - $hook = Admin_Menu::add_menu( __( 'Stats', 'jetpack' ), __( 'Stats', 'jetpack' ), 'view_stats', 'stats', 'jetpack_admin_ui_stats_report_page_wrapper' );
252 + $hook = Admin_Menu::add_menu(
253 + __( 'Stats', 'jetpack' ),
254 + __( 'Stats', 'jetpack' ),
255 + 'view_stats',
256 + 'stats',
257 + 'jetpack_admin_ui_stats_report_page_wrapper',
258 + null,
259 + array(
260 + 'product' => 'stats',
261 + 'key' => 'jetpack-stats',
262 + )
263 + );
250 264 add_action( "load-$hook", 'stats_reports_load' );
251 265 } else {
252 266 // Enable the new Odyssey Stats experience.
253 - $stats_dashboard = new Stats_Dashboard();
254 - $hook = Admin_Menu::add_menu( __( 'Stats', 'jetpack' ), __( 'Stats', 'jetpack' ), 'view_stats', 'stats', array( $stats_dashboard, 'render' ) );
255 - add_action( "load-$hook", array( $stats_dashboard, 'admin_init' ) );
267 + Stats_Dashboard::init();
256 268 }
257 269 }
258 270
259 271 /**
@@ -293,9 +305,8 @@
293 305 // Detect if JS is on. If so, remove cookie so next page load is via JS.
294 306 add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
295 307 } elseif ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
296 308 // Normal page load. Load page content via JS.
297 - add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
298 309 add_action( 'admin_print_footer_scripts', 'stats_script_dismiss_nudge_handler' );
299 310 }
300 311 }
301 312
@@ -314,10 +325,10 @@
314 325 element.classList.toggle( "is-hidden" );
315 326 // Send an AJAX request.
316 327 // Note we can provide a 'postponed_for' parameter to set the delay.
317 328 // Without a parameter it defaults to 30 days which is what we want here.
318 - let nonce = <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ) ); ?>;
319 - let url = <?php echo wp_json_encode( rest_url( '/jetpack/v4/stats-app/stats/notices' ) ); ?>;
329 + let nonce = <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
330 + let url = <?php echo wp_json_encode( rest_url( '/jetpack/v4/stats-app/stats/notices' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
320 331 let data = {
321 332 id: 'opt_in_new_stats',
322 333 status: 'postponed',
323 334 };
@@ -345,16 +356,8 @@
345 356 max-width: 1040px;
346 357 margin: 0 auto;
347 358 overflow: hidden;
348 359 }
349 -
350 -#stats-loading-wrap p {
351 - text-align: center;
352 - font-size: 2em;
353 - margin-bottom: 3em;
354 - height: 64px;
355 - line-height: 64px;
356 -}
357 360 </style>
358 361 <?php
359 362 }
360 363
@@ -368,9 +371,9 @@
368 371 $parsed = wp_parse_url( admin_url() );
369 372 ?>
370 373 <script type="text/javascript">
371 374 /* <![CDATA[ */
372 -document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
375 +document.cookie = <?php echo wp_json_encode( 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=' . $parsed['path'], JSON_UNESCAPED_SLASHES | JSON_HEX_TAG ); ?>;
373 376 /* ]]> */
374 377 </script>
375 378 <?php
376 379 }
@@ -375,33 +378,8 @@
375 378 <?php
376 379 }
377 380
378 381 /**
379 - * Normal page load. Load page content via JS.
380 - *
381 - * @access public
382 - * @return void
383 - */
384 -function stats_js_load_page_via_ajax() {
385 - ?>
386 -<script type="text/javascript">
387 -/* <![CDATA[ */
388 -if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
389 - jQuery( function( $ ) {
390 - const loadStatsUrl = new URL( document.location.href );
391 - loadStatsUrl.searchParams.append( 'noheader', 1 );
392 - $.get( loadStatsUrl.toString(), function( responseText ) {
393 - $( '#stats-loading-wrap' ).replaceWith( responseText );
394 - $( '#jp-stats-wrap' )[0].dispatchEvent( new Event( 'stats-loaded' ) );
395 - } );
396 - } );
397 -}
398 -/* ]]> */
399 -</script>
400 - <?php
401 -}
402 -
403 -/**
404 382 * Jetpack Admin Page Wrapper.
405 383 */
406 384 function jetpack_admin_ui_stats_report_page_wrapper() {
407 385 if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
@@ -419,9 +397,9 @@
419 397 */
420 398 function stats_reports_page( $main_chart_only = false ) {
421 399 if ( isset( $_GET['dashboard'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
422 400 stats_dashboard_widget_content();
423 - exit; // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though stats_dashboard_widget_content() never returns.
401 + exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though stats_dashboard_widget_content() never returns.
424 402 }
425 403
426 404 $blog_id = Stats_Options::get_option( 'blog_id' );
427 405 $learn_url = Redirect::get_url( 'jetpack-stats-learn-more' );
@@ -428,12 +406,9 @@
428 406 $redirect_url = admin_url( 'admin.php?page=stats&enable_new_stats=1' );
429 407 $stats_bg_url = plugins_url( 'images/odyssey-upgrade/background.png', JETPACK__PLUGIN_FILE );
430 408 $stats_bg_gradient_url = plugins_url( 'images/odyssey-upgrade/gradient.png', JETPACK__PLUGIN_FILE );
431 409
432 - if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
433 - $nojs_url = add_query_arg( 'nojs', '1' );
434 - $http = is_ssl() ? 'https' : 'http';
435 - // Loading message. No JS fallback message.
410 + if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
436 411 ?>
437 412
438 413 <style>
439 414 .stats-odyssey-notice {
@@ -532,9 +507,9 @@
532 507 $i18n_headers = jetpack_get_module_i18n( 'stats' );
533 508 ?>
534 509 <a
535 510 style="font-size:13px;"
536 - href="<?php echo esc_url( admin_url( 'admin.php?page=jetpack#/settings?term=' . rawurlencode( $i18n_headers['name'] ) ) ); ?>"
511 + href="<?php echo esc_url( admin_url( 'admin.php?page=jetpack#/settings?term=' . rawurlencode( $i18n_headers['name'] ?? '' ) ) ); ?>"
537 512 >
538 513 <?php esc_html_e( 'Configure', 'jetpack' ); ?>
539 514 </a>
540 515 <?php
@@ -539,19 +514,8 @@
539 514 </a>
540 515 <?php
541 516 endif;
542 517
543 - /**
544 - * Sets external resource URL.
545 - *
546 - * @module stats
547 - *
548 - * @since 1.4.0
549 - * @todo Clean up various uses of this filter. It's seemingly filtering different types of images in different places.
550 - *
551 - * @param string $args URL of external resource.
552 - */
553 - $static_url = apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" );
554 518 ?>
555 519 </h2>
556 520 </div>
557 521 <div class="wrap">
@@ -557,9 +521,9 @@
557 521 <div class="wrap">
558 522 <div class="stats-odyssey-notice stats-odyssey-notice--content__highlighted">
559 523 <div class="stats-odyssey-notice--content">
560 524 <h2 class="stats-odyssey-notice--content-header"><?php esc_html_e( 'Deprecated Jetpack Stats Experience', 'jetpack' ); ?></h2>
561 - <p class="stats-odyssey-notice--content-text"><?php esc_html_e( 'The old Jetpack Stats has been deprecated and will be removed soon. Please click the button to enable the new experience.', 'jetpack' ); ?></p>
525 + <p class="stats-odyssey-notice--content-text"><?php esc_html_e( 'The old Jetpack Stats has been deprecated. Please click the button to enable the new experience.', 'jetpack' ); ?></p>
562 526 <div class="stats-odyssey-notice--action-bar">
563 527 <button class="dops-button stats-odyssey-notice--primary-button">
564 528 <a class="is-primary-link" href="<?php echo esc_url( $redirect_url ); ?>"><?php esc_html_e( 'Switch to new Stats', 'jetpack' ); ?></a>
565 529 </button>
@@ -568,13 +532,9 @@
568 532 </div>
569 533 <div class="stats-odyssey-notice--image-container"></div>
570 534 </div>
571 535 </div>
572 - <div id="stats-loading-wrap" class="wrap">
573 - <p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php echo esc_url( $static_url ); ?>" /></p>
574 - <p class="hide-if-js"><?php esc_html_e( 'Jetpack Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
575 - <a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Jetpack Stats without JavaScript', 'jetpack' ); ?></a>.</p>
576 - </div>
536 + <p></p>
577 537 </div>
578 538 <?php
579 539 return;
580 540 }
@@ -637,15 +597,14 @@
637 597 }
638 598 }
639 599 }
640 600
601 + $url = 'https://' . STATS_DASHBOARD_SERVER . '/wp-admin/index.php';
641 602 if ( isset( $_GET['chart'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
642 603 if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
643 604 $chart = sanitize_title( $_GET['chart'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
644 605 $url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
645 606 }
646 - } else {
647 - $url = 'https://' . STATS_DASHBOARD_SERVER . '/wp-admin/index.php';
648 607 }
649 608
650 609 $url = add_query_arg( $q, $url );
651 610 $method = 'GET';
@@ -653,34 +612,19 @@
653 612 $user_id = 0; // Means use the blog token.
654 613
655 614 $get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
656 615 $get_code = wp_remote_retrieve_response_code( $get );
657 - if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
616 + if ( is_wp_error( $get ) || $get_code === '' || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
658 617 stats_print_wp_remote_error( $get, $url );
659 - } else {
660 - if ( ! empty( $get['headers']['content-type'] ) ) {
661 - $type = $get['headers']['content-type'];
662 - if ( str_starts_with( $type, 'image' ) ) {
663 - $img = $get['body'];
664 - header( 'Content-Type: ' . $type );
665 - header( 'Content-Length: ' . strlen( $img ) );
666 - echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
667 - die();
668 - }
618 + } elseif ( ! empty( $get['headers']['content-type'] ) ) {
619 + $type = $get['headers']['content-type'];
620 + if ( str_starts_with( $type, 'image' ) ) {
621 + $img = $get['body'];
622 + header( 'Content-Type: ' . $type );
623 + header( 'Content-Length: ' . strlen( $img ) );
624 + echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
625 + die( 0 );
669 626 }
670 - $body = stats_convert_post_titles( $get['body'] );
671 - $body = stats_convert_chart_urls( $body );
672 - $body = stats_convert_image_urls( $body );
673 - $body = stats_convert_admin_urls( $body );
674 -
675 - // The response can contain either the content to display OR
676 - // the scripts for the chart UI. The following calls inspect the
677 - // response, insert the Odyssey nudge as needed, and make sure
678 - // everything is output correctly.
679 - stats_print_header_section( $body );
680 - stats_print_odyssey_nudge( $body );
681 - stats_print_content_section( $body );
682 - stats_print_chart_scripts( $body );
683 627 }
684 628
685 629 if ( isset( $_GET['page'] ) && 'stats' === $_GET['page'] && ! isset( $_GET['chart'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
686 630 $tracking = new Tracking();
@@ -687,246 +631,13 @@
687 631 $tracking->record_user_event( 'wpa_page_view', array( 'path' => 'old_stats' ) );
688 632 }
689 633
690 634 if ( isset( $_GET['noheader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
691 - die;
635 + die( 0 );
692 636 }
693 637 }
694 638
695 639 /**
696 - * Legacy Stats: Print Header Section
697 - *
698 - * @access public
699 - * @param mixed $html HTML.
700 - * @return void
701 - */
702 -function stats_print_header_section( $html ) {
703 - $header = stats_parse_header_section( $html );
704 - if ( $header !== '' ) {
705 - echo $header; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
706 - }
707 -}
708 -
709 -/**
710 - * Legacy Stats: Print Content Section
711 - *
712 - * @access public
713 - * @param mixed $html HTML.
714 - * @return void
715 - */
716 -function stats_print_content_section( $html ) {
717 - $content = stats_parse_content_section( $html );
718 - if ( $content !== '' ) {
719 - echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
720 - }
721 -}
722 -
723 -/**
724 - * Legacy Stats: Print Chart Scripts
725 - *
726 - * @access public
727 - * @param mixed $html HTML.
728 - * @return void
729 - */
730 -function stats_print_chart_scripts( $html ) {
731 - if ( is_chart_scripts( $html ) ) {
732 - echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
733 - }
734 -}
735 -
736 -/**
737 - * Legacy Stats: Test for presence of chart scripts
738 - *
739 - * @access public
740 - * @param mixed $html Input HTML that may or may not include the chart scripts.
741 - * @return bool
742 - */
743 -function is_chart_scripts( $html ) {
744 - $pos = strpos( $html, STATS_CONTENT_MARKER );
745 - return $pos === false;
746 -}
747 -
748 -/**
749 - * Legacy Stats: Parse Header Section
750 - *
751 - * Returns the section of the content up to and including the date header.
752 - *
753 - * @access public
754 - * @param mixed $html HTML.
755 - * @return string
756 - */
757 -function stats_parse_header_section( $html ) {
758 - $head = strstr( $html, STATS_CONTENT_MARKER, true );
759 - // Enforce a string result instead of string|false.
760 - if ( $head === false ) {
761 - return '';
762 - }
763 - return $head;
764 -}
765 -
766 -/**
767 - * Legacy Stats: Print Content Section
768 - *
769 - * Returns the section of the content excluding the date header.
770 - *
771 - * @access public
772 - * @param mixed $html HTML.
773 - * @return string
774 - */
775 -function stats_parse_content_section( $html ) {
776 - $body = strstr( $html, STATS_BODY_MARKER );
777 - // Enforce a string result instead of string|false.
778 - if ( $body === false ) {
779 - return '';
780 - }
781 - return $body;
782 -}
783 -
784 -/**
785 - * Legacy Stats: Determine if we need to show the Odyssey upgrade nudge.
786 - *
787 - * @access public
788 - * @return boolean
789 - */
790 -function stats_should_show_odyssey_nudge() {
791 - $stats_notices = ( new Stats_Notices() )->get_notices_to_show();
792 - return isset( $stats_notices[ Stats_Notices::OPT_IN_NEW_STATS_NOTICE_ID ] )
793 - && $stats_notices[ Stats_Notices::OPT_IN_NEW_STATS_NOTICE_ID ];
794 -}
795 -
796 -/**
797 - * Legacy Stats: Print the Odyssey upgrade nudge.
798 - *
799 - * @access public
800 - * @param mixed $html HTML.
801 - * @return void
802 - */
803 -function stats_print_odyssey_nudge( $html ) {
804 - if ( ! stats_should_show_odyssey_nudge() ) {
805 - return;
806 - }
807 - $pos = strpos( $html, STATS_CONTENT_MARKER );
808 - if ( $pos === false ) {
809 - return;
810 - }
811 - $learn_url = Redirect::get_url( 'jetpack-stats-learn-more' );
812 - $redirect_url = admin_url( 'admin.php?page=stats&enable_new_stats=1' );
813 - ?>
814 - <style>
815 - .stats-odyssey-notice {
816 - display: flex;
817 - font-size: var( --font-body );
818 -
819 - border: 1px solid var( --jp-gray-5 );
820 - border-left-color: var( --jp-black );
821 - border-left-width: 6px;
822 - border-radius: 4px;
823 -
824 - margin-top: 24px;
825 - background: white;
826 - position: relative;
827 - }
828 - .stats-odyssey-notice--content {
829 - padding: 24px 0 24px 30px;
830 - font-size: 2em;
831 - width: 100%;
832 - }
833 - .stats-odyssey-notice--content-header {
834 - font-size: 24px;
835 - line-height: 32px;
836 - margin: 0;
837 - margin-bottom: 8px;
838 - }
839 - .stats-odyssey-notice--content-text {
840 - font-size: 16px;
841 - margin: 0;
842 - }
843 - .stats-odyssey-notice--image-container {
844 - background-image: url("/wp-content/plugins/jetpack/images/odyssey-upgrade/background.png"), url("/wp-content/plugins/jetpack/images/odyssey-upgrade/gradient.png");
845 - background-size: cover;
846 - padding-right: 28px;
847 - width: 100%;
848 - }
849 - .stats-odyssey-notice--close-button {
850 - position: absolute;
851 - top: 1rem;
852 - right: 1rem;
853 - background-color: transparent;
854 - border: none;
855 - cursor: pointer;
856 - }
857 - .stats-odyssey-notice--action-bar {
858 - display: flex;
859 - align-items: center;
860 - margin-top: 24px;
861 - }
862 - .stats-odyssey-notice--primary-button {
863 - margin-right: 18px;
864 - padding-left: 20px;
865 - padding-right: 20px;
866 - font-size: 16px;
867 - border-color: black;
868 - background-color: black;
869 - }
870 - .stats-odyssey-notice--primary-button:hover {
871 - border-color: #3c434a;
872 - background-color: #3c434a;
873 - }
874 - .is-primary-link {
875 - color: white;
876 - text-decoration: none;
877 - }
878 - .is-primary-link:active {
879 - color: white;
880 - }
881 - .is-primary-link:focus {
882 - color: white;
883 - box-shadow: none;
884 - outline: none;
885 - }
886 - .is-primary-link:hover {
887 - color: white;
888 - }
889 - .is-secondary-link {
890 - color: black;
891 - font-size: var( --font-body );
892 - }
893 - .is-secondary-link:hover {
894 - color: black;
895 - }
896 - .is-hidden {
897 - display: none;
898 - }
899 - </style>
900 - <div id="stats-odyssey-nudge-main" class="stats-odyssey-notice">
901 - <div class="stats-odyssey-notice--content">
902 - <h2 class="stats-odyssey-notice--content-header"><?php esc_html_e( 'Explore the new Jetpack Stats', 'jetpack' ); ?></h2>
903 - <p class="stats-odyssey-notice--content-text"><?php esc_html_e( "We've added new stats and insights in a more modern and mobile friendly experience to help you grow your site.", 'jetpack' ); ?></p>
904 - <div class="stats-odyssey-notice--action-bar">
905 - <button class="dops-button stats-odyssey-notice--primary-button">
906 - <a class="is-primary-link" href="<?php echo esc_url( $redirect_url ); ?>"><?php esc_html_e( 'Switch to new Stats', 'jetpack' ); ?></a>
907 - </button>
908 - <a class="is-secondary-link" href="<?php echo esc_url( $learn_url ); ?>" rel="noopener noreferrer" target="_blank"><?php esc_html_e( 'Learn about Stats', 'jetpack' ); ?> <svg xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle;" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg></a>
909 - </div>
910 - </div>
911 - <div class="stats-odyssey-notice--image-container"></div>
912 - <button class="stats-odyssey-notice--close-button" onclick="stats_odyssey_dismiss_nudge()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg></button>
913 - </div>
914 - <?php
915 -}
916 -
917 -/**
918 - * Stats Convert Admin Urls.
919 - *
920 - * @access public
921 - * @param mixed $html HTML.
922 - * @return string
923 - */
924 -function stats_convert_admin_urls( $html ) {
925 - return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
926 -}
927 -
928 -/**
929 640 * Stats Convert Image URLs.
930 641 *
931 642 * @access public
932 643 * @param mixed $html HTML.
@@ -1031,8 +742,13 @@
1031 742 * @access public
1032 743 * @return void
1033 744 */
1034 745 function stats_admin_bar_head() {
746 + // Let's not show the stats admin bar to users who are not logged in.
747 + if ( ! is_user_logged_in() ) {
748 + return;
749 + }
750 +
1035 751 if ( ! Stats_Options::get_option( 'admin_bar' ) ) {
1036 752 return;
1037 753 }
1038 754
@@ -1113,9 +829,9 @@
1113 829
1114 830 $menu = array(
1115 831 'id' => 'stats',
1116 832 'href' => add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ), // no menu_page_url() blog-side.
1117 - 'title' => "<div><img src='$img_src' srcset='$img_src 1x, $img_src_2x 2x' width='112' height='24' alt='$alt' title='$title'></div>",
833 + 'title' => "<div><img fetchpriority='low' loading='lazy' decoding='async' src='$img_src' srcset='$img_src 1x, $img_src_2x 2x' width='112' height='24' alt='$alt' title='$title'></div>",
1118 834 );
1119 835
1120 836 $wp_admin_bar->add_menu( $menu );
1121 837 }
@@ -1120,21 +836,33 @@
1120 836 $wp_admin_bar->add_menu( $menu );
1121 837 }
1122 838
1123 839 /**
840 + * Adds a Stats link to the site-name admin bar submenu, alongside Dashboard.
1124 841 *
1125 - * Deprecated. The stats module should not update blog details. This is handled by Sync.
1126 - *
1127 - * Stats Update Blog.
1128 - *
1129 842 * @access public
1130 843 * @return void
1131 - *
1132 - * @deprecated since 10.3.
1133 844 */
1134 -function stats_update_blog() {
1135 - _deprecated_function( __METHOD__, 'jetpack-10.3' );
1136 - XMLRPC_Async_Call::add_call( 'jetpack.updateBlog', 0, stats_get_blog() );
845 +function stats_add_link_to_admin_bar_site_menu() {
846 + global $wp_admin_bar;
847 +
848 + if (
849 + ! is_object( $wp_admin_bar ) ||
850 + ! $wp_admin_bar->get_node( 'dashboard' ) ||
851 + ! current_user_can( 'view_stats' ) ||
852 + ( new Host() )->is_wpcom_platform()
853 + ) {
854 + return;
855 + }
856 +
857 + $wp_admin_bar->add_node(
858 + array(
859 + 'parent' => 'site-name',
860 + 'id' => 'jetpack-stats',
861 + 'title' => __( 'Stats', 'jetpack' ),
862 + 'href' => admin_url( 'admin.php?page=stats' ),
863 + )
864 + );
1137 865 }
1138 866
1139 867 /**
1140 868 * Stats Get Blog.
@@ -1365,9 +1093,9 @@
1365 1093 $user_id = 0; // Means use the blog token.
1366 1094
1367 1095 $get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1368 1096 $get_code = wp_remote_retrieve_response_code( $get );
1369 - if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1097 + if ( is_wp_error( $get ) || $get_code === '' || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1370 1098 stats_print_wp_remote_error( $get, $url );
1371 1099 } else {
1372 1100 $body = stats_convert_post_titles( $get['body'] );
1373 1101 $body = stats_convert_chart_urls( $body );
@@ -1470,9 +1198,9 @@
1470 1198 </div>
1471 1199 </div>
1472 1200 </div>
1473 1201 <?php
1474 - exit;
1202 + exit( 0 );
1475 1203 }
1476 1204
1477 1205 /**
1478 1206 * Stats Print WP Remote Error.
@@ -1484,9 +1212,9 @@
1484 1212 */
1485 1213 function stats_print_wp_remote_error( $get, $url ) {
1486 1214 $state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1487 1215 $previous_error = Jetpack::state( $state_name );
1488 - $error = md5( wp_json_encode( compact( 'get', 'url' ) ) );
1216 + $error = md5( wp_json_encode( compact( 'get', 'url' ), JSON_UNESCAPED_SLASHES ) );
1489 1217 Jetpack::state( $state_name, $error );
1490 1218 if ( $error !== $previous_error ) {
1491 1219 ?>
1492 1220 <div class="wrap">
@@ -1546,8 +1274,9 @@
1546 1274 * @type string $days The length of the desired time frame. Default is 30. Maximum 90 days.
1547 1275 * @type int $limit The maximum number of records to return. Default is 10. Maximum 100.
1548 1276 * @type int $post_id The ID of the post to retrieve stats data for
1549 1277 * @type string $summarize If present, summarizes all matching records. Default Null.
1278 + * @type int $blog_id The WordPress.com blog ID to retrieve stats data for. Default is the current blog.
1550 1279 *
1551 1280 * }
1552 1281 *
1553 1282 * @return array {
@@ -1569,13 +1298,13 @@
1569 1298 'days' => false,
1570 1299 'limit' => 3,
1571 1300 'post_id' => false,
1572 1301 'summarize' => '',
1302 + 'blog_id' => Jetpack_Options::get_option( 'id' ),
1573 1303 );
1574 1304
1575 - $args = wp_parse_args( $args, $defaults );
1576 - $args['table'] = $table;
1577 - $args['blog_id'] = Jetpack_Options::get_option( 'id' );
1305 + $args = wp_parse_args( $args, $defaults );
1306 + $args['table'] = $table;
1578 1307
1579 1308 $stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
1580 1309
1581 1310 $key = md5( $stats_csv_url );
@@ -1645,9 +1374,9 @@
1645 1374 $user_id = 0; // Blog token.
1646 1375
1647 1376 $get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1648 1377 $get_code = wp_remote_retrieve_response_code( $get );
1649 - if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1378 + if ( is_wp_error( $get ) || $get_code === '' || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1650 1379 return array(); // @todo: return an error?
1651 1380 } else {
1652 1381 return stats_str_getcsv( $get['body'] );
1653 1382 }
@@ -1661,10 +1390,16 @@
1661 1390 * @param mixed $csv CSV.
1662 1391 * @return array
1663 1392 */
1664 1393 function stats_str_getcsv( $csv ) {
1665 - $lines = str_getcsv( $csv, "\n" );
1666 - return array_map( 'str_getcsv', $lines );
1394 + // @todo Correctly handle embedded newlines. Note, despite claims online, `str_getcsv( $csv, "\n" )` does not actually work.
1395 + $lines = explode( "\n", rtrim( $csv, "\n" ) );
1396 + return array_map(
1397 + function ( $line ) {
1398 + return str_getcsv( $line, ',', '"', '' );
1399 + },
1400 + $lines
1401 + );
1667 1402 }
1668 1403
1669 1404 /**
1670 1405 * Abstract out building the rest api stats path.
@@ -1691,9 +1426,9 @@
1691 1426 _deprecated_function( __METHOD__, 'jetpack-11.5', 'Please checkout the methods available in Automattic\Jetpack\Stats\WPCOM_Stats' );
1692 1427 $endpoint = jetpack_stats_api_path( $resource );
1693 1428 $api_version = '1.1';
1694 1429 $args = wp_parse_args( $args, array() );
1695 - $cache_key = md5( implode( '|', array( $endpoint, $api_version, wp_json_encode( $args ) ) ) );
1430 + $cache_key = md5( implode( '|', array( $endpoint, $api_version, wp_json_encode( $args, JSON_UNESCAPED_SLASHES ) ) ) );
1696 1431
1697 1432 $transient_name = "jetpack_restapi_stats_cache_{$cache_key}";
1698 1433
1699 1434 $stats_cache = get_transient( $transient_name );
@@ -1727,107 +1462,8 @@
1727 1462 // To reduce size in storage: store with time as key, store JSON encoded data (unless error).
1728 1463 set_transient( $transient_name, array( time() => $data ), 5 * MINUTE_IN_SECONDS );
1729 1464
1730 1465 return $return;
1731 -}
1732 -
1733 -/**
1734 - * Load CSS needed for Stats column width in WP-Admin area.
1735 - *
1736 - * @since 4.7.0
1737 - */
1738 -function jetpack_stats_load_admin_css() {
1739 - ?>
1740 - <style type="text/css">
1741 - .fixed .column-stats {
1742 - width: 5em;
1743 - }
1744 - </style>
1745 - <?php
1746 -}
1747 -
1748 -/**
1749 - * Set header for column that allows to view an entry's stats.
1750 - *
1751 - * @param array $columns An array of column names.
1752 - *
1753 - * @since 4.7.0
1754 - *
1755 - * @return mixed
1756 - */
1757 -function jetpack_stats_post_table( $columns ) {
1758 - /*
1759 - * Stats can be accessed in wp-admin or in Calypso,
1760 - * depending on what version of the stats screen is enabled on your site.
1761 - *
1762 - * In both cases, the user must be allowed to access stats.
1763 - *
1764 - * If the Odyssey Stats experience isn't enabled, the user will need to go to Calypso,
1765 - * so they need to be connected to WordPress.com to be able to access that page.
1766 - */
1767 - if (
1768 - ! current_user_can( 'view_stats' )
1769 - || (
1770 - ! Stats_Options::get_option( 'enable_odyssey_stats' )
1771 - && ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected()
1772 - )
1773 - ) {
1774 - return $columns;
1775 - }
1776 -
1777 - // Array-Fu to add before comments.
1778 - $pos = array_search( 'comments', array_keys( $columns ), true );
1779 -
1780 - // Fallback to the last position if the post type does not support comments.
1781 - if ( ! is_int( $pos ) ) {
1782 - $pos = count( $columns );
1783 - }
1784 -
1785 - // Final fallback, if the array was malformed by another plugin for example.
1786 - if ( ! is_int( $pos ) ) {
1787 - return $columns;
1788 - }
1789 -
1790 - $chunks = array_chunk( $columns, $pos, true );
1791 - $chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
1792 -
1793 - return call_user_func_array( 'array_merge', $chunks );
1794 -}
1795 -
1796 -/**
1797 - * Set content for cell with link to an entry's stats in Odyssey Stats.
1798 - *
1799 - * @param string $column The name of the column to display.
1800 - * @param int $post_id The current post ID.
1801 - *
1802 - * @since 4.7.0
1803 - *
1804 - * @return mixed
1805 - */
1806 -function jetpack_stats_post_table_cell( $column, $post_id ) {
1807 - if ( 'stats' === $column ) {
1808 - if ( 'publish' !== get_post_status( $post_id ) ) {
1809 - printf(
1810 - '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
1811 - esc_html__( 'No stats', 'jetpack' )
1812 - );
1813 - } else {
1814 - $stats_post_url = ! ( new Host() )->is_woa_site() && Stats_Options::get_option( 'enable_odyssey_stats' )
1815 - ? admin_url( sprintf( 'admin.php?page=stats#!/stats/post/%d/%d', $post_id, Jetpack_Options::get_option( 'id', 0 ) ) )
1816 - : Redirect::get_url(
1817 - 'calypso-stats-post',
1818 - array(
1819 - 'path' => $post_id,
1820 - )
1821 - );
1822 -
1823 - printf(
1824 - '<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
1825 - esc_url( $stats_post_url ),
1826 - esc_html__( 'View stats for this post', 'jetpack' )
1827 - );
1828 - }
1829 - }
1830 1466 }
1831 1467
1832 1468 /**
1833 1469 * Add the Jetpack plugin version to the stats tracking data.