Completed
Push — fix/typo-pro-promo ( e5832e...fc698e )
by
unknown
29:20
created

stats.php ➔ stats_reports_load()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 6
nop 0
dl 0
loc 21
rs 7.551
c 0
b 0
f 0
1
<?php
2
/**
3
 * Module Name: Site Stats
4
 * Module Description: Collect valuable traffic stats and insights.
5
 * Sort Order: 1
6
 * Recommendation Order: 2
7
 * First Introduced: 1.1
8
 * Requires Connection: Yes
9
 * Auto Activate: Yes
10
 * Module Tags: Site Stats, Recommended
11
 * Feature: Engagement
12
 * Additional Search Queries: statistics, tracking, analytics, views, traffic, stats
13
 *
14
 * @package Jetpack
15
 */
16
17
if ( defined( 'STATS_VERSION' ) ) {
18
	return;
19
}
20
21
define( 'STATS_VERSION', '9' );
22
defined( 'STATS_DASHBOARD_SERVER' ) or define( 'STATS_DASHBOARD_SERVER', 'dashboard.wordpress.com' );
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
23
24
add_action( 'jetpack_modules_loaded', 'stats_load' );
25
26
/**
27
 * Load Stats.
28
 *
29
 * @access public
30
 * @return void
31
 */
32
function stats_load() {
33
	Jetpack::enable_module_configurable( __FILE__ );
34
	Jetpack::module_configuration_load( __FILE__, 'stats_configuration_load' );
35
	Jetpack::module_configuration_head( __FILE__, 'stats_configuration_head' );
36
	Jetpack::module_configuration_screen( __FILE__, 'stats_configuration_screen' );
37
38
	// Generate the tracking code after wp() has queried for posts.
39
	add_action( 'template_redirect', 'stats_template_redirect', 1 );
40
41
	add_action( 'wp_head', 'stats_admin_bar_head', 100 );
42
43
	add_action( 'wp_head', 'stats_hide_smile_css' );
44
45
	add_action( 'jetpack_admin_menu', 'stats_admin_menu' );
46
47
	// Map stats caps.
48
	add_filter( 'map_meta_cap', 'stats_map_meta_caps', 10, 3 );
49
50
	if ( isset( $_GET['oldwidget'] ) ) {
51
		// Old one.
52
		add_action( 'wp_dashboard_setup', 'stats_register_dashboard_widget' );
53
	} else {
54
		add_action( 'admin_init', 'stats_merged_widget_admin_init' );
55
	}
56
57
	add_filter( 'jetpack_xmlrpc_methods', 'stats_xmlrpc_methods' );
58
59
	add_filter( 'pre_option_db_version', 'stats_ignore_db_version' );
60
61
	// Add an icon to see stats in WordPress.com for a particular post
62
	add_action( 'admin_print_styles-edit.php', 'jetpack_stats_load_admin_css' );
63
	add_filter( 'manage_posts_columns', 'jetpack_stats_post_table' );
64
	add_filter( 'manage_pages_columns', 'jetpack_stats_post_table' );
65
	add_action( 'manage_posts_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
66
	add_action( 'manage_pages_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
67
}
68
69
/**
70
 * Delay conditional for current_user_can to after init.
71
 *
72
 * @access public
73
 * @return void
74
 */
75
function stats_merged_widget_admin_init() {
76
	if ( current_user_can( 'view_stats' ) ) {
77
		add_action( 'load-index.php', 'stats_enqueue_dashboard_head' );
78
		add_action( 'wp_dashboard_setup', 'stats_register_widget_control_callback' ); // Hacky but works.
79
		add_action( 'jetpack_dashboard_widget', 'stats_jetpack_dashboard_widget' );
80
	}
81
}
82
83
/**
84
 * Enqueue Stats Dashboard
85
 *
86
 * @access public
87
 * @return void
88
 */
89
function stats_enqueue_dashboard_head() {
90
	add_action( 'admin_head', 'stats_dashboard_head' );
91
}
92
93
/**
94
 * Prevent sparkline img requests being redirected to upgrade.php.
95
 * See wp-admin/admin.php where it checks $wp_db_version.
96
 *
97
 * @access public
98
 * @param mixed $version Version.
99
 * @return string $version.
100
 */
101
function stats_ignore_db_version( $version ) {
102
	if (
103
		is_admin() &&
104
		isset( $_GET['page'] ) && 'stats' === $_GET['page'] &&
105
		isset( $_GET['chart'] ) && strpos($_GET['chart'], 'admin-bar-hours') === 0
106
	) {
107
		global $wp_db_version;
108
		return $wp_db_version;
109
	}
110
	return $version;
111
}
112
113
/**
114
 * Maps view_stats cap to read cap as needed.
115
 *
116
 * @access public
117
 * @param mixed $caps Caps.
118
 * @param mixed $cap Cap.
119
 * @param mixed $user_id User ID.
120
 * @return array Possibly mapped capabilities for meta capability.
121
 */
122
function stats_map_meta_caps( $caps, $cap, $user_id ) {
123
	// Map view_stats to exists.
124
	if ( 'view_stats' === $cap ) {
125
		$user        = new WP_User( $user_id );
126
		$user_role   = array_shift( $user->roles );
127
		$stats_roles = stats_get_option( 'roles' );
128
129
		// Is the users role in the available stats roles?
130
		if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles ) ) {
131
			$caps = array( 'read' );
132
		}
133
	}
134
135
	return $caps;
136
}
137
138
/**
139
 * Stats Template Redirect.
140
 *
141
 * @access public
142
 * @return void
143
 */
144
function stats_template_redirect() {
145
	global $current_user, $stats_footer;
146
147
	if ( is_feed() || is_robots() || is_trackback() || is_preview() ) {
148
		return;
149
	}
150
151
	// Should we be counting this user's views?
152
	if ( ! empty( $current_user->ID ) ) {
153
		$count_roles = stats_get_option( 'count_roles' );
154
		if ( ! is_array( $count_roles ) || ! array_intersect( $current_user->roles, $count_roles ) ) {
155
			return;
156
		}
157
	}
158
159
	add_action( 'wp_footer', 'stats_footer', 101 );
160
	add_action( 'wp_head', 'stats_add_shutdown_action' );
161
162
	$script = 'https://stats.wp.com/e-' . gmdate( 'YW' ) . '.js';
163
	$data = stats_build_view_data();
164
	$data_stats_array = stats_array( $data );
165
166
	$stats_footer = <<<END
167
<script type='text/javascript' src='{$script}' async='async' defer='defer'></script>
168
<script type='text/javascript'>
169
	_stq = window._stq || [];
170
	_stq.push([ 'view', {{$data_stats_array}} ]);
171
	_stq.push([ 'clickTrackerInit', '{$data['blog']}', '{$data['post']}' ]);
172
</script>
173
174
END;
175
}
176
177
178
/**
179
 * Stats Build View Data.
180
 *
181
 * @access public
182
 * @return array.
0 ignored issues
show
Documentation introduced by
The doc-type array. could not be parsed: Unknown type name "array." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
183
 */
184
function stats_build_view_data() {
185
	global $wp_the_query;
186
187
	$blog = Jetpack_Options::get_option( 'id' );
188
	$tz = get_option( 'gmt_offset' );
189
	$v = 'ext';
190
	$blog_url = parse_url( site_url() );
191
	$srv = $blog_url['host'];
192
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
193
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
194
		// Store and reset the queried_object and queried_object_id
195
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
196
		// Repro:
197
		// 1. Set home_url = https://ExamPle.com/
198
		// 2. Set show_on_front = page
199
		// 3. Set page_on_front = something
200
		// 4. Visit https://example.com/ !
201
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
202
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
203
		$post = $wp_the_query->get_queried_object_id();
204
		$wp_the_query->queried_object = $queried_object;
205
		$wp_the_query->queried_object_id = $queried_object_id;
206
	} else {
207
		$post = '0';
208
	}
209
210
	return compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
211
}
212
213
/**
214
 * Stats Add Shutdown Action.
215
 *
216
 * @access public
217
 * @return void
218
 */
219
function stats_add_shutdown_action() {
220
	// Just in case wp_footer isn't in your theme.
221
	add_action( 'shutdown',  'stats_footer', 101 );
222
}
223
224
/**
225
 * Stats Footer.
226
 *
227
 * @access public
228
 * @return void
229
 */
230
function stats_footer() {
231
	global $stats_footer;
232
	print $stats_footer;
233
	$stats_footer = '';
234
}
235
236
/**
237
 * Stats Get Options.
238
 *
239
 * @access public
240
 * @return array.
0 ignored issues
show
Documentation introduced by
The doc-type array. could not be parsed: Unknown type name "array." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
241
 */
242
function stats_get_options() {
243
	$options = get_option( 'stats_options' );
244
245
	if ( ! isset( $options['version'] ) || $options['version'] < STATS_VERSION ) {
246
		$options = stats_upgrade_options( $options );
247
	}
248
249
	return $options;
250
}
251
252
/**
253
 * Get Stats Options.
254
 *
255
 * @access public
256
 * @param mixed $option Option.
257
 * @return mixed|null.
0 ignored issues
show
Documentation introduced by
The doc-type mixed|null. could not be parsed: Unknown type name "null." at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
258
 */
259
function stats_get_option( $option ) {
260
	$options = stats_get_options();
261
262
	if ( 'blog_id' === $option ) {
263
		return Jetpack_Options::get_option( 'id' );
264
	}
265
266
	if ( isset( $options[ $option ] ) ) {
267
		return $options[ $option ];
268
	}
269
270
	return null;
271
}
272
273
/**
274
 * Stats Set Options.
275
 *
276
 * @access public
277
 * @param mixed $option Option.
278
 * @param mixed $value Value.
279
 * @return bool.
0 ignored issues
show
Documentation introduced by
The doc-type bool. could not be parsed: Unknown type name "bool." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
280
 */
281
function stats_set_option( $option, $value ) {
282
	$options = stats_get_options();
283
284
	$options[ $option ] = $value;
285
286
	return stats_set_options( $options );
287
}
288
289
/**
290
 * Stats Set Options.
291
 *
292
 * @access public
293
 * @param mixed $options Options.
294
 * @return bool
295
 */
296
function stats_set_options( $options ) {
297
	return update_option( 'stats_options', $options );
298
}
299
300
/**
301
 * Stats Upgrade Options.
302
 *
303
 * @access public
304
 * @param mixed $options Options.
305
 * @return array|bool
306
 */
307
function stats_upgrade_options( $options ) {
308
	$defaults = array(
309
		'admin_bar'    => true,
310
		'roles'        => array( 'administrator' ),
311
		'count_roles'  => array(),
312
		'blog_id'      => Jetpack_Options::get_option( 'id' ),
313
		'do_not_track' => true, // @todo
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
314
		'hide_smile'   => true,
315
	);
316
317
	if ( isset( $options['reg_users'] ) ) {
318
		if ( ! function_exists( 'get_editable_roles' ) ) {
319
			require_once ABSPATH . 'wp-admin/includes/user.php';
320
		}
321
		if ( $options['reg_users'] ) {
322
			$options['count_roles'] = array_keys( get_editable_roles() );
323
		}
324
		unset( $options['reg_users'] );
325
	}
326
327
	if ( is_array( $options ) && ! empty( $options ) ) {
328
		$new_options = array_merge( $defaults, $options );
329
	} else { $new_options = $defaults;
330
	}
331
332
	$new_options['version'] = STATS_VERSION;
333
334
	if ( ! stats_set_options( $new_options ) ) {
335
		return false;
336
	}
337
338
	stats_update_blog();
339
340
	return $new_options;
341
}
342
343
/**
344
 * Stats Array.
345
 *
346
 * @access public
347
 * @param mixed $kvs KVS.
348
 * @return array
349
 */
350
function stats_array( $kvs ) {
351
	/**
352
	 * Filter the options added to the JavaScript Stats tracking code.
353
	 *
354
	 * @module stats
355
	 *
356
	 * @since 1.1.0
357
	 *
358
	 * @param array $kvs Array of options about the site and page you're on.
359
	 */
360
	$kvs = apply_filters( 'stats_array', $kvs );
361
	$kvs = array_map( 'addslashes', $kvs );
362
	foreach ( $kvs as $k => $v ) {
363
		$jskvs[] = "$k:'$v'";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$jskvs was never initialized. Although not strictly required by PHP, it is generally a good practice to add $jskvs = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
364
	}
365
	return join( ',', $jskvs );
0 ignored issues
show
Bug introduced by
The variable $jskvs does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
366
}
367
368
/**
369
 * Admin Pages.
370
 *
371
 * @access public
372
 * @return void
373
 */
374
function stats_admin_menu() {
375
	global $pagenow;
376
377
	// If we're at an old Stats URL, redirect to the new one.
378
	// Don't even bother with caps, menu_page_url(), etc.  Just do it.
379
	if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'stats' === $_GET['page'] ) {
380
		$redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] );
381
		$relative_pos = strpos( $redirect_url, '/wp-admin/' );
382
		if ( false !== $relative_pos ) {
383
			wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
384
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function stats_admin_menu() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
385
		}
386
	}
387
388
	$hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' );
389
	add_action( "load-$hook", 'stats_reports_load' );
390
}
391
392
/**
393
 * Stats Admin Path.
394
 *
395
 * @access public
396
 * @return string
397
 */
398
function stats_admin_path() {
399
	return Jetpack::module_configuration_url( __FILE__ );
400
}
401
402
/**
403
 * Stats Reports Load.
404
 *
405
 * @access public
406
 * @return void
407
 */
408
function stats_reports_load() {
409
	wp_enqueue_script( 'jquery' );
410
	wp_enqueue_script( 'postbox' );
411
	wp_enqueue_script( 'underscore' );
412
413
	add_action( 'admin_print_styles', 'stats_reports_css' );
414
415
	if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) {
416
		$parsed = parse_url( admin_url() );
417
		// Remember user doesn't want JS.
418
		setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days.
419
	}
420
421
	if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) {
422
		// Detect if JS is on.  If so, remove cookie so next page load is via JS.
423
		add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
424
	} else if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) {
425
		// Normal page load.  Load page content via JS.
426
		add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
427
	}
428
}
429
430
/**
431
 * Stats Reports CSS.
432
 *
433
 * @access public
434
 * @return void
435
 */
436
function stats_reports_css() {
437
?>
438
<style type="text/css">
439
#stats-loading-wrap p {
440
	text-align: center;
441
	font-size: 2em;
442
	margin: 7.5em 15px 0 0;
443
	height: 64px;
444
	line-height: 64px;
445
}
446
</style>
447
<?php
448
}
449
450
451
/**
452
 * Detect if JS is on.  If so, remove cookie so next page load is via JS.
453
 *
454
 * @access public
455
 * @return void
456
 */
457
function stats_js_remove_stnojs_cookie() {
458
	$parsed = parse_url( admin_url() );
459
?>
460
<script type="text/javascript">
461
/* <![CDATA[ */
462
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
463
/* ]]> */
464
</script>
465
<?php
466
}
467
468
/**
469
 * Normal page load.  Load page content via JS.
470
 *
471
 * @access public
472
 * @return void
473
 */
474
function stats_js_load_page_via_ajax() {
475
?>
476
<script type="text/javascript">
477
/* <![CDATA[ */
478
if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
479
	jQuery( function( $ ) {
480
		$.get( document.location.href + '&noheader', function( responseText ) {
481
			$( '#stats-loading-wrap' ).replaceWith( responseText );
482
		} );
483
	} );
484
}
485
/* ]]> */
486
</script>
487
<?php
488
}
489
490
/**
491
 * Stats Report Page.
492
 *
493
 * @access public
494
 * @param bool $main_chart_only (default: false) Main Chart Only.
495
 */
496
function stats_reports_page( $main_chart_only = false ) {
497
498
	if ( isset( $_GET['dashboard'] ) ) {
499
		return stats_dashboard_widget_content();
500
	}
501
502
	$blog_id = stats_get_option( 'blog_id' );
503
	$domain = Jetpack::build_raw_urls( get_home_url() );
504
505
	if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) {
506
		$nojs_url = add_query_arg( 'nojs', '1' );
507
		$http = is_ssl() ? 'https' : 'http';
508
		// Loading message. No JS fallback message.
509
?>
510
<div class="wrap">
511
	<h2><?php esc_html_e( 'Site Stats', 'jetpack' ); ?> <?php if ( current_user_can( 'jetpack_manage_modules' ) ) : ?><a style="font-size:13px;" href="<?php echo esc_url( admin_url( 'admin.php?page=jetpack&configure=stats' ) ); ?>"><?php esc_html_e( 'Configure', 'jetpack' ); ?></a><?php endif; ?></h2>
512
</div>
513
<div id="stats-loading-wrap" class="wrap">
514
<p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php
515
		echo esc_url(
516
			/**
517
			 * Sets external resource URL.
518
			 *
519
			 * @module stats
520
			 *
521
			 * @since 1.4.0
522
			 *
523
			 * @param string $args URL of external resource.
524
			 */
525
			apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" )
526
		); ?>" /></p>
527
<p style="font-size: 11pt; margin: 0;"><a href="https://wordpress.com/stats/<?php echo esc_attr( $domain ); ?>" target="_blank"><?php esc_html_e( 'View stats on WordPress.com right now', 'jetpack' ); ?></a></p>
528
<p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
529
<a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without JavaScript', 'jetpack' ); ?></a>.</p>
530
</div>
531
<?php
532
		return;
533
	}
534
535
	$day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false;
536
	$q = array(
537
		'noheader' => 'true',
538
		'proxy' => '',
539
		'page' => 'stats',
540
		'day' => $day,
541
		'blog' => $blog_id,
542
		'charset' => get_option( 'blog_charset' ),
543
		'color' => get_user_option( 'admin_color' ),
544
		'ssl' => is_ssl(),
545
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
546
	);
547
	if ( get_locale() !== 'en_US' ) {
548
		$q['jp_lang'] = get_locale();
549
	}
550
	// Only show the main chart, without extra header data, or metaboxes.
551
	$q['main_chart_only'] = $main_chart_only;
552
	$args = array(
553
		'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),
554
		'numdays' => 'int',
555
		'day' => 'date',
556
		'unit' => array( 1, 7, 31, 'human' ),
557
		'humanize' => array( 'true' ),
558
		'num' => 'int',
559
		'summarize' => null,
560
		'post' => 'int',
561
		'width' => 'int',
562
		'height' => 'int',
563
		'data' => 'data',
564
		'blog_subscribers' => 'int',
565
		'comment_subscribers' => null,
566
		'type' => array( 'wpcom', 'email', 'pending' ),
567
		'pagenum' => 'int',
568
	);
569
	foreach ( $args as $var => $vals ) {
570
		if ( ! isset( $_REQUEST[$var] ) )
571
			continue;
572
		if ( is_array( $vals ) ) {
573
			if ( in_array( $_REQUEST[$var], $vals ) )
574
				$q[$var] = $_REQUEST[$var];
575
		} elseif ( 'int' === $vals ) {
576
			$q[$var] = intval( $_REQUEST[$var] );
577
		} elseif ( 'date' === $vals ) {
578
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) )
579
				$q[$var] = $_REQUEST[$var];
580
		} elseif ( null === $vals ) {
581
			$q[$var] = '';
582
		} elseif ( 'data' === $vals ) {
583
			if ( 'index.php' === substr( $_REQUEST[$var], 0, 9 ) )
584
				$q[$var] = $_REQUEST[$var];
585
		}
586
	}
587
588
	if ( isset( $_GET['chart'] ) ) {
589
		if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) {
590
			$chart = sanitize_title( $_GET['chart'] );
591
			$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
592
		}
593
	} else {
594
		$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
595
	}
596
597
	$url = add_query_arg( $q, $url );
0 ignored issues
show
Bug introduced by
The variable $url does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
598
	$method = 'GET';
599
	$timeout = 90;
600
	$user_id = JETPACK_MASTER_USER; // means send the wp.com user_id
601
602
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
603
	$get_code = wp_remote_retrieve_response_code( $get );
604
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
605
		stats_print_wp_remote_error( $get, $url );
606
	} else {
607
		if ( ! empty( $get['headers']['content-type'] ) ) {
608
			$type = $get['headers']['content-type'];
609
			if ( substr( $type, 0, 5 ) === 'image' ) {
610
				$img = $get['body'];
611
				header( 'Content-Type: ' . $type );
612
				header( 'Content-Length: ' . strlen( $img ) );
613
				echo $img;
614
				die();
0 ignored issues
show
Coding Style Compatibility introduced by
The function stats_reports_page() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
615
			}
616
		}
617
		$body = stats_convert_post_titles( $get['body'] );
618
		$body = stats_convert_chart_urls( $body );
619
		$body = stats_convert_image_urls( $body );
620
		$body = stats_convert_admin_urls( $body );
621
		echo $body;
622
	}
623
624
	if ( isset( $_GET['page'] ) && 'stats' === $_GET['page'] && ! isset( $_GET['chart'] ) ) {
625
		JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_stats' ) );
626
	}
627
628
	if ( isset( $_GET['noheader'] ) ) {
629
		die;
0 ignored issues
show
Coding Style Compatibility introduced by
The function stats_reports_page() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
630
	}
631
}
632
633
/**
634
 * Stats Convert Admin Urls.
635
 *
636
 * @access public
637
 * @param mixed $html HTML.
638
 * @return string
639
 */
640
function stats_convert_admin_urls( $html ) {
641
	return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
642
}
643
644
/**
645
 * Stats Convert Image URLs.
646
 *
647
 * @access public
648
 * @param mixed $html HTML.
649
 * @return string
650
 */
651
function stats_convert_image_urls( $html ) {
652
	$url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER );
653
	$html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html );
654
	return $html;
655
}
656
657
/**
658
 * Callback for preg_replace_callback used in stats_convert_chart_urls()
659
 *
660
 * @since 5.6.0
661
 *
662
 * @param  array  $matches The matches resulting from the preg_replace_callback call.
663
 * @return string          The admin url for the chart.
664
 */
665
function jetpack_stats_convert_chart_urls_callback( $matches ) {
666
	// If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string.
667
	return 'admin.php?page=stats&noheader&chart=' . $matches[1] . str_replace( '?', '&', $matches[2] );
668
}
669
670
/**
671
 * Stats Convert Chart URLs.
672
 *
673
 * @access public
674
 * @param mixed $html HTML.
675
 * @return string
676
 */
677
function stats_convert_chart_urls( $html ) {
678
	$html = preg_replace_callback(
679
		'|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
680
		'jetpack_stats_convert_chart_urls_callback',
681
		$html
682
	);
683
	return $html;
684
}
685
686
/**
687
 * Stats Convert Post Title HTML
688
 *
689
 * @access public
690
 * @param mixed $html HTML.
691
 * @return string
692
 */
693
function stats_convert_post_titles( $html ) {
694
	global $stats_posts;
695
	$pattern = "<span class='post-(\d+)-link'>.*?</span>";
696
	if ( ! preg_match_all( "!$pattern!", $html, $matches ) ) {
697
		return $html;
698
	}
699
	$posts = get_posts(
700
		array(
701
			'include' => implode( ',', $matches[1] ),
702
			'post_type' => 'any',
703
			'post_status' => 'any',
704
			'numberposts' => -1,
705
			'suppress_filters' => false,
706
		)
707
	);
708
	foreach ( $posts as $post ) {
709
		$stats_posts[ $post->ID ] = $post;
710
	}
711
	$html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html );
712
	return $html;
713
}
714
715
/**
716
 * Stats Convert Post Title Matches.
717
 *
718
 * @access public
719
 * @param mixed $matches Matches.
720
 * @return string
721
 */
722
function stats_convert_post_title( $matches ) {
723
	global $stats_posts;
724
	$post_id = $matches[1];
725
	if ( isset( $stats_posts[$post_id] ) )
726
		return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>';
727
	return $matches[0];
728
}
729
730
/**
731
 * Stats Configuration Load.
732
 *
733
 * @access public
734
 * @return void
735
 */
736
function stats_configuration_load() {
737
	if ( isset( $_POST['action'] ) && 'save_options' === $_POST['action'] && $_POST['_wpnonce'] === wp_create_nonce( 'stats' ) ) {
738
		$options = stats_get_options();
739
		$options['admin_bar']  = isset( $_POST['admin_bar']  ) && $_POST['admin_bar'];
740
		$options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile'];
741
742
		$options['roles'] = array( 'administrator' );
743 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
744
			if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] ) {
745
				$options['roles'][] = $role;
746
			}
747
		}
748
749
		$options['count_roles'] = array();
750 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
751
			if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] ) {
752
				$options['count_roles'][] = $role;
753
			}
754
		}
755
756
		stats_set_options( $options );
757
		stats_update_blog();
758
		Jetpack::state( 'message', 'module_configured' );
759
		wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) );
760
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function stats_configuration_load() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
761
	}
762
}
763
764
/**
765
 * Stats Configuration Head.
766
 *
767
 * @access public
768
 * @return void
769
 */
770
function stats_configuration_head() {
771
?>
772
	<style type="text/css">
773
		#statserror {
774
			border: 1px solid #766;
775
			background-color: #d22;
776
			padding: 1em 3em;
777
		}
778
		.stats-smiley {
779
			vertical-align: 1px;
780
		}
781
	</style>
782
	<?php
783
}
784
785
/**
786
 * Stats Configuration Screen.
787
 *
788
 * @access public
789
 * @return void
790
 */
791
function stats_configuration_screen() {
792
	$options = stats_get_options();
793
?>
794
	<div class="narrow">
795
		<p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p>
796
		<form method="post">
797
		<input type='hidden' name='action' value='save_options' />
798
		<?php wp_nonce_field( 'stats' ); ?>
799
		<table id="menu" class="form-table">
800
		<tr valign="top"><th scope="row"><label for="admin_bar"><?php esc_html_e( 'Admin bar' , 'jetpack' ); ?></label></th>
801
		<td><label><input type='checkbox'<?php checked( $options['admin_bar'] ); ?> name='admin_bar' id='admin_bar' /> <?php esc_html_e( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ); ?></label></td></tr>
802
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Registered users', 'jetpack' ); ?></th>
803
		<td>
804
			<?php esc_html_e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/>
805
			<?php
806
	$count_roles = stats_get_option( 'count_roles' );
807
	foreach ( get_editable_roles() as $role => $details ) {
808
?>
809
				<label><input type='checkbox' name='count_role_<?php echo $role; ?>'<?php checked( in_array( $role, $count_roles ) ); ?> /> <?php echo translate_user_role( $details['name'] ); ?></label><br/>
810
				<?php
811
	}
812
?>
813
		</td></tr>
814
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Smiley' , 'jetpack' ); ?></th>
815
		<td><label><input type='checkbox'<?php checked( isset( $options['hide_smile'] ) && $options['hide_smile'] ); ?> name='hide_smile' id='hide_smile' /> <?php esc_html_e( 'Hide the stats smiley face image.', 'jetpack' ); ?></label><br /> <span class="description"><?php echo wp_kses( __( 'The image helps collect stats and <strong>makes the world a better place</strong> but should still work when hidden', 'jetpack' ), array( 'strong' => array() ) ); ?> <img class="stats-smiley" alt="<?php esc_attr_e( 'Smiley face', 'jetpack' ); ?>" src="<?php echo esc_url( plugins_url( 'images/stats-smiley.gif', dirname( __FILE__ ) ) ); ?>" width="6" height="5" /></span></td></tr>
816
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Report visibility' , 'jetpack' ); ?></th>
817
		<td>
818
			<?php esc_html_e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/>
819
			<?php
820
	$stats_roles = stats_get_option( 'roles' );
821
	foreach ( get_editable_roles() as $role => $details ) {
822
?>
823
				<label><input type='checkbox' <?php if ( 'administrator' === $role ) echo "disabled='disabled' "; ?>name='role_<?php echo $role; ?>'<?php checked( 'administrator' === $role || in_array( $role, $stats_roles ) ); ?> /> <?php echo translate_user_role( $details['name'] ); ?></label><br/>
824
				<?php
825
	}
826
?>
827
		</td></tr>
828
		</table>
829
		<p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p>
830
		</form>
831
	</div>
832
	<?php
833
}
834
835
/**
836
 * Stats Hide Smile.
837
 *
838
 * @access public
839
 * @return void
840
 */
841
function stats_hide_smile_css() {
842
	$options = stats_get_options();
843
	if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) {
844
?>
845
<style type='text/css'>img#wpstats{display:none}</style><?php
846
	}
847
}
848
849
/**
850
 * Stats Admin Bar Head.
851
 *
852
 * @access public
853
 * @return void
854
 */
855
function stats_admin_bar_head() {
856
	if ( ! stats_get_option( 'admin_bar' ) )
857
		return;
858
859
	if ( ! current_user_can( 'view_stats' ) )
860
		return;
861
862
	if ( function_exists( 'is_admin_bar_showing' ) && ! is_admin_bar_showing() ) {
863
		return;
864
	}
865
866
	add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 );
867
?>
868
869
<style type='text/css'>
870
#wpadminbar .quicklinks li#wp-admin-bar-stats {
871
	height: 32px;
872
}
873
#wpadminbar .quicklinks li#wp-admin-bar-stats a {
874
	height: 32px;
875
	padding: 0;
876
}
877
#wpadminbar .quicklinks li#wp-admin-bar-stats a div {
878
	height: 32px;
879
	width: 95px;
880
	overflow: hidden;
881
	margin: 0 10px;
882
}
883
#wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
884
	width: auto;
885
	margin: 0 8px 0 10px;
886
}
887
#wpadminbar .quicklinks li#wp-admin-bar-stats a img {
888
	height: 24px;
889
	padding: 4px 0;
890
	max-width: none;
891
	border: none;
892
}
893
</style>
894
<?php
895
}
896
897
/**
898
 * Stats AdminBar.
899
 *
900
 * @access public
901
 * @param mixed $wp_admin_bar WPAdminBar.
902
 * @return void
903
 */
904
function stats_admin_bar_menu( &$wp_admin_bar ) {
905
	$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
906
907
	$img_src = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale' ), $url ) );
908
	$img_src_2x = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale-2x' ), $url ) );
909
910
	$alt = esc_attr( __( 'Stats', 'jetpack' ) );
911
912
	$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
913
914
	$menu = array( 'id' => 'stats', 'title' => "<div><script type='text/javascript'>var src;if(typeof(window.devicePixelRatio)=='undefined'||window.devicePixelRatio<2){src='$img_src';}else{src='$img_src_2x';}document.write('<img src=\''+src+'\' alt=\'$alt\' title=\'$title\' />');</script></div>", 'href' => $url );
915
916
	$wp_admin_bar->add_menu( $menu );
917
}
918
919
/**
920
 * Stats Update Blog.
921
 *
922
 * @access public
923
 * @return void
924
 */
925
function stats_update_blog() {
926
	Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() );
927
}
928
929
/**
930
 * Stats Get Blog.
931
 *
932
 * @access public
933
 * @return string
934
 */
935
function stats_get_blog() {
936
	$home = parse_url( trailingslashit( get_option( 'home' ) ) );
937
	$blog = array(
938
		'host'                => $home['host'],
939
		'path'                => $home['path'],
940
		'blogname'            => get_option( 'blogname' ),
941
		'blogdescription'     => get_option( 'blogdescription' ),
942
		'siteurl'             => get_option( 'siteurl' ),
943
		'gmt_offset'          => get_option( 'gmt_offset' ),
944
		'timezone_string'     => get_option( 'timezone_string' ),
945
		'stats_version'       => STATS_VERSION,
946
		'stats_api'           => 'jetpack',
947
		'page_on_front'       => get_option( 'page_on_front' ),
948
		'permalink_structure' => get_option( 'permalink_structure' ),
949
		'category_base'       => get_option( 'category_base' ),
950
		'tag_base'            => get_option( 'tag_base' ),
951
	);
952
	$blog = array_merge( stats_get_options(), $blog );
953
	unset( $blog['roles'], $blog['blog_id'] );
954
	return stats_esc_html_deep( $blog );
955
}
956
957
/**
958
 * Modified from stripslashes_deep()
959
 *
960
 * @access public
961
 * @param mixed $value Value.
962
 * @return string
963
 */
964
function stats_esc_html_deep( $value ) {
965
	if ( is_array( $value ) ) {
966
		$value = array_map( 'stats_esc_html_deep', $value );
967
	} elseif ( is_object( $value ) ) {
968
		$vars = get_object_vars( $value );
969
		foreach ( $vars as $key => $data ) {
970
			$value->{$key} = stats_esc_html_deep( $data );
971
		}
972
	} elseif ( is_string( $value ) ) {
973
		$value = esc_html( $value );
974
	}
975
976
	return $value;
977
}
978
979
/**
980
 * Stats xmlrpc_methods function.
981
 *
982
 * @access public
983
 * @param mixed $methods Methods.
984
 * @return array
985
 */
986
function stats_xmlrpc_methods( $methods ) {
987
	$my_methods = array(
988
		'jetpack.getBlog' => 'stats_get_blog',
989
	);
990
991
	return array_merge( $methods, $my_methods );
992
}
993
994
/**
995
 * Register Stats Dashboard Widget.
996
 *
997
 * @access public
998
 * @return void
999
 */
1000
function stats_register_dashboard_widget() {
1001
	if ( ! current_user_can( 'view_stats' ) )
1002
		return;
1003
1004
	// With wp_dashboard_empty: we load in the content after the page load via JS.
1005
	wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' );
1006
1007
	add_action( 'admin_head', 'stats_dashboard_head' );
1008
}
1009
1010
/**
1011
 * Stats Dashboard Widget Options.
1012
 *
1013
 * @access public
1014
 * @return array
1015
 */
1016
function stats_dashboard_widget_options() {
1017
	$defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 );
1018
	if ( ( ! $options = get_option( 'stats_dashboard_widget' ) ) || ! is_array( $options ) ) {
1019
		$options = array();
1020
	}
1021
1022
	// Ignore obsolete option values.
1023
	$intervals = array( 1, 7, 31, 90, 365 );
1024
	foreach ( array( 'top', 'search' ) as $key ) {
1025
		if ( isset( $options[ $key ] ) && ! in_array( $options[ $key ], $intervals ) ) {
1026
			unset( $options[ $key ] );
1027
		}
1028
	}
1029
1030
		return array_merge( $defaults, $options );
1031
}
1032
1033
/**
1034
 * Stats Dashboard Widget Control.
1035
 *
1036
 * @access public
1037
 * @return void
1038
 */
1039
function stats_dashboard_widget_control() {
1040
	$periods   = array(
1041
		'1' => __( 'day', 'jetpack' ),
1042
		'7' => __( 'week', 'jetpack' ),
1043
		'31' => __( 'month', 'jetpack' ),
1044
	);
1045
	$intervals = array(
1046
		'1' => __( 'the past day', 'jetpack' ),
1047
		'7' => __( 'the past week', 'jetpack' ),
1048
		'31' => __( 'the past month', 'jetpack' ),
1049
		'90' => __( 'the past quarter', 'jetpack' ),
1050
		'365' => __( 'the past year', 'jetpack' ),
1051
	);
1052
	$defaults = array(
1053
		'top' => 1,
1054
		'search' => 7,
1055
	);
1056
1057
	$options = stats_dashboard_widget_options();
1058
1059
	if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' === $_POST['widget_id'] ) {
1060
		if ( isset( $periods[ $_POST['chart'] ] ) ) {
1061
			$options['chart'] = $_POST['chart'];
1062
		}
1063
		foreach ( array( 'top', 'search' ) as $key ) {
1064
			if ( isset( $intervals[ $_POST[ $key ] ] ) ) {
1065
				$options[ $key ] = $_POST[ $key ];
1066
			} else { $options[ $key ] = $defaults[ $key ];
1067
			}
1068
		}
1069
		update_option( 'stats_dashboard_widget', $options );
1070
	}
1071
?>
1072
	<p>
1073
	<label for="chart"><?php esc_html_e( 'Chart stats by' , 'jetpack' ); ?></label>
1074
	<select id="chart" name="chart">
1075
	<?php
1076
	foreach ( $periods as $val => $label ) {
1077
?>
1078
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
1079
		<?php
1080
	}
1081
?>
1082
	</select>.
1083
	</p>
1084
1085
	<p>
1086
	<label for="top"><?php esc_html_e( 'Show top posts over', 'jetpack' ); ?></label>
1087
	<select id="top" name="top">
1088
	<?php
1089 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1090
?>
1091
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
1092
		<?php
1093
	}
1094
?>
1095
	</select>.
1096
	</p>
1097
1098
	<p>
1099
	<label for="search"><?php esc_html_e( 'Show top search terms over', 'jetpack' ); ?></label>
1100
	<select id="search" name="search">
1101
	<?php
1102 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1103
?>
1104
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
1105
		<?php
1106
	}
1107
?>
1108
	</select>.
1109
	</p>
1110
	<?php
1111
}
1112
1113
/**
1114
 * Jetpack Stats Dashboard Widget.
1115
 *
1116
 * @access public
1117
 * @return void
1118
 */
1119
function stats_jetpack_dashboard_widget() {
1120
?>
1121
	<form id="stats_dashboard_widget_control" action="<?php echo esc_url( admin_url() ); ?>" method="post">
1122
		<?php stats_dashboard_widget_control(); ?>
1123
		<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
1124
		<input type="hidden" name="widget_id" value="dashboard_stats" />
1125
		<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
1126
	</form>
1127
	<span id="js-toggle-stats_dashboard_widget_control">
1128
		<?php esc_html_e( 'Configure', 'jetpack' ); ?>
1129
	</span>
1130
	<div id="dashboard_stats">
1131
		<div class="inside">
1132
			<div style="height: 250px;"></div>
1133
		</div>
1134
	</div>
1135
	<script>
1136
		jQuery(document).ready(function($){
1137
			var $toggle = $('#js-toggle-stats_dashboard_widget_control');
1138
1139
			$toggle.parent().prev().append( $toggle );
1140
			$toggle.show().click(function(e){
1141
				e.preventDefault();
1142
				e.stopImmediatePropagation();
1143
				$(this).parent().toggleClass('controlVisible');
1144
				$('#stats_dashboard_widget_control').slideToggle();
1145
			});
1146
		});
1147
	</script>
1148
	<style>
1149
		#js-toggle-stats_dashboard_widget_control {
1150
			display: none;
1151
			float: right;
1152
			margin-top: 0.2em;
1153
			font-weight: 400;
1154
			color: #444;
1155
			font-size: .8em;
1156
			text-decoration: underline;
1157
			cursor: pointer;
1158
		}
1159
		#stats_dashboard_widget_control {
1160
			display: none;
1161
			padding: 0 10px;
1162
			overflow: hidden;
1163
		}
1164
		#stats_dashboard_widget_control .button-primary {
1165
			float: right;
1166
		}
1167
		#dashboard_stats {
1168
			box-sizing: border-box;
1169
			width: 100%;
1170
			padding: 0 10px;
1171
		}
1172
	</style>
1173
	<?php
1174
}
1175
1176
/**
1177
 * Register Stats Widget Control Callback.
1178
 *
1179
 * @access public
1180
 * @return void
1181
 */
1182
function stats_register_widget_control_callback() {
1183
	$GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control';
1184
}
1185
1186
/**
1187
 * JavaScript and CSS for dashboard widget.
1188
 *
1189
 * @access public
1190
 * @return void
1191
 */
1192
function stats_dashboard_head() { ?>
1193
<script type="text/javascript">
1194
/* <![CDATA[ */
1195
jQuery( function($) {
1196
	var dashStats = jQuery( '#dashboard_stats div.inside' );
1197
1198
	if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
1199
		return;
1200
	}
1201
1202
	if ( ! dashStats.length ) {
1203
		dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
1204
		var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
1205
		var args = 'width=' + dashStats.width() + '&height=' + h.toString();
1206
	} else {
1207
		if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
1208
			var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
1209
		} else {
1210
			var args = 'width=' + ( dashStats.width() * 2 ).toString();
1211
		}
1212
	}
1213
1214
	dashStats
1215
		.not( '.dashboard-widget-control' )
1216
		.load( 'admin.php?page=stats&noheader&dashboard&' + args );
1217
1218
	jQuery( window ).one( 'resize', function() {
1219
		jQuery( '#stat-chart' ).css( 'width', 'auto' );
1220
	} );
1221
} );
1222
/* ]]> */
1223
</script>
1224
<style type="text/css">
1225
/* <![CDATA[ */
1226
#stat-chart {
1227
	background: none !important;
1228
}
1229
#dashboard_stats .inside {
1230
	margin: 10px 0 0 0 !important;
1231
}
1232
#dashboard_stats #stats-graph {
1233
	margin: 0;
1234
}
1235
#stats-info {
1236
	border-top: 1px solid #dfdfdf;
1237
	margin: 7px -10px 0 -10px;
1238
	padding: 10px;
1239
	background: #fcfcfc;
1240
	-moz-box-shadow:inset 0 1px 0 #fff;
1241
	-webkit-box-shadow:inset 0 1px 0 #fff;
1242
	box-shadow:inset 0 1px 0 #fff;
1243
	overflow: hidden;
1244
	border-radius: 0 0 2px 2px;
1245
	-webkit-border-radius: 0 0 2px 2px;
1246
	-moz-border-radius: 0 0 2px 2px;
1247
	-khtml-border-radius: 0 0 2px 2px;
1248
}
1249
#stats-info #top-posts, #stats-info #top-search {
1250
	float: left;
1251
	width: 50%;
1252
}
1253
#stats-info #top-posts {
1254
	padding-right: 3%;
1255
}
1256
#top-posts .stats-section-inner p {
1257
	white-space: nowrap;
1258
	overflow: hidden;
1259
}
1260
#top-posts .stats-section-inner p a {
1261
	overflow: hidden;
1262
	text-overflow: ellipsis;
1263
}
1264
#stats-info div#active {
1265
	border-top: 1px solid #dfdfdf;
1266
	margin: 0 -10px;
1267
	padding: 10px 10px 0 10px;
1268
	-moz-box-shadow:inset 0 1px 0 #fff;
1269
	-webkit-box-shadow:inset 0 1px 0 #fff;
1270
	box-shadow:inset 0 1px 0 #fff;
1271
	overflow: hidden;
1272
}
1273
#top-search p {
1274
	color: #999;
1275
}
1276
#stats-info h3 {
1277
	font-size: 1em;
1278
	margin: 0 0 .5em 0 !important;
1279
}
1280
#stats-info p {
1281
	margin: 0 0 .25em;
1282
	color: #999;
1283
}
1284
#stats-info p.widget-loading {
1285
	margin: 1em 0 0;
1286
	color: #333;
1287
}
1288
#stats-info p a {
1289
	display: block;
1290
}
1291
#stats-info p a.button {
1292
	display: inline;
1293
}
1294
/* ]]> */
1295
</style>
1296
<?php
1297
}
1298
1299
/**
1300
 * Stats Dashboard Widget Content.
1301
 *
1302
 * @access public
1303
 * @return void
1304
 */
1305
function stats_dashboard_widget_content() {
1306
	if ( ! isset( $_GET['width'] ) || ( ! $width = (int) ( $_GET['width'] / 2 ) ) || $width < 250 ) {
1307
		$width = 370;
1308
	}
1309
	if ( ! isset( $_GET['height'] ) || ( ! $height = (int) $_GET['height'] - 36 ) || $height < 230 ) {
1310
		$height = 180;
1311
	}
1312
1313
	$_width  = $width  - 5;
1314
	$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // Hack!
1315
1316
	$options = stats_dashboard_widget_options();
1317
	$blog_id = Jetpack_Options::get_option( 'id' );
1318
1319
	$q = array(
1320
		'noheader' => 'true',
1321
		'proxy' => '',
1322
		'blog' => $blog_id,
1323
		'page' => 'stats',
1324
		'chart' => '',
1325
		'unit' => $options['chart'],
1326
		'color' => get_user_option( 'admin_color' ),
1327
		'width' => $_width,
1328
		'height' => $_height,
1329
		'ssl' => is_ssl(),
1330
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
1331
	);
1332
1333
	$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
1334
1335
	$url = add_query_arg( $q, $url );
1336
	$method = 'GET';
1337
	$timeout = 90;
1338
	$user_id = JETPACK_MASTER_USER;
1339
1340
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1341
	$get_code = wp_remote_retrieve_response_code( $get );
1342
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1343
		stats_print_wp_remote_error( $get, $url );
1344
	} else {
1345
		$body = stats_convert_post_titles( $get['body'] );
1346
		$body = stats_convert_chart_urls( $body );
1347
		$body = stats_convert_image_urls( $body );
1348
		echo $body;
1349
	}
1350
1351
	$post_ids = array();
1352
1353
	$csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) );
1354
	$csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" );
1355
	/* Translators: Stats dashboard widget postviews list: "$post_title $views Views". */
1356
	$printf = __( '%1$s %2$s Views' , 'jetpack' );
1357
1358
	foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) {
1359
		if ( 0 === $post['post_id'] ) {
1360
			unset( $top_posts[$i] );
1361
			continue;
1362
		}
1363
		$post_ids[] = $post['post_id'];
1364
	}
1365
1366
	// Cache.
1367
	get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
1368
1369
	$searches = array();
1370
	foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) {
1371
		if ( 'encrypted_search_terms' === $search_term['searchterm'] ) {
1372
			continue;
1373
		}
1374
		$searches[] = esc_html( $search_term['searchterm'] );
1375
	}
1376
1377
?>
1378
<a class="button" href="admin.php?page=stats"><?php  esc_html_e( 'View All', 'jetpack' ); ?></a>
1379
<div id="stats-info">
1380
	<div id="top-posts" class='stats-section'>
1381
		<div class="stats-section-inner">
1382
		<h3 class="heading"><?php  esc_html_e( 'Top Posts' , 'jetpack' ); ?></h3>
1383
		<?php
1384
	if ( empty( $top_posts ) ) {
1385
?>
1386
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1387
			<?php
1388
	} else {
1389
		foreach ( $top_posts as $post ) {
1390
			if ( ! get_post( $post['post_id'] ) ) {
1391
				continue;
1392
			}
1393
?>
1394
				<p><?php printf(
1395
				$printf,
1396
				'<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',
1397
				number_format_i18n( $post['views'] )
1398
			); ?></p>
1399
				<?php
1400
		}
1401
	}
1402
?>
1403
		</div>
1404
	</div>
1405
	<div id="top-search" class='stats-section'>
1406
		<div class="stats-section-inner">
1407
		<h3 class="heading"><?php  esc_html_e( 'Top Searches' , 'jetpack' ); ?></h3>
1408
		<?php
1409
	if ( empty( $searches ) ) {
1410
?>
1411
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1412
			<?php
1413
	} else {
1414
?>
1415
			<p><?php echo join( ',&nbsp; ', $searches );?></p>
1416
			<?php
1417
	}
1418
?>
1419
		</div>
1420
	</div>
1421
</div>
1422
<div class="clear"></div>
1423
<?php
1424
	exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function stats_dashboard_widget_content() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1425
}
1426
1427
/**
1428
 * Stats Print WP Remote Error.
1429
 *
1430
 * @access public
1431
 * @param mixed $get Get.
1432
 * @param mixed $url URL.
1433
 * @return void
1434
 */
1435
function stats_print_wp_remote_error( $get, $url ) {
1436
	$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1437
	$previous_error = Jetpack::state( $state_name );
1438
	$error = md5( serialize( compact( 'get', 'url' ) ) );
1439
	Jetpack::state( $state_name, $error );
1440
	if ( $error !== $previous_error ) {
1441
?>
1442
	<div class="wrap">
1443
	<p><?php esc_html_e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
1444
	</div>
1445
<?php
1446
		return;
1447
	}
1448
?>
1449
	<div class="wrap">
1450
	<p><?php printf( __( 'We were unable to get your stats just now. Please reload this page to try again. If this error persists, please <a href="%1$s" target="_blank">contact support</a>. In your report please include the information below.', 'jetpack' ), 'https://support.wordpress.com/contact/?jetpack=needs-service' ); ?></p>
1451
	<pre>
1452
	User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
1453
	Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
1454
	API URL: "<?php echo esc_url( $url ); ?>"
1455
<?php
1456
if ( is_wp_error( $get ) ) {
1457
	foreach ( $get->get_error_codes() as $code ) {
1458
		foreach ( $get->get_error_messages( $code ) as $message ) {
1459
?>
1460
<?php print $code . ': "' . $message . '"' ?>
1461
1462
<?php
1463
		}
1464
	}
1465
} else {
1466
	$get_code = wp_remote_retrieve_response_code( $get );
1467
	$content_length = strlen( wp_remote_retrieve_body( $get ) );
1468
?>
1469
Response code: "<?php print $get_code ?>"
1470
Content length: "<?php print $content_length ?>"
1471
1472
<?php
1473
}
1474
	?></pre>
1475
	</div>
1476
	<?php
1477
}
1478
1479
/**
1480
 * Get stats from WordPress.com
1481
 *
1482
 * @param string $table The stats which you want to retrieve: postviews, or searchterms.
1483
 * @param array  $args {
0 ignored issues
show
Documentation introduced by
Should the type for parameter $args not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1484
 *      An associative array of arguments.
1485
 *
1486
 *      @type bool    $end        The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
1487
 *                                and default timezone is UTC date. Default value is Now.
1488
 *      @type string  $days       The length of the desired time frame. Default is 30. Maximum 90 days.
1489
 *      @type int     $limit      The maximum number of records to return. Default is 10. Maximum 100.
1490
 *      @type int     $post_id    The ID of the post to retrieve stats data for
1491
 *      @type string  $summarize  If present, summarizes all matching records. Default Null.
1492
 *
1493
 * }
1494
 *
1495
 * @return array {
1496
 *      An array of post view data, each post as an array
1497
 *
1498
 *      array {
1499
 *          The post view data for a single post
1500
 *
1501
 *          @type string  $post_id         The ID of the post
1502
 *          @type string  $post_title      The title of the post
1503
 *          @type string  $post_permalink  The permalink for the post
1504
 *          @type string  $views           The number of views for the post within the $num_days specified
1505
 *      }
1506
 * }
1507
 */
1508
function stats_get_csv( $table, $args = null ) {
1509
	$defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' );
1510
1511
	$args = wp_parse_args( $args, $defaults );
1512
	$args['table'] = $table;
1513
	$args['blog_id'] = Jetpack_Options::get_option( 'id' );
1514
1515
	$stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
1516
1517
	$key = md5( $stats_csv_url );
1518
1519
	// Get cache.
1520
	$stats_cache = get_option( 'stats_cache' );
1521
	if ( ! $stats_cache || ! is_array( $stats_cache ) ) {
1522
		$stats_cache = array();
1523
	}
1524
1525
	// Return or expire this key.
1526
	if ( isset( $stats_cache[ $key ] ) ) {
1527
		$time = key( $stats_cache[ $key ] );
1528
		if ( time() - $time < 300 ) {
1529
			return $stats_cache[ $key ][ $time ];
1530
		}
1531
		unset( $stats_cache[ $key ] );
1532
	}
1533
1534
	$stats_rows = array();
1535
	do {
1536
		if ( ! $stats = stats_get_remote_csv( $stats_csv_url ) ) {
1537
			break;
1538
		}
1539
1540
		$labels = array_shift( $stats );
1541
1542
		if ( 0 === stripos( $labels[0], 'error' ) ) {
1543
			break;
1544
		}
1545
1546
		$stats_rows = array();
1547
		for ( $s = 0; isset( $stats[ $s ] ); $s++ ) {
1548
			$row = array();
1549
			foreach ( $labels as $col => $label ) {
1550
				$row[ $label ] = $stats[ $s ][ $col ];
1551
			}
1552
			$stats_rows[] = $row;
1553
		}
1554
	} while ( 0 );
1555
1556
	// Expire old keys.
1557 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1558
		if ( ! is_array( $cache ) || 300 < time() - key( $cache ) ) {
1559
			unset( $stats_cache[ $k ] );
1560
		}
1561
	}
1562
1563
		// Set cache.
1564
		$stats_cache[ $key ] = array( time() => $stats_rows );
1565
	update_option( 'stats_cache', $stats_cache );
1566
1567
	return $stats_rows;
1568
}
1569
1570
/**
1571
 * Stats get remote CSV.
1572
 *
1573
 * @access public
1574
 * @param mixed $url URL.
1575
 * @return array
1576
 */
1577
function stats_get_remote_csv( $url ) {
1578
	$method = 'GET';
1579
	$timeout = 90;
1580
	$user_id = JETPACK_MASTER_USER;
1581
1582
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1583
	$get_code = wp_remote_retrieve_response_code( $get );
1584
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1585
		return array(); // @todo: return an error?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1586
	} else {
1587
		return stats_str_getcsv( $get['body'] );
1588
	}
1589
}
1590
1591
/**
1592
 * Rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it.
1593
 *
1594
 * @access public
1595
 * @param mixed $csv CSV.
1596
 * @return array.
0 ignored issues
show
Documentation introduced by
The doc-type array. could not be parsed: Unknown type name "array." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1597
 */
1598
function stats_str_getcsv( $csv ) {
1599
	if ( function_exists( 'str_getcsv' ) ) {
1600
		$lines = str_getcsv( $csv, "\n" );
1601
		return array_map( 'str_getcsv', $lines );
1602
	}
1603
	if ( ! $temp = tmpfile() ) { // The tmpfile() automatically unlinks.
1604
		return false;
1605
	}
1606
1607
	$data = array();
1608
1609
	fwrite( $temp, $csv, strlen( $csv ) );
1610
	fseek( $temp, 0 );
1611
	while ( false !== $row = fgetcsv( $temp, 2000 ) ) {
1612
		$data[] = $row;
1613
	}
1614
	fclose( $temp );
1615
1616
	return $data;
1617
}
1618
1619
/**
1620
 * Abstract out building the rest api stats path.
1621
 *
1622
 * @param  string $resource Resource.
1623
 * @return string
1624
 */
1625
function jetpack_stats_api_path( $resource = '' ) {
1626
	$resource = ltrim( $resource, '/' );
1627
	return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
1628
}
1629
1630
/**
1631
 * Fetches stats data from the REST API.  Caches locally for 5 minutes.
1632
 *
1633
 * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
1634
 * @access public
1635
 * @param array  $args (default: array())  The args that are passed to the endpoint.
1636
 * @param string $resource (default: '') Optional sub-endpoint following /stats/.
1637
 * @return array|WP_Error.
0 ignored issues
show
Documentation introduced by
The doc-type array|WP_Error. could not be parsed: Unknown type name "WP_Error." at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1638
 */
1639
function stats_get_from_restapi( $args = array(), $resource = '' ) {
1640
	$endpoint    = jetpack_stats_api_path( $resource );
1641
	$api_version = '1.1';
1642
	$args        = wp_parse_args( $args, array() );
1643
	$cache_key   = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) );
1644
1645
	// Get cache.
1646
	$stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() );
1647
	if ( ! is_array( $stats_cache ) ) {
1648
		$stats_cache = array();
1649
	}
1650
1651
	// Return or expire this key.
1652
	if ( isset( $stats_cache[ $cache_key ] ) ) {
1653
		$time = key( $stats_cache[ $cache_key ] );
1654
		if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) {
1655
			$cached_stats = $stats_cache[ $cache_key ][ $time ];
1656
			$cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats );
1657
			return $cached_stats;
1658
		}
1659
		unset( $stats_cache[ $cache_key ] );
1660
	}
1661
1662
	// Do the dirty work.
1663
	$response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
1664
	if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1665
		// If bad, just return it, don't cache.
1666
		return $response;
1667
	}
1668
1669
	$data = json_decode( wp_remote_retrieve_body( $response ) );
1670
1671
	// Expire old keys.
1672 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1673
		if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) {
1674
			unset( $stats_cache[ $k ] );
1675
		}
1676
	}
1677
1678
	// Set cache.
1679
	$stats_cache[ $cache_key ] = array(
1680
		time() => $data,
1681
	);
1682
	Jetpack_Options::update_option( 'restapi_stats_cache', $stats_cache, false );
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1683
1684
	return $data;
1685
}
1686
1687
/**
1688
 * Load CSS needed for Stats column width in WP-Admin area.
1689
 *
1690
 * @since 4.7.0
1691
 */
1692
function jetpack_stats_load_admin_css() {
1693
	?>
1694
	<style type="text/css">
1695
		.fixed .column-stats {
1696
			width: 5em;
1697
		}
1698
	</style>
1699
	<?php
1700
}
1701
1702
/**
1703
 * Set header for column that allows to go to WordPress.com to see an entry's stats.
1704
 *
1705
 * @param array $columns An array of column names.
1706
 *
1707
 * @since 4.7.0
1708
 *
1709
 * @return mixed
1710
 */
1711
function jetpack_stats_post_table( $columns ) { // Adds a stats link on the edit posts page
1712
	if ( ! current_user_can( 'view_stats' ) || ! Jetpack::is_user_connected() ) {
1713
		return $columns;
1714
	}
1715
	// Array-Fu to add before comments
1716
	$pos = array_search( 'comments', array_keys( $columns ) );
1717
	if ( ! is_int( $pos ) ) {
1718
		return $columns;
1719
	}
1720
	$chunks             = array_chunk( $columns, $pos, true );
1721
	$chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
1722
1723
	return call_user_func_array( 'array_merge', $chunks );
1724
}
1725
1726
/**
1727
 * Set content for cell with link to an entry's stats in WordPress.com.
1728
 *
1729
 * @param string $column  The name of the column to display.
1730
 * @param int    $post_id The current post ID.
1731
 *
1732
 * @since 4.7.0
1733
 *
1734
 * @return mixed
1735
 */
1736
function jetpack_stats_post_table_cell( $column, $post_id ) {
1737
	if ( 'stats' == $column ) {
1738
		if ( 'publish' != get_post_status( $post_id ) ) {
1739
			printf(
1740
				'<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
1741
				esc_html__( 'No stats', 'jetpack' )
1742
			);
1743
		} else {
1744
			printf(
1745
				'<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
1746
				esc_url( "https://wordpress.com/stats/post/$post_id/" . Jetpack::build_raw_urls( get_home_url() ) ),
1747
				esc_html__( 'View stats for this post in WordPress.com', 'jetpack' )
1748
			);
1749
		}
1750
	}
1751
}
1752