PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
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 14.1.1 14.2.2 All 502 releases
← All changes | modules/stats.php +63 -456 14.0.116.2 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
@@ -55,21 +57,19 @@
55 57 Jetpack::enable_module_configurable( __FILE__ );
56 58
57 59 // Only run the callback for those who can see the stats.
58 60 if ( is_user_logged_in() && current_user_can( 'view_stats' ) ) {
61 + add_action( 'admin_head', 'stats_admin_bar_head', 100 );
59 62 add_action( 'wp_head', 'stats_admin_bar_head', 100 );
60 63 }
61 64
65 + Admin_Post_List_Column::register();
66 +
62 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' );
63 69
64 70 add_filter( 'pre_option_db_version', 'stats_ignore_db_version' );
65 71
66 - // Add an icon to see stats in WordPress.com for a particular post.
67 - add_action( 'admin_print_styles-edit.php', 'jetpack_stats_load_admin_css' );
68 - add_filter( 'manage_posts_columns', 'jetpack_stats_post_table' );
69 - add_filter( 'manage_pages_columns', 'jetpack_stats_post_table' );
70 - add_action( 'manage_posts_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
71 - add_action( 'manage_pages_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
72 72 // Filter for adding the Jetpack plugin version to tracking stats.
73 73 add_filter( 'stats_array', 'filter_stats_array_add_jp_version' );
74 74
75 75 require_once __DIR__ . '/stats/class-jetpack-stats-upgrade-nudges.php';
@@ -232,9 +232,9 @@
232 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 );
233 233 $relative_pos = strpos( $redirect_url, '/wp-admin/' );
234 234 if ( false !== $relative_pos ) {
235 235 wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
236 - exit;
236 + exit( 0 );
237 237 }
238 238 }
239 239
240 240 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
@@ -252,11 +252,9 @@
252 252 $hook = Admin_Menu::add_menu( __( 'Stats', 'jetpack' ), __( 'Stats', 'jetpack' ), 'view_stats', 'stats', 'jetpack_admin_ui_stats_report_page_wrapper' );
253 253 add_action( "load-$hook", 'stats_reports_load' );
254 254 } else {
255 255 // Enable the new Odyssey Stats experience.
256 - $stats_dashboard = new Stats_Dashboard();
257 - $hook = Admin_Menu::add_menu( __( 'Stats', 'jetpack' ), __( 'Stats', 'jetpack' ), 'view_stats', 'stats', array( $stats_dashboard, 'render' ), 1 );
258 - add_action( "load-$hook", array( $stats_dashboard, 'admin_init' ) );
256 + Stats_Dashboard::init();
259 257 }
260 258 }
261 259
262 260 /**
@@ -296,9 +294,8 @@
296 294 // Detect if JS is on. If so, remove cookie so next page load is via JS.
297 295 add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
298 296 } elseif ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
299 297 // Normal page load. Load page content via JS.
300 - add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
301 298 add_action( 'admin_print_footer_scripts', 'stats_script_dismiss_nudge_handler' );
302 299 }
303 300 }
304 301
@@ -317,10 +314,10 @@
317 314 element.classList.toggle( "is-hidden" );
318 315 // Send an AJAX request.
319 316 // Note we can provide a 'postponed_for' parameter to set the delay.
320 317 // Without a parameter it defaults to 30 days which is what we want here.
321 - let nonce = <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ) ); ?>;
322 - let url = <?php echo wp_json_encode( rest_url( '/jetpack/v4/stats-app/stats/notices' ) ); ?>;
318 + let nonce = <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
319 + let url = <?php echo wp_json_encode( rest_url( '/jetpack/v4/stats-app/stats/notices' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
323 320 let data = {
324 321 id: 'opt_in_new_stats',
325 322 status: 'postponed',
326 323 };
@@ -348,16 +345,8 @@
348 345 max-width: 1040px;
349 346 margin: 0 auto;
350 347 overflow: hidden;
351 348 }
352 -
353 -#stats-loading-wrap p {
354 - text-align: center;
355 - font-size: 2em;
356 - margin-bottom: 3em;
357 - height: 64px;
358 - line-height: 64px;
359 -}
360 349 </style>
361 350 <?php
362 351 }
363 352
@@ -371,9 +360,9 @@
371 360 $parsed = wp_parse_url( admin_url() );
372 361 ?>
373 362 <script type="text/javascript">
374 363 /* <![CDATA[ */
375 -document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
364 +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 ); ?>;
376 365 /* ]]> */
377 366 </script>
378 367 <?php
379 368 }
@@ -378,33 +367,8 @@
378 367 <?php
379 368 }
380 369
381 370 /**
382 - * Normal page load. Load page content via JS.
383 - *
384 - * @access public
385 - * @return void
386 - */
387 -function stats_js_load_page_via_ajax() {
388 - ?>
389 -<script type="text/javascript">
390 -/* <![CDATA[ */
391 -if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
392 - jQuery( function( $ ) {
393 - const loadStatsUrl = new URL( document.location.href );
394 - loadStatsUrl.searchParams.append( 'noheader', 1 );
395 - $.get( loadStatsUrl.toString(), function( responseText ) {
396 - $( '#stats-loading-wrap' ).replaceWith( responseText );
397 - $( '#jp-stats-wrap' )[0].dispatchEvent( new Event( 'stats-loaded' ) );
398 - } );
399 - } );
400 -}
401 -/* ]]> */
402 -</script>
403 - <?php
404 -}
405 -
406 -/**
407 371 * Jetpack Admin Page Wrapper.
408 372 */
409 373 function jetpack_admin_ui_stats_report_page_wrapper() {
410 374 if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
@@ -422,9 +386,9 @@
422 386 */
423 387 function stats_reports_page( $main_chart_only = false ) {
424 388 if ( isset( $_GET['dashboard'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
425 389 stats_dashboard_widget_content();
426 - exit; // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though stats_dashboard_widget_content() never returns.
390 + exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though stats_dashboard_widget_content() never returns.
427 391 }
428 392
429 393 $blog_id = Stats_Options::get_option( 'blog_id' );
430 394 $learn_url = Redirect::get_url( 'jetpack-stats-learn-more' );
@@ -431,12 +395,9 @@
431 395 $redirect_url = admin_url( 'admin.php?page=stats&enable_new_stats=1' );
432 396 $stats_bg_url = plugins_url( 'images/odyssey-upgrade/background.png', JETPACK__PLUGIN_FILE );
433 397 $stats_bg_gradient_url = plugins_url( 'images/odyssey-upgrade/gradient.png', JETPACK__PLUGIN_FILE );
434 398
435 - if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
436 - $nojs_url = add_query_arg( 'nojs', '1' );
437 - $http = is_ssl() ? 'https' : 'http';
438 - // Loading message. No JS fallback message.
399 + if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
439 400 ?>
440 401
441 402 <style>
442 403 .stats-odyssey-notice {
@@ -535,9 +496,9 @@
535 496 $i18n_headers = jetpack_get_module_i18n( 'stats' );
536 497 ?>
537 498 <a
538 499 style="font-size:13px;"
539 - href="<?php echo esc_url( admin_url( 'admin.php?page=jetpack#/settings?term=' . rawurlencode( $i18n_headers['name'] ) ) ); ?>"
500 + href="<?php echo esc_url( admin_url( 'admin.php?page=jetpack#/settings?term=' . rawurlencode( $i18n_headers['name'] ?? '' ) ) ); ?>"
540 501 >
541 502 <?php esc_html_e( 'Configure', 'jetpack' ); ?>
542 503 </a>
543 504 <?php
@@ -542,19 +503,8 @@
542 503 </a>
543 504 <?php
544 505 endif;
545 506
546 - /**
547 - * Sets external resource URL.
548 - *
549 - * @module stats
550 - *
551 - * @since 1.4.0
552 - * @todo Clean up various uses of this filter. It's seemingly filtering different types of images in different places.
553 - *
554 - * @param string $args URL of external resource.
555 - */
556 - $static_url = apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" );
557 507 ?>
558 508 </h2>
559 509 </div>
560 510 <div class="wrap">
@@ -560,9 +510,9 @@
560 510 <div class="wrap">
561 511 <div class="stats-odyssey-notice stats-odyssey-notice--content__highlighted">
562 512 <div class="stats-odyssey-notice--content">
563 513 <h2 class="stats-odyssey-notice--content-header"><?php esc_html_e( 'Deprecated Jetpack Stats Experience', 'jetpack' ); ?></h2>
564 - <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>
514 + <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>
565 515 <div class="stats-odyssey-notice--action-bar">
566 516 <button class="dops-button stats-odyssey-notice--primary-button">
567 517 <a class="is-primary-link" href="<?php echo esc_url( $redirect_url ); ?>"><?php esc_html_e( 'Switch to new Stats', 'jetpack' ); ?></a>
568 518 </button>
@@ -571,13 +521,9 @@
571 521 </div>
572 522 <div class="stats-odyssey-notice--image-container"></div>
573 523 </div>
574 524 </div>
575 - <div id="stats-loading-wrap" class="wrap">
576 - <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>
577 - <p class="hide-if-js"><?php esc_html_e( 'Jetpack Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
578 - <a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Jetpack Stats without JavaScript', 'jetpack' ); ?></a>.</p>
579 - </div>
525 + <p></p>
580 526 </div>
581 527 <?php
582 528 return;
583 529 }
@@ -640,15 +586,14 @@
640 586 }
641 587 }
642 588 }
643 589
590 + $url = 'https://' . STATS_DASHBOARD_SERVER . '/wp-admin/index.php';
644 591 if ( isset( $_GET['chart'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
645 592 if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
646 593 $chart = sanitize_title( $_GET['chart'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
647 594 $url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
648 595 }
649 - } else {
650 - $url = 'https://' . STATS_DASHBOARD_SERVER . '/wp-admin/index.php';
651 596 }
652 597
653 598 $url = add_query_arg( $q, $url );
654 599 $method = 'GET';
@@ -656,34 +601,19 @@
656 601 $user_id = 0; // Means use the blog token.
657 602
658 603 $get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
659 604 $get_code = wp_remote_retrieve_response_code( $get );
660 - if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
605 + if ( is_wp_error( $get ) || $get_code === '' || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
661 606 stats_print_wp_remote_error( $get, $url );
662 - } else {
663 - if ( ! empty( $get['headers']['content-type'] ) ) {
664 - $type = $get['headers']['content-type'];
665 - if ( str_starts_with( $type, 'image' ) ) {
666 - $img = $get['body'];
667 - header( 'Content-Type: ' . $type );
668 - header( 'Content-Length: ' . strlen( $img ) );
669 - echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
670 - die();
671 - }
607 + } elseif ( ! empty( $get['headers']['content-type'] ) ) {
608 + $type = $get['headers']['content-type'];
609 + if ( str_starts_with( $type, 'image' ) ) {
610 + $img = $get['body'];
611 + header( 'Content-Type: ' . $type );
612 + header( 'Content-Length: ' . strlen( $img ) );
613 + echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
614 + die( 0 );
672 615 }
673 - $body = stats_convert_post_titles( $get['body'] );
674 - $body = stats_convert_chart_urls( $body );
675 - $body = stats_convert_image_urls( $body );
676 - $body = stats_convert_admin_urls( $body );
677 -
678 - // The response can contain either the content to display OR
679 - // the scripts for the chart UI. The following calls inspect the
680 - // response, insert the Odyssey nudge as needed, and make sure
681 - // everything is output correctly.
682 - stats_print_header_section( $body );
683 - stats_print_odyssey_nudge( $body );
684 - stats_print_content_section( $body );
685 - stats_print_chart_scripts( $body );
686 616 }
687 617
688 618 if ( isset( $_GET['page'] ) && 'stats' === $_GET['page'] && ! isset( $_GET['chart'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
689 619 $tracking = new Tracking();
@@ -690,246 +620,13 @@
690 620 $tracking->record_user_event( 'wpa_page_view', array( 'path' => 'old_stats' ) );
691 621 }
692 622
693 623 if ( isset( $_GET['noheader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
694 - die;
624 + die( 0 );
695 625 }
696 626 }
697 627
698 628 /**
699 - * Legacy Stats: Print Header Section
700 - *
701 - * @access public
702 - * @param mixed $html HTML.
703 - * @return void
704 - */
705 -function stats_print_header_section( $html ) {
706 - $header = stats_parse_header_section( $html );
707 - if ( $header !== '' ) {
708 - echo $header; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
709 - }
710 -}
711 -
712 -/**
713 - * Legacy Stats: Print Content Section
714 - *
715 - * @access public
716 - * @param mixed $html HTML.
717 - * @return void
718 - */
719 -function stats_print_content_section( $html ) {
720 - $content = stats_parse_content_section( $html );
721 - if ( $content !== '' ) {
722 - echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
723 - }
724 -}
725 -
726 -/**
727 - * Legacy Stats: Print Chart Scripts
728 - *
729 - * @access public
730 - * @param mixed $html HTML.
731 - * @return void
732 - */
733 -function stats_print_chart_scripts( $html ) {
734 - if ( is_chart_scripts( $html ) ) {
735 - echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
736 - }
737 -}
738 -
739 -/**
740 - * Legacy Stats: Test for presence of chart scripts
741 - *
742 - * @access public
743 - * @param mixed $html Input HTML that may or may not include the chart scripts.
744 - * @return bool
745 - */
746 -function is_chart_scripts( $html ) {
747 - $pos = strpos( $html, STATS_CONTENT_MARKER );
748 - return $pos === false;
749 -}
750 -
751 -/**
752 - * Legacy Stats: Parse Header Section
753 - *
754 - * Returns the section of the content up to and including the date header.
755 - *
756 - * @access public
757 - * @param mixed $html HTML.
758 - * @return string
759 - */
760 -function stats_parse_header_section( $html ) {
761 - $head = strstr( $html, STATS_CONTENT_MARKER, true );
762 - // Enforce a string result instead of string|false.
763 - if ( $head === false ) {
764 - return '';
765 - }
766 - return $head;
767 -}
768 -
769 -/**
770 - * Legacy Stats: Print Content Section
771 - *
772 - * Returns the section of the content excluding the date header.
773 - *
774 - * @access public
775 - * @param mixed $html HTML.
776 - * @return string
777 - */
778 -function stats_parse_content_section( $html ) {
779 - $body = strstr( $html, STATS_BODY_MARKER );
780 - // Enforce a string result instead of string|false.
781 - if ( $body === false ) {
782 - return '';
783 - }
784 - return $body;
785 -}
786 -
787 -/**
788 - * Legacy Stats: Determine if we need to show the Odyssey upgrade nudge.
789 - *
790 - * @access public
791 - * @return boolean
792 - */
793 -function stats_should_show_odyssey_nudge() {
794 - $stats_notices = ( new Stats_Notices() )->get_notices_to_show();
795 - return isset( $stats_notices[ Stats_Notices::OPT_IN_NEW_STATS_NOTICE_ID ] )
796 - && $stats_notices[ Stats_Notices::OPT_IN_NEW_STATS_NOTICE_ID ];
797 -}
798 -
799 -/**
800 - * Legacy Stats: Print the Odyssey upgrade nudge.
801 - *
802 - * @access public
803 - * @param mixed $html HTML.
804 - * @return void
805 - */
806 -function stats_print_odyssey_nudge( $html ) {
807 - if ( ! stats_should_show_odyssey_nudge() ) {
808 - return;
809 - }
810 - $pos = strpos( $html, STATS_CONTENT_MARKER );
811 - if ( $pos === false ) {
812 - return;
813 - }
814 - $learn_url = Redirect::get_url( 'jetpack-stats-learn-more' );
815 - $redirect_url = admin_url( 'admin.php?page=stats&enable_new_stats=1' );
816 - ?>
817 - <style>
818 - .stats-odyssey-notice {
819 - display: flex;
820 - font-size: var( --font-body );
821 -
822 - border: 1px solid var( --jp-gray-5 );
823 - border-left-color: var( --jp-black );
824 - border-left-width: 6px;
825 - border-radius: 4px;
826 -
827 - margin-top: 24px;
828 - background: white;
829 - position: relative;
830 - }
831 - .stats-odyssey-notice--content {
832 - padding: 24px 0 24px 30px;
833 - font-size: 2em;
834 - width: 100%;
835 - }
836 - .stats-odyssey-notice--content-header {
837 - font-size: 24px;
838 - line-height: 32px;
839 - margin: 0;
840 - margin-bottom: 8px;
841 - }
842 - .stats-odyssey-notice--content-text {
843 - font-size: 16px;
844 - margin: 0;
845 - }
846 - .stats-odyssey-notice--image-container {
847 - background-image: url("/wp-content/plugins/jetpack/images/odyssey-upgrade/background.png"), url("/wp-content/plugins/jetpack/images/odyssey-upgrade/gradient.png");
848 - background-size: cover;
849 - padding-right: 28px;
850 - width: 100%;
851 - }
852 - .stats-odyssey-notice--close-button {
853 - position: absolute;
854 - top: 1rem;
855 - right: 1rem;
856 - background-color: transparent;
857 - border: none;
858 - cursor: pointer;
859 - }
860 - .stats-odyssey-notice--action-bar {
861 - display: flex;
862 - align-items: center;
863 - margin-top: 24px;
864 - }
865 - .stats-odyssey-notice--primary-button {
866 - margin-right: 18px;
867 - padding-left: 20px;
868 - padding-right: 20px;
869 - font-size: 16px;
870 - border-color: black;
871 - background-color: black;
872 - }
873 - .stats-odyssey-notice--primary-button:hover {
874 - border-color: #3c434a;
875 - background-color: #3c434a;
876 - }
877 - .is-primary-link {
878 - color: white;
879 - text-decoration: none;
880 - }
881 - .is-primary-link:active {
882 - color: white;
883 - }
884 - .is-primary-link:focus {
885 - color: white;
886 - box-shadow: none;
887 - outline: none;
888 - }
889 - .is-primary-link:hover {
890 - color: white;
891 - }
892 - .is-secondary-link {
893 - color: black;
894 - font-size: var( --font-body );
895 - }
896 - .is-secondary-link:hover {
897 - color: black;
898 - }
899 - .is-hidden {
900 - display: none;
901 - }
902 - </style>
903 - <div id="stats-odyssey-nudge-main" class="stats-odyssey-notice">
904 - <div class="stats-odyssey-notice--content">
905 - <h2 class="stats-odyssey-notice--content-header"><?php esc_html_e( 'Explore the new Jetpack Stats', 'jetpack' ); ?></h2>
906 - <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>
907 - <div class="stats-odyssey-notice--action-bar">
908 - <button class="dops-button stats-odyssey-notice--primary-button">
909 - <a class="is-primary-link" href="<?php echo esc_url( $redirect_url ); ?>"><?php esc_html_e( 'Switch to new Stats', 'jetpack' ); ?></a>
910 - </button>
911 - <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>
912 - </div>
913 - </div>
914 - <div class="stats-odyssey-notice--image-container"></div>
915 - <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>
916 - </div>
917 - <?php
918 -}
919 -
920 -/**
921 - * Stats Convert Admin Urls.
922 - *
923 - * @access public
924 - * @param mixed $html HTML.
925 - * @return string
926 - */
927 -function stats_convert_admin_urls( $html ) {
928 - return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
929 -}
930 -
931 -/**
932 629 * Stats Convert Image URLs.
933 630 *
934 631 * @access public
935 632 * @param mixed $html HTML.
@@ -1121,9 +818,9 @@
1121 818
1122 819 $menu = array(
1123 820 'id' => 'stats',
1124 821 'href' => add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ), // no menu_page_url() blog-side.
1125 - 'title' => "<div><img src='$img_src' srcset='$img_src 1x, $img_src_2x 2x' width='112' height='24' alt='$alt' title='$title'></div>",
822 + '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>",
1126 823 );
1127 824
1128 825 $wp_admin_bar->add_menu( $menu );
1129 826 }
@@ -1128,21 +825,33 @@
1128 825 $wp_admin_bar->add_menu( $menu );
1129 826 }
1130 827
1131 828 /**
829 + * Adds a Stats link to the site-name admin bar submenu, alongside Dashboard.
1132 830 *
1133 - * Deprecated. The stats module should not update blog details. This is handled by Sync.
1134 - *
1135 - * Stats Update Blog.
1136 - *
1137 831 * @access public
1138 832 * @return void
1139 - *
1140 - * @deprecated since 10.3.
1141 833 */
1142 -function stats_update_blog() {
1143 - _deprecated_function( __METHOD__, 'jetpack-10.3' );
1144 - XMLRPC_Async_Call::add_call( 'jetpack.updateBlog', 0, stats_get_blog() );
834 +function stats_add_link_to_admin_bar_site_menu() {
835 + global $wp_admin_bar;
836 +
837 + if (
838 + ! is_object( $wp_admin_bar ) ||
839 + ! $wp_admin_bar->get_node( 'dashboard' ) ||
840 + ! current_user_can( 'view_stats' ) ||
841 + ( new Host() )->is_wpcom_platform()
842 + ) {
843 + return;
844 + }
845 +
846 + $wp_admin_bar->add_node(
847 + array(
848 + 'parent' => 'site-name',
849 + 'id' => 'jetpack-stats',
850 + 'title' => __( 'Stats', 'jetpack' ),
851 + 'href' => admin_url( 'admin.php?page=stats' ),
852 + )
853 + );
1145 854 }
1146 855
1147 856 /**
1148 857 * Stats Get Blog.
@@ -1373,9 +1082,9 @@
1373 1082 $user_id = 0; // Means use the blog token.
1374 1083
1375 1084 $get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1376 1085 $get_code = wp_remote_retrieve_response_code( $get );
1377 - if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1086 + if ( is_wp_error( $get ) || $get_code === '' || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1378 1087 stats_print_wp_remote_error( $get, $url );
1379 1088 } else {
1380 1089 $body = stats_convert_post_titles( $get['body'] );
1381 1090 $body = stats_convert_chart_urls( $body );
@@ -1478,9 +1187,9 @@
1478 1187 </div>
1479 1188 </div>
1480 1189 </div>
1481 1190 <?php
1482 - exit;
1191 + exit( 0 );
1483 1192 }
1484 1193
1485 1194 /**
1486 1195 * Stats Print WP Remote Error.
@@ -1492,9 +1201,9 @@
1492 1201 */
1493 1202 function stats_print_wp_remote_error( $get, $url ) {
1494 1203 $state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1495 1204 $previous_error = Jetpack::state( $state_name );
1496 - $error = md5( wp_json_encode( compact( 'get', 'url' ) ) );
1205 + $error = md5( wp_json_encode( compact( 'get', 'url' ), JSON_UNESCAPED_SLASHES ) );
1497 1206 Jetpack::state( $state_name, $error );
1498 1207 if ( $error !== $previous_error ) {
1499 1208 ?>
1500 1209 <div class="wrap">
@@ -1554,8 +1263,9 @@
1554 1263 * @type string $days The length of the desired time frame. Default is 30. Maximum 90 days.
1555 1264 * @type int $limit The maximum number of records to return. Default is 10. Maximum 100.
1556 1265 * @type int $post_id The ID of the post to retrieve stats data for
1557 1266 * @type string $summarize If present, summarizes all matching records. Default Null.
1267 + * @type int $blog_id The WordPress.com blog ID to retrieve stats data for. Default is the current blog.
1558 1268 *
1559 1269 * }
1560 1270 *
1561 1271 * @return array {
@@ -1577,13 +1287,13 @@
1577 1287 'days' => false,
1578 1288 'limit' => 3,
1579 1289 'post_id' => false,
1580 1290 'summarize' => '',
1291 + 'blog_id' => Jetpack_Options::get_option( 'id' ),
1581 1292 );
1582 1293
1583 - $args = wp_parse_args( $args, $defaults );
1584 - $args['table'] = $table;
1585 - $args['blog_id'] = Jetpack_Options::get_option( 'id' );
1294 + $args = wp_parse_args( $args, $defaults );
1295 + $args['table'] = $table;
1586 1296
1587 1297 $stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
1588 1298
1589 1299 $key = md5( $stats_csv_url );
@@ -1653,9 +1363,9 @@
1653 1363 $user_id = 0; // Blog token.
1654 1364
1655 1365 $get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1656 1366 $get_code = wp_remote_retrieve_response_code( $get );
1657 - if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1367 + if ( is_wp_error( $get ) || $get_code === '' || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1658 1368 return array(); // @todo: return an error?
1659 1369 } else {
1660 1370 return stats_str_getcsv( $get['body'] );
1661 1371 }
@@ -1673,10 +1383,9 @@
1673 1383 // @todo Correctly handle embedded newlines. Note, despite claims online, `str_getcsv( $csv, "\n" )` does not actually work.
1674 1384 $lines = explode( "\n", rtrim( $csv, "\n" ) );
1675 1385 return array_map(
1676 1386 function ( $line ) {
1677 - // @todo When we drop support for PHP <7.4, consider passing empty-string for `$escape` here for better spec compatibility.
1678 - return str_getcsv( $line, ',', '"', '\\' );
1387 + return str_getcsv( $line, ',', '"', '' );
1679 1388 },
1680 1389 $lines
1681 1390 );
1682 1391 }
@@ -1706,9 +1415,9 @@
1706 1415 _deprecated_function( __METHOD__, 'jetpack-11.5', 'Please checkout the methods available in Automattic\Jetpack\Stats\WPCOM_Stats' );
1707 1416 $endpoint = jetpack_stats_api_path( $resource );
1708 1417 $api_version = '1.1';
1709 1418 $args = wp_parse_args( $args, array() );
1710 - $cache_key = md5( implode( '|', array( $endpoint, $api_version, wp_json_encode( $args ) ) ) );
1419 + $cache_key = md5( implode( '|', array( $endpoint, $api_version, wp_json_encode( $args, JSON_UNESCAPED_SLASHES ) ) ) );
1711 1420
1712 1421 $transient_name = "jetpack_restapi_stats_cache_{$cache_key}";
1713 1422
1714 1423 $stats_cache = get_transient( $transient_name );
@@ -1742,110 +1451,8 @@
1742 1451 // To reduce size in storage: store with time as key, store JSON encoded data (unless error).
1743 1452 set_transient( $transient_name, array( time() => $data ), 5 * MINUTE_IN_SECONDS );
1744 1453
1745 1454 return $return;
1746 -}
1747 -
1748 -/**
1749 - * Load CSS needed for Stats column width in WP-Admin area.
1750 - *
1751 - * @since 4.7.0
1752 - */
1753 -function jetpack_stats_load_admin_css() {
1754 - ?>
1755 - <style type="text/css">
1756 - .fixed .column-stats {
1757 - width: 5em;
1758 - }
1759 - </style>
1760 - <?php
1761 -}
1762 -
1763 -/**
1764 - * Set header for column that allows to view an entry's stats.
1765 - *
1766 - * @param array $columns An array of column names.
1767 - *
1768 - * @since 4.7.0
1769 - *
1770 - * @return mixed
1771 - */
1772 -function jetpack_stats_post_table( $columns ) {
1773 - /*
1774 - * Stats can be accessed in wp-admin or in Calypso,
1775 - * depending on what version of the stats screen is enabled on your site.
1776 - *
1777 - * In both cases, the user must be allowed to access stats.
1778 - *
1779 - * If the Odyssey Stats experience isn't enabled, the user will need to go to Calypso,
1780 - * so they need to be connected to WordPress.com to be able to access that page.
1781 - */
1782 - if (
1783 - ! current_user_can( 'view_stats' )
1784 - || (
1785 - ! Stats_Options::get_option( 'enable_odyssey_stats' )
1786 - && ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected()
1787 - )
1788 - ) {
1789 - return $columns;
1790 - }
1791 -
1792 - // Array-Fu to add before comments.
1793 - $pos = array_search( 'comments', array_keys( $columns ), true );
1794 -
1795 - // Fallback to the last position if the post type does not support comments.
1796 - if ( ! is_int( $pos ) ) {
1797 - $pos = count( $columns );
1798 - }
1799 -
1800 - // Final fallback, if the array was malformed by another plugin for example.
1801 - if ( ! is_int( $pos ) ) {
1802 - return $columns;
1803 - }
1804 -
1805 - $chunks = array_chunk( $columns, $pos, true );
1806 - $chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
1807 -
1808 - return call_user_func_array( 'array_merge', $chunks );
1809 -}
1810 -
1811 -/**
1812 - * Set content for cell with link to an entry's stats in Odyssey Stats.
1813 - *
1814 - * @param string $column The name of the column to display.
1815 - * @param int $post_id The current post ID.
1816 - *
1817 - * @since 4.7.0
1818 - *
1819 - * @return mixed
1820 - */
1821 -function jetpack_stats_post_table_cell( $column, $post_id ) {
1822 - if ( 'stats' === $column ) {
1823 - if ( 'publish' !== get_post_status( $post_id ) ) {
1824 - printf(
1825 - '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
1826 - esc_html__( 'No stats', 'jetpack' )
1827 - );
1828 - } else {
1829 - // Link to the wp-admin stats page.
1830 - $stats_post_url = admin_url( sprintf( 'admin.php?page=stats#!/stats/post/%d/%d', $post_id, Jetpack_Options::get_option( 'id', 0 ) ) );
1831 - // Unless the user is on a Default style WOA site, in which case link to Calypso.
1832 - if ( ( new Host() )->is_woa_site() && Stats_Options::get_option( 'enable_odyssey_stats' ) && 'wp-admin' !== get_option( 'wpcom_admin_interface' ) ) {
1833 - $stats_post_url = Redirect::get_url(
1834 - 'calypso-stats-post',
1835 - array(
1836 - 'path' => $post_id,
1837 - )
1838 - );
1839 - }
1840 -
1841 - printf(
1842 - '<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
1843 - esc_url( $stats_post_url ),
1844 - esc_html__( 'View stats for this post', 'jetpack' )
1845 - );
1846 - }
1847 - }
1848 1455 }
1849 1456
1850 1457 /**
1851 1458 * Add the Jetpack plugin version to the stats tracking data.