—%s', esc_html__( 'No stats', 'jetpack-stats-admin' ) ); } else { // Link to the wp-admin stats page. $query_args = array( 'from' => 'postList', 'jp_post_type' => get_post_type( $post_id ), ); $list_criteria_params = array( 's' => sanitize_text_field( get_search_query() ), 'paged' => absint( get_query_var( 'paged' ) ), 'post_status' => sanitize_text_field( get_query_var( 'post_status' ) ), 'orderby' => sanitize_text_field( get_query_var( 'orderby' ) ), 'order' => sanitize_text_field( get_query_var( 'order' ) ), 'author' => absint( get_query_var( 'author' ) ), 'cat' => absint( get_query_var( 'cat' ) ), // 'cat' is the query var for category ID 'm' => absint( get_query_var( 'm' ) ), // 'm' is the query var for YYYYMM 'category_name' => sanitize_text_field( get_query_var( 'category_name' ) ), ); foreach ( $list_criteria_params as $key => $value ) { if ( isset( $_GET[ $key ] ) && $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking if the key existts and not reading the value from the request. $query_args[ 'jp_' . $key ] = $value; } } $stats_post_url = add_query_arg( $query_args, admin_url( 'admin.php?page=stats#!/stats/post/' . $post_id . '/' . \Jetpack_Options::get_option( 'id', 0 ) ) ); // Unless the user is on a Default style WOA site, in which case link to Calypso. if ( ( new Host() )->is_woa_site() && Stats_Options::get_option( 'enable_odyssey_stats' ) && 'wp-admin' !== get_option( 'wpcom_admin_interface' ) ) { $stats_post_url = Redirect::get_url( 'calypso-stats-post', array( 'path' => $post_id, ) ); } /** * Filters where the post list table's views column links to. * * Lets a newer analytics dashboard claim the entry point without this * package knowing about it. * * @since 0.34.0 * * @param string $stats_post_url Stats URL for the post. * @param int $post_id The post the row belongs to. */ $stats_post_url = apply_filters( 'jetpack_stats_post_list_column_url', $stats_post_url, $post_id ); static $post_views = null; /** * Jetpack_stats_get_post_page_views_for_current_list makes a request with all post ids in the current $wp_query. * This way, we'll make a single API request instead of making one for each post. * * For this reason, we'll cache the result with the static $post_views variable. */ if ( null === $post_views ) { $post_views = $this->get_post_page_views_for_current_list(); } $views = $post_views[ $post_id ] ?? null; $current_locale = get_locale(); if ( null !== $views ) { $formatted_views = class_exists( '\NumberFormatter' ) ? $this->get_formatter( $current_locale )->format( $views ) : $this->get_fallback_format_to_compact_version( $views ); } else { $formatted_views = ''; } ?>   post_type ) { $post_type_object = get_post_type_object( $screen->post_type ); if ( $post_type_object && ! $post_type_object->public ) { return $columns; } } } /** * The manage_options capability is a fallback for Simple. * This should be updated with a proper fix. Implemented based on this PR: https://github.com/Automattic/jetpack/pull/41549. */ $has_access = current_user_can( 'view_stats' ) || current_user_can( 'manage_options' ); /* * Stats can be accessed in wp-admin or in Calypso, * depending on what version of the stats screen is enabled on your site. * * In both cases, the user must be allowed to access stats. * * If the Odyssey Stats experience isn't enabled, the user will need to go to Calypso, * so they need to be connected to WordPress.com to be able to access that page. */ if ( ! $has_access || ( ! Stats_Options::get_option( 'enable_odyssey_stats' ) && ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) ) { return $columns; } // Array-Fu to add before comments. $pos = array_search( 'comments', array_keys( $columns ), true ); // Fallback to the last position if the post type does not support comments. if ( ! is_int( $pos ) ) { $pos = count( $columns ); } // If comments position is 0, then prepend the element at the beginning of the array. if ( 0 === $pos ) { return array_merge( array( 'stats' => esc_html__( 'Views: 30 days', 'jetpack-stats-admin' ) ), $columns ); } $chunks = array_chunk( $columns, $pos, true ); $chunks[0]['stats'] = esc_html__( 'Views: 30 days', 'jetpack-stats-admin' ); return call_user_func_array( 'array_merge', $chunks ); } /** * Get a list of post views for each post id from the global $wp_query. * * @return array */ public function get_post_page_views_for_current_list(): array { global $wp_query; if ( $wp_query->posts ) { $post_ids = wp_list_pluck( $wp_query->posts, 'ID' ); } elseif ( wp_doing_ajax() && ! empty( $_POST['action'] ) && 'inline-save' === $_POST['action'] && ! empty( $_POST['post_ID'] ) && check_ajax_referer( 'inlineeditnonce', '_inline_edit' ) ) { $post_ids = array( absint( wp_unslash( $_POST['post_ID'] ) ) ); } else { return array(); } $wpcom_stats = $this->get_stats(); $post_views = $wpcom_stats->get_total_post_views( array( 'num' => 30, 'post_ids' => implode( ',', $post_ids ), ) ); if ( is_wp_error( $post_views ) || empty( $post_views ) ) { return array(); } $views = array(); foreach ( $post_views['posts'] as $post ) { $views[ $post['ID'] ] = $post['views']; } return $views; } /** * Get the stats object. * * @return WPCOM_Stats */ protected function get_stats() { return new WPCOM_Stats(); } /** * Get and validate the locale. * * @param string $locale The locale to validate. * * @return string The validated locale. */ public function get_validated_locale( string $locale ): string { if ( isset( $this->locale ) ) { return $this->locale; } /* * Check if the locale is valid and available. * If not, fallback to en_US. */ if ( ! in_array( $locale, \IntlCalendar::getAvailableLocales(), true ) ) { $locale = 'en_US'; } $this->locale = $locale; return $locale; } /** * Get the NumberFormatter instance. * * @param string $locale The current locale. * * @return NumberFormatter */ protected function get_formatter( string $locale ): \NumberFormatter { if ( isset( $this->formatter[ $locale ] ) ) { return $this->formatter[ $locale ]; } $locale = $this->get_validated_locale( $locale ); /** * PHP's NumberFormatter is just a wrapper over the ICU C library. The library does support decimal compact short formatter, but PHP doesn't have a stub for it (=< PHP 8.4). * * @see https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/unum_8h.html UNUM_DECIMAL_COMPACT_SHORT constant. */ $compact_decimal_short = 14; /** * NumberFormatter::DECIMAL_COMPACT_SHORT only exists in PHP 8.5 and later. At this time, NumberFormatter::DECIMAL_COMPACT_SHORT only exists in PHP `main` branch. * * Use the constant if it's defined since it's safer. */ if ( defined( '\NumberFormatter::DECIMAL_COMPACT_SHORT' ) ) { // @phan-suppress-next-line PhanUndeclaredConstantOfClass $compact_decimal_short = NumberFormatter::DECIMAL_COMPACT_SHORT; } try { $formatter = new \NumberFormatter( $locale, $compact_decimal_short ); $formatter->setAttribute( \NumberFormatter::MAX_FRACTION_DIGITS, 1 ); } catch ( \Exception $e ) { // Fallback to decimal if for some reason it fails to work. $formatter = new \NumberFormatter( $locale, \NumberFormatter::DECIMAL ); } $this->formatter[ $locale ] = $formatter; return $formatter; } /** * Fallback Format a number to a compact version if the Intl extension is not available. * * @param int $views The given number. * * @return string */ public function get_fallback_format_to_compact_version( $views ) { if ( $views >= 10000000 ) { return round( $views / 1000000 ) . 'M'; } elseif ( $views >= 1000000 ) { $views = round( $views / 1000000, 1 ); return preg_replace( '/\.0$/', '', (string) $views ) . 'M'; } elseif ( $views >= 10000 ) { return round( $views / 1000 ) . 'K'; } elseif ( $views >= 1000 ) { $views = round( $views / 1000, 1 ); return preg_replace( '/\.0$/', '', (string) $views ) . 'K'; } return (string) $views; } }