class="L905" rel="L905"> * @param mixed $wp_admin_bar WPAdminBar.
* @return void
*/
function stats_admin_bar_menu( &$wp_admin_bar ) {
$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
$img_src = esc_attr(
add_query_arg(
array(
'noheader' => '',
'proxy' => '',
'chart' => 'admin-bar-hours-scale',
),
$url
)
);
$img_src_2x = esc_attr(
add_query_arg(
array(
'noheader' => '',
'proxy' => '',
'chart' => 'admin-bar-hours-scale-2x',
),
$url
)
);
$alt = esc_attr( __( 'Stats', 'jetpack' ) );
$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
$menu = array(
'id' => 'stats',
'href' => $url,
'title' => "<div><img src='$img_src' srcset='$img_src 1x, $img_src_2x 2x' width='112' height='24' alt='$alt' title='$title'></div>",
);
$wp_admin_bar->add_menu( $menu );
}
/**
* Stats Update Blog.
*
* @access public
* @return void
*/
function stats_update_blog() {
XMLRPC_Async_Call::add_call( 'jetpack.updateBlog', 0, stats_get_blog() );
}
/**
* Stats Get Blog.
*
* @access public
* @return string
*/
function stats_get_blog() {
$home = wp_parse_url( trailingslashit( get_option( 'home' ) ) );
$blog = array(
'host' => $home['host'],
'path' => $home['path'],
'blogname' => get_option( 'blogname' ),
'blogdescription' => get_option( 'blogdescription' ),
'siteurl' => get_option( 'siteurl' ),
'gmt_offset' => get_option( 'gmt_offset' ),
'timezone_string' => get_option( 'timezone_string' ),
'stats_version' => STATS_VERSION,
'stats_api' => 'jetpack',
'page_on_front' => get_option( 'page_on_front' ),
'permalink_structure' => get_option( 'permalink_structure' ),
'category_base' => get_option( 'category_base' ),
'tag_base' => get_option( 'tag_base' ),
);
$blog = array_merge( stats_get_options(), $blog );
unset( $blog['roles'], $blog['blog_id'] );
return stats_esc_html_deep( $blog );
}
/**
* Modified from stripslashes_deep()
*
* @access public
* @param mixed $value Value.
* @return string
*/
function stats_esc_html_deep( $value ) {
if ( is_array( $value ) ) {
$value = array_map( 'stats_esc_html_deep', $value );
} elseif ( is_object( $value ) ) {
$vars = get_object_vars( $value );
foreach ( $vars as $key => $data ) {
$value->{$key} = stats_esc_html_deep( $data );
}
} elseif ( is_string( $value ) ) {
$value = esc_html( $value );
}
return $value;
}
/**
* Stats xmlrpc_methods function.
*
* @access public
* @param mixed $methods Methods.
* @return array
*/
function stats_xmlrpc_methods( $methods ) {
$my_methods = array(
'jetpack.getBlog' => 'stats_get_blog',
);
return array_merge( $methods, $my_methods );
}
/**
* Stats Dashboard Widget Options.
*
* @access public
* @return array
*/
function stats_dashboard_widget_options() {
$defaults = array(
'chart' => 1,
'top' => 1,
'search' => 7,
);
$options = get_option( 'stats_dashboard_widget' );
if ( ( ! $options ) || ! is_array( $options ) ) {
$options = array();
}
// Ignore obsolete option values.
$intervals = array( 1, 7, 31, 90, 365 );
foreach ( array( 'top', 'search' ) as $key ) {
if ( isset( $options[ $key ] ) && ! in_array( (int) $options[ $key ], $intervals, true ) ) {
unset( $options[ $key ] );
}
}
return array_merge( $defaults, $options );
}
/**
* Stats Dashboard Widget Control.
*
* @access public
* @return void
*/
function stats_dashboard_widget_control() {
$periods = array(
'1' => __( 'day', 'jetpack' ),
'7' => __( 'week', 'jetpack' ),
'31' => __( 'month', 'jetpack' ),
);
$intervals = array(
'1' => __( 'the past day', 'jetpack' ),
'7' => __( 'the past week', 'jetpack' ),
'31' => __( 'the past month', 'jetpack' ),
'90' => __( 'the past quarter', 'jetpack' ),
'365' => __( 'the past year', 'jetpack' ),
);
$defaults = array(
'top' => 1,
'search' => 7,
);
$options = stats_dashboard_widget_options();
if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' === $_POST['widget_id'] ) { // phpcs:ignore WordPress.Security.NonceVerification
if ( isset( $periods[ $_POST['chart'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
$options['chart'] = $_POST['chart']; // phpcs:ignore WordPress.Security.NonceVerification
}
foreach ( array( 'top', 'search' ) as $key ) {
if ( isset( $intervals[ $_POST[ $key ] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
$options[ $key ] = $_POST[ $key ]; // phpcs:ignore WordPress.Security.NonceVerification
} else {
$options[ $key ] = $defaults[ $key ];
}
}
update_option( 'stats_dashboard_widget', $options );
}
?>
<p>
<label for="chart"><?php esc_html_e( 'Chart stats by', 'jetpack' ); ?></label>
<select id="chart" name="chart">
<?php
foreach ( $periods as $val => $label ) {
?>
<option value="<?php echo esc_attr( $val ); ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
<?php
}
?>
</select>.
</p>
<p>
<label for="top"><?php esc_html_e( 'Show top posts over', 'jetpack' ); ?></label>
<select id="top" name="top">
<?php
foreach ( $intervals as $val => $label ) {
?>
<option value="<?php echo esc_attr( $val ); ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
<?php
}
?>
</select>.
</p>
<p>
<label for="search"><?php esc_html_e( 'Show top search terms over', 'jetpack' ); ?></label>
<select id="search" name="search">
<?php
foreach ( $intervals as $val => $label ) {
?>
<option value="<?php echo esc_attr( $val ); ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
<?php
}
?>
</select>.
</p>
<?php
}
/**
* Jetpack Stats Dashboard Widget.
*
* @access public
* @return void
*/
function stats_jetpack_dashboard_widget() {
?>
<form id="stats_dashboard_widget_control" action="<?php echo esc_url( admin_url() ); ?>" method="post">
<?php stats_dashboard_widget_control(); ?>
<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
<input type="hidden" name="widget_id" value="dashboard_stats" />
<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
</form>
<button type="button" class="handlediv js-toggle-stats_dashboard_widget_control" aria-expanded="true">
<span class="screen-reader-text"><?php esc_html_e( 'Configure', 'jetpack' ); ?></span>
<span class="toggle-indicator" aria-hidden="true"></span>
</button>
<div id="dashboard_stats">
<div class="inside">
<div style="height: 250px;"></div>
</div>
</div>
<?php
}
/**
* JavaScript and CSS for dashboard widget.
*
* @access public
* @return void
*/
function stats_dashboard_head() {
?>
<script type="text/javascript">
/* <![CDATA[ */
jQuery( function($) {
var dashStats = jQuery( '#dashboard_stats div.inside' );
if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
return;
}
if ( ! dashStats.length ) {
dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
var args = 'width=' + dashStats.width() + '&height=' + h.toString();
} else {
if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
} else {
var args = 'width=' + ( dashStats.width() * 2 ).toString();
}
}
dashStats
.not( '.dashboard-widget-control' )
.load( 'admin.php?page=stats&noheader&dashboard&' + args );
jQuery( window ).one( 'resize', function() {
jQuery( '#stat-chart' ).css( 'width', 'auto' );
} );
// Widget settings toggle container.
var toggle = $( '.js-toggle-stats_dashboard_widget_control' );
// Move the toggle in the widget header.
toggle.appendTo( '#jetpack_summary_widget .handle-actions' );
// Toggle settings when clicking on it.
toggle.show().click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
$( this ).parent().toggleClass( 'controlVisible' );
$( '#stats_dashboard_widget_control' ).slideToggle();
} );
} );
/* ]]> */
</script>
<?php
}
/**
* Stats Dashboard Widget Content.
*
* @access public
* @return void
*/
function stats_dashboard_widget_content() {
$width = isset( $_GET['width'] ) ? (int) ( $_GET['width'] / 2 ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$height = isset( $_GET['height'] ) ? (int) $_GET['height'] - 36 : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! $width || $width < 250 ) {
$width = 370;
}
if ( ! $height || $height < 230 ) {
$height = 180;
}
$_width = $width - 5;
$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // Hack! @todo Remove WordPress 5.8 is minimum. IE should be fully deprecated.
$options = stats_dashboard_widget_options();
$blog_id = Jetpack_Options::get_option( 'id' );
$q = array(
'noheader' => 'true',
'proxy' => '',
'blog' => $blog_id,
'page' => 'stats',
'chart' => '',
'unit' => $options['chart'],
'color' => get_user_option( 'admin_color' ),
'width' => $_width,
'height' => $_height,
'ssl' => is_ssl(),
'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
);
$url = 'https://' . STATS_DASHBOARD_SERVER . '/wp-admin/index.php';
$url = add_query_arg( $q, $url );
$method = 'GET';
$timeout = 90;
$user_id = 0; // Means use the blog token.
$get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
$get_code = wp_remote_retrieve_response_code( $get );
if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
stats_print_wp_remote_error( $get, $url );
} else {
$body = stats_convert_post_titles( $get['body'] );
$body = stats_convert_chart_urls( $body );
$body = stats_convert_image_urls( $body );
echo $body; // phpcs:ignore WordPress.Security.EscapeOutput
}
$post_ids = array();
$csv_end_date = current_time( 'Y-m-d' );
$csv_args = array(
'top' => "&limit=8&end=$csv_end_date",
'search' => "&limit=5&end=$csv_end_date",
);
$top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" );
foreach ( $top_posts as $i => $post ) {
if ( 0 === $post['post_id'] ) {
unset( $top_posts[ $i ] );
continue;
}
$post_ids[] = $post['post_id'];
}
// Cache.
get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
$searches = array();
$search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" );
foreach ( $search_terms as $search_term ) {
if ( 'encrypted_search_terms' === $search_term['searchterm'] ) {
continue;
}
$searches[] = esc_html( $search_term['searchterm'] );
}
?>
<div id="stats-info">
<div id="top-posts" class='stats-section'>
<div class="stats-section-inner">
<h3 class="heading"><?php esc_html_e( 'Top Posts', 'jetpack' ); ?></h3>
<?php
if ( empty( $top_posts ) ) {
?>
<p class="nothing"><?php esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
<?php
} else {
foreach ( $top_posts as $post ) {
if ( ! get_post( $post['post_id'] ) ) {
continue;
}
?>
<p>
<?php
printf(
esc_html(
/* Translators: Stats dashboard widget Post list with view count: "Post Title 1 View (or Views if plural)". */
_n( '%1$s %2$s View', '%1$s %2$s Views', $post['views'], 'jetpack' )
),
'<a href="' . esc_url( get_permalink( $post['post_id'] ) ) . '">' . esc_html( get_the_title( $post['post_id'] ) ) . '</a>',
esc_html( number_format_i18n( $post['views'] ) )
);
?>
</p>
<?php
}
}
?>
</div>
</div>
<div id="top-search" class='stats-section'>
<div class="stats-section-inner">
<h3 class="heading"><?php esc_html_e( 'Top Searches', 'jetpack' ); ?></h3>
<?php
if ( empty( $searches ) ) {
?>
<p class="nothing"><?php esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
<?php
} else {
foreach ( $searches as $search_term_item ) {
printf(
'<p>%s</p>',
esc_html( $search_term_item )
);
}
}
?>
</div>
</div>
</div>
<div class="clear"></div>
<div class="stats-view-all">
<?php
$stats_day_url = Redirect::get_url( 'calypso-stats-day' );
printf(
'<a class="button" target="_blank" rel="noopener noreferrer" href="%1$s">%2$s</a>',
esc_url( $stats_day_url ),
esc_html__( 'View all stats', 'jetpack' )
);
?>
</div>
<div class="clear"></div>
<?php
exit;
}
/**
* Stats Print WP Remote Error.
*
* @access public
* @param mixed $get Get.
* @param mixed $url URL.
* @return void
*/
function stats_print_wp_remote_error( $get, $url ) {
$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
$previous_error = Jetpack::state( $state_name );
$error = md5( wp_json_encode( compact( 'get', 'url' ) ) );
Jetpack::state( $state_name, $error );
if ( $error !== $previous_error ) {
?>
<div class="wrap">
<p><?php esc_html_e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
</div>
<?php
return;
}
?>
<div class="wrap">
<p>
<?php
printf(
/* translators: placeholder is an a href for a support site. */
esc_html__( 'We were unable to get your stats just now. Please reload this page to try again. If this error persists, please contact %1$s. In your report, please include the information below.', 'jetpack' ),
sprintf(
'<a href="https://support.wordpress.com/contact/?jetpack=needs-service">%s</a>',
esc_html__( 'Jetpack Support', 'jetpack' )
)
);
?>
</p>
<pre>
User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
Page URL: "http<?php echo ( is_ssl() ? 's' : '' ) . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
API URL: "<?php echo esc_url( $url ); ?>"
<?php
if ( is_wp_error( $get ) ) {
foreach ( $get->get_error_codes() as $code ) {
foreach ( $get->get_error_messages( $code ) as $message ) {
?>
<?php print esc_html( $code ) . ': "' . esc_html( $message ) . '"'; ?>
<?php
}
}
} else {
$get_code = wp_remote_retrieve_response_code( $get );
$content_length = strlen( wp_remote_retrieve_body( $get ) );
?>
Response code: "<?php print esc_html( $get_code ); ?>"
Content length: "<?php print esc_html( $content_length ); ?>"
<?php
}
?>
</pre>
</div>
<?php
}
/**
* Get stats from WordPress.com
*
* @param string $table The stats which you want to retrieve: postviews, or searchterms.
* @param array $args {
* An associative array of arguments.
*
* @type bool $end The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
* and default timezone is UTC date. Default value is Now.
* @type string $days The length of the desired time frame. Default is 30. Maximum 90 days.
* @type int $limit The maximum number of records to return. Default is 10. Maximum 100.
* @type int $post_id The ID of the post to retrieve stats data for
* @type string $summarize If present, summarizes all matching records. Default Null.
*
* }
*
* @return array {
* An array of post view data, each post as an array
*
* array {
* The post view data for a single post
*
* @type string $post_id The ID of the post
* @type string $post_title The title of the post
* @type string $post_permalink The permalink for the post
* @type string $views The number of views for the post within the $num_days specified
* }
* }
*/
function stats_get_csv( $table, $args = null ) {
$defaults = array(
'end' => false,
'days' => false,
'limit' => 3,
'post_id' => false,
'summarize' => '',
);
$args = wp_parse_args( $args, $defaults );
$args['table'] = $table;
$args['blog_id'] = Jetpack_Options::get_option( 'id' );
$stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
$key = md5( $stats_csv_url );
// Get cache.
$stats_cache = get_option( 'stats_cache' );
if ( ! $stats_cache || ! is_array( $stats_cache ) ) {
$stats_cache = array();
}
// Return or expire this key.
if ( isset( $stats_cache[ $key ] ) ) {
$time = key( $stats_cache[ $key ] );
if ( time() - $time < 300 ) {
return $stats_cache[ $key ][ $time ];
}
unset( $stats_cache[ $key ] );
}
$stats_rows = array();
do {
$stats = stats_get_remote_csv( $stats_csv_url );
if ( ! $stats ) {
break;
}
$labels = array_shift( $stats );
if ( 0 === stripos( $labels[0], 'error' ) ) {
break;
}
$stats_rows = array();
for ( $s = 0; isset( $stats[ $s ] ); $s++ ) {
$row = array();
foreach ( $labels as $col => $label ) {
$row[ $label ] = $stats[ $s ][ $col ];
}
$stats_rows[] = $row;
}
} while ( 0 );
// Expire old keys.
foreach ( $stats_cache as $k => $cache ) {
if ( ! is_array( $cache ) || 300 < time() - key( $cache ) ) {
unset( $stats_cache[ $k ] );
}
}
// Set cache.
$stats_cache[ $key ] = array( time() => $stats_rows );
update_option( 'stats_cache', $stats_cache );
return $stats_rows;
}
/**
* Stats get remote CSV.
*
* @access public
* @param mixed $url URL.
* @return array
*/
function stats_get_remote_csv( $url ) {
$method = 'GET';
$timeout = 90;
$user_id = 0; // Blog token.
$get = Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
$get_code = wp_remote_retrieve_response_code( $get );
if ( is_wp_error( $get ) || ( 2 !== (int) ( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
return array(); // @todo: return an error?
} else {
return stats_str_getcsv( $get['body'] );
}
}
/**
* Recursively run str_getcsv on the stats csv.
*
* @since 9.7.0 Remove custom handling since str_getcsv is available on all servers running this now.
*
* @param mixed $csv CSV.
* @return array.
*/
function stats_str_getcsv( $csv ) {
$lines = str_getcsv( $csv, "\n" );
return array_map( 'str_getcsv', $lines );
}
/**
* Abstract out building the rest api stats path.
*
* @param string $resource Resource.
* @return string
*/
function jetpack_stats_api_path( $resource = '' ) {
$resource = ltrim( $resource, '/' );
return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
}
/**
* Fetches stats data from the REST API. Caches locally for 5 minutes.
*
* @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
* @access public
* @param array $args (default: array()) The args that are passed to the endpoint.
* @param string $resource (default: '') Optional sub-endpoint following /stats/.
* @return array|WP_Error.
*/
function stats_get_from_restapi( $args = array(), $resource = '' ) {
$endpoint = jetpack_stats_api_path( $resource );
$api_version = '1.1';
$args = wp_parse_args( $args, array() );
$cache_key = md5( implode( '|', array( $endpoint, $api_version, wp_json_encode( $args ) ) ) );
$transient_name = "jetpack_restapi_stats_cache_{$cache_key}";
$stats_cache = get_transient( $transient_name );
// Return or expire this key.
if ( $stats_cache ) {
$time = key( $stats_cache );
$data = $stats_cache[ $time ]; // WP_Error or string (JSON encoded object).
if ( is_wp_error( $data ) ) {
return $data;
}
return (object) array_merge( array( 'cached_at' => $time ), (array) json_decode( $data ) );
}
// Do the dirty work.
$response = Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
// WP_Error.
$data = is_wp_error( $response ) ? $response : new WP_Error( 'stats_error' );
// WP_Error.
$return = $data;
} else {
// string (JSON encoded object).
$data = wp_remote_retrieve_body( $response );
// object (rare: null on JSON failure).
$return = json_decode( $data );
}
// To reduce size in storage: store with time as key, store JSON encoded data (unless error).
set_transient( $transient_name, array( time() => $data ), 5 * MINUTE_IN_SECONDS );
return $return;
}
/**
* Load CSS needed for Stats column width in WP-Admin area.
*
* @since 4.7.0
*/
function jetpack_stats_load_admin_css() {
?>
<style type="text/css">
.fixed .column-stats {
width: 5em;
}
</style>
<?php
}
/**
* Set header for column that allows to go to WordPress.com to see an entry's stats.
*
* @param array $columns An array of column names.
*
* @since 4.7.0
*
* @return mixed
*/
function jetpack_stats_post_table( $columns ) {
// Adds a stats link on the edit posts page.
if ( ! current_user_can( 'view_stats' ) || ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) {
return $columns;
}
// Array-Fu to add before comments.
$pos = array_search( 'comments', array_keys( $columns ), true );
if ( ! is_int( $pos ) ) {
return $columns;
}
$chunks = array_chunk( $columns, $pos, true );
$chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
return call_user_func_array( 'array_merge', $chunks );
}
/**
* Set content for cell with link to an entry's stats in WordPress.com.
*
* @param string $column The name of the column to display.
* @param int $post_id The current post ID.
*
* @since 4.7.0
*
* @return mixed
*/
function jetpack_stats_post_table_cell( $column, $post_id ) {
if ( 'stats' === $column ) {
if ( 'publish' !== get_post_status( $post_id ) ) {
printf(
'<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
esc_html__( 'No stats', 'jetpack' )
);
} else {
$stats_post_url = Redirect::get_url(
'calypso-stats-post',
array(
'path' => $post_id,
)
);
printf(
'<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
esc_url( $stats_post_url ),
esc_html__( 'View stats for this post in WordPress.com', 'jetpack' )
);
}
}
}