Completed
Push — fix/visibility-condition-issue... ( eee6fd...a71c15 )
by
unknown
11:07
created

stats.php ➔ jetpack_stats_post_table()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 3
nop 1
dl 0
loc 14
rs 9.2
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 ( ! 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 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
 * Stats Convert Chart URLs.
659
 *
660
 * @access public
661
 * @param mixed $html HTML.
662
 * @return string
663
 */
664
function stats_convert_chart_urls( $html ) {
665
	$html = preg_replace_callback( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
666
		create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
667
			'$matches',
668
			// If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string.
669
			'return "admin.php?page=stats&noheader&chart=" . $matches[1] . str_replace( "?", "&", $matches[2] );'
670
		),
671
		$html );
672
	return $html;
673
}
674
675
/**
676
 * Stats Convert Post Title HTML
677
 *
678
 * @access public
679
 * @param mixed $html HTML.
680
 * @return string
681
 */
682
function stats_convert_post_titles( $html ) {
683
	global $stats_posts;
684
	$pattern = "<span class='post-(\d+)-link'>.*?</span>";
685
	if ( ! preg_match_all( "!$pattern!", $html, $matches ) ) {
686
		return $html;
687
	}
688
	$posts = get_posts(
689
		array(
690
			'include' => implode( ',', $matches[1] ),
691
			'post_type' => 'any',
692
			'post_status' => 'any',
693
			'numberposts' => -1,
694
		)
695
	);
696
	foreach ( $posts as $post ) {
697
		$stats_posts[ $post->ID ] = $post;
698
	}
699
	$html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html );
700
	return $html;
701
}
702
703
/**
704
 * Stats Convert Post Title Matches.
705
 *
706
 * @access public
707
 * @param mixed $matches Matches.
708
 * @return string
709
 */
710
function stats_convert_post_title( $matches ) {
711
	global $stats_posts;
712
	$post_id = $matches[1];
713
	if ( isset( $stats_posts[$post_id] ) )
714
		return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>';
715
	return $matches[0];
716
}
717
718
/**
719
 * Stats Configuration Load.
720
 *
721
 * @access public
722
 * @return void
723
 */
724
function stats_configuration_load() {
725
	if ( isset( $_POST['action'] ) && 'save_options' === $_POST['action'] && $_POST['_wpnonce'] === wp_create_nonce( 'stats' ) ) {
726
		$options = stats_get_options();
727
		$options['admin_bar']  = isset( $_POST['admin_bar']  ) && $_POST['admin_bar'];
728
		$options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile'];
729
730
		$options['roles'] = array( 'administrator' );
731 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
732
			if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] ) {
733
				$options['roles'][] = $role;
734
			}
735
		}
736
737
		$options['count_roles'] = array();
738 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
739
			if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] ) {
740
				$options['count_roles'][] = $role;
741
			}
742
		}
743
744
		stats_set_options( $options );
745
		stats_update_blog();
746
		Jetpack::state( 'message', 'module_configured' );
747
		wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) );
748
		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...
749
	}
750
}
751
752
/**
753
 * Stats Configuration Head.
754
 *
755
 * @access public
756
 * @return void
757
 */
758
function stats_configuration_head() {
759
?>
760
	<style type="text/css">
761
		#statserror {
762
			border: 1px solid #766;
763
			background-color: #d22;
764
			padding: 1em 3em;
765
		}
766
		.stats-smiley {
767
			vertical-align: 1px;
768
		}
769
	</style>
770
	<?php
771
}
772
773
/**
774
 * Stats Configuration Screen.
775
 *
776
 * @access public
777
 * @return void
778
 */
779
function stats_configuration_screen() {
780
	$options = stats_get_options();
781
?>
782
	<div class="narrow">
783
		<p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p>
784
		<form method="post">
785
		<input type='hidden' name='action' value='save_options' />
786
		<?php wp_nonce_field( 'stats' ); ?>
787
		<table id="menu" class="form-table">
788
		<tr valign="top"><th scope="row"><label for="admin_bar"><?php esc_html_e( 'Admin bar' , 'jetpack' ); ?></label></th>
789
		<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>
790
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Registered users', 'jetpack' ); ?></th>
791
		<td>
792
			<?php esc_html_e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/>
793
			<?php
794
	$count_roles = stats_get_option( 'count_roles' );
795
	foreach ( get_editable_roles() as $role => $details ) {
796
?>
797
				<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/>
798
				<?php
799
	}
800
?>
801
		</td></tr>
802
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Smiley' , 'jetpack' ); ?></th>
803
		<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 esc_html_e( 'The image helps collect stats and <strong>makes the world a better place</strong> but should still work when hidden', 'jetpack' ); ?> <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>
804
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Report visibility' , 'jetpack' ); ?></th>
805
		<td>
806
			<?php esc_html_e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/>
807
			<?php
808
	$stats_roles = stats_get_option( 'roles' );
809
	foreach ( get_editable_roles() as $role => $details ) {
810
?>
811
				<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/>
812
				<?php
813
	}
814
?>
815
		</td></tr>
816
		</table>
817
		<p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p>
818
		</form>
819
	</div>
820
	<?php
821
}
822
823
/**
824
 * Stats Hide Smile.
825
 *
826
 * @access public
827
 * @return void
828
 */
829
function stats_hide_smile_css() {
830
	$options = stats_get_options();
831
	if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) {
832
?>
833
<style type='text/css'>img#wpstats{display:none}</style><?php
834
	}
835
}
836
837
/**
838
 * Stats Admin Bar Head.
839
 *
840
 * @access public
841
 * @return void
842
 */
843
function stats_admin_bar_head() {
844
	if ( ! stats_get_option( 'admin_bar' ) )
845
		return;
846
847
	if ( ! current_user_can( 'view_stats' ) )
848
		return;
849
850
	if ( function_exists( 'is_admin_bar_showing' ) && ! is_admin_bar_showing() ) {
851
		return;
852
	}
853
854
	add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 );
855
?>
856
857
<style type='text/css'>
858
#wpadminbar .quicklinks li#wp-admin-bar-stats {
859
	height: 32px;
860
}
861
#wpadminbar .quicklinks li#wp-admin-bar-stats a {
862
	height: 32px;
863
	padding: 0;
864
}
865
#wpadminbar .quicklinks li#wp-admin-bar-stats a div {
866
	height: 32px;
867
	width: 95px;
868
	overflow: hidden;
869
	margin: 0 10px;
870
}
871
#wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
872
	width: auto;
873
	margin: 0 8px 0 10px;
874
}
875
#wpadminbar .quicklinks li#wp-admin-bar-stats a img {
876
	height: 24px;
877
	padding: 4px 0;
878
	max-width: none;
879
	border: none;
880
}
881
</style>
882
<?php
883
}
884
885
/**
886
 * Stats AdminBar.
887
 *
888
 * @access public
889
 * @param mixed $wp_admin_bar WPAdminBar.
890
 * @return void
891
 */
892
function stats_admin_bar_menu( &$wp_admin_bar ) {
893
	$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
894
895
	$img_src = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale' ), $url ) );
896
	$img_src_2x = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale-2x' ), $url ) );
897
898
	$alt = esc_attr( __( 'Stats', 'jetpack' ) );
899
900
	$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
901
902
	$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 );
903
904
	$wp_admin_bar->add_menu( $menu );
905
}
906
907
/**
908
 * Stats Update Blog.
909
 *
910
 * @access public
911
 * @return void
912
 */
913
function stats_update_blog() {
914
	Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() );
915
}
916
917
/**
918
 * Stats Get Blog.
919
 *
920
 * @access public
921
 * @return string
922
 */
923
function stats_get_blog() {
924
	$home = parse_url( trailingslashit( get_option( 'home' ) ) );
925
	$blog = array(
926
		'host'                => $home['host'],
927
		'path'                => $home['path'],
928
		'blogname'            => get_option( 'blogname' ),
929
		'blogdescription'     => get_option( 'blogdescription' ),
930
		'siteurl'             => get_option( 'siteurl' ),
931
		'gmt_offset'          => get_option( 'gmt_offset' ),
932
		'timezone_string'     => get_option( 'timezone_string' ),
933
		'stats_version'       => STATS_VERSION,
934
		'stats_api'           => 'jetpack',
935
		'page_on_front'       => get_option( 'page_on_front' ),
936
		'permalink_structure' => get_option( 'permalink_structure' ),
937
		'category_base'       => get_option( 'category_base' ),
938
		'tag_base'            => get_option( 'tag_base' ),
939
	);
940
	$blog = array_merge( stats_get_options(), $blog );
941
	unset( $blog['roles'], $blog['blog_id'] );
942
	return stats_esc_html_deep( $blog );
943
}
944
945
/**
946
 * Modified from stripslashes_deep()
947
 *
948
 * @access public
949
 * @param mixed $value Value.
950
 * @return string
951
 */
952
function stats_esc_html_deep( $value ) {
953
	if ( is_array( $value ) ) {
954
		$value = array_map( 'stats_esc_html_deep', $value );
955
	} elseif ( is_object( $value ) ) {
956
		$vars = get_object_vars( $value );
957
		foreach ( $vars as $key => $data ) {
958
			$value->{$key} = stats_esc_html_deep( $data );
959
		}
960
	} elseif ( is_string( $value ) ) {
961
		$value = esc_html( $value );
962
	}
963
964
	return $value;
965
}
966
967
/**
968
 * Stats xmlrpc_methods function.
969
 *
970
 * @access public
971
 * @param mixed $methods Methods.
972
 * @return array
973
 */
974
function stats_xmlrpc_methods( $methods ) {
975
	$my_methods = array(
976
		'jetpack.getBlog' => 'stats_get_blog',
977
	);
978
979
	return array_merge( $methods, $my_methods );
980
}
981
982
/**
983
 * Register Stats Dashboard Widget.
984
 *
985
 * @access public
986
 * @return void
987
 */
988
function stats_register_dashboard_widget() {
989
	if ( ! current_user_can( 'view_stats' ) )
990
		return;
991
992
	// With wp_dashboard_empty: we load in the content after the page load via JS.
993
	wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' );
994
995
	add_action( 'admin_head', 'stats_dashboard_head' );
996
}
997
998
/**
999
 * Stats Dashboard Widget Options.
1000
 *
1001
 * @access public
1002
 * @return array
1003
 */
1004
function stats_dashboard_widget_options() {
1005
	$defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 );
1006
	if ( ( ! $options = get_option( 'stats_dashboard_widget' ) ) || ! is_array( $options ) ) {
1007
		$options = array();
1008
	}
1009
1010
	// Ignore obsolete option values.
1011
	$intervals = array( 1, 7, 31, 90, 365 );
1012
	foreach ( array( 'top', 'search' ) as $key ) {
1013
		if ( isset( $options[ $key ] ) && ! in_array( $options[ $key ], $intervals ) ) {
1014
			unset( $options[ $key ] );
1015
		}
1016
	}
1017
1018
		return array_merge( $defaults, $options );
1019
}
1020
1021
/**
1022
 * Stats Dashboard Widget Control.
1023
 *
1024
 * @access public
1025
 * @return void
1026
 */
1027
function stats_dashboard_widget_control() {
1028
	$periods   = array(
1029
		'1' => __( 'day', 'jetpack' ),
1030
		'7' => __( 'week', 'jetpack' ),
1031
		'31' => __( 'month', 'jetpack' ),
1032
	);
1033
	$intervals = array(
1034
		'1' => __( 'the past day', 'jetpack' ),
1035
		'7' => __( 'the past week', 'jetpack' ),
1036
		'31' => __( 'the past month', 'jetpack' ),
1037
		'90' => __( 'the past quarter', 'jetpack' ),
1038
		'365' => __( 'the past year', 'jetpack' ),
1039
	);
1040
	$defaults = array(
1041
		'top' => 1,
1042
		'search' => 7,
1043
	);
1044
1045
	$options = stats_dashboard_widget_options();
1046
1047
	if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' === $_POST['widget_id'] ) {
1048
		if ( isset( $periods[ $_POST['chart'] ] ) ) {
1049
			$options['chart'] = $_POST['chart'];
1050
		}
1051
		foreach ( array( 'top', 'search' ) as $key ) {
1052
			if ( isset( $intervals[ $_POST[ $key ] ] ) ) {
1053
				$options[ $key ] = $_POST[ $key ];
1054
			} else { $options[ $key ] = $defaults[ $key ];
1055
			}
1056
		}
1057
		update_option( 'stats_dashboard_widget', $options );
1058
	}
1059
?>
1060
	<p>
1061
	<label for="chart"><?php esc_html_e( 'Chart stats by' , 'jetpack' ); ?></label>
1062
	<select id="chart" name="chart">
1063
	<?php
1064
	foreach ( $periods as $val => $label ) {
1065
?>
1066
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
1067
		<?php
1068
	}
1069
?>
1070
	</select>.
1071
	</p>
1072
1073
	<p>
1074
	<label for="top"><?php esc_html_e( 'Show top posts over', 'jetpack' ); ?></label>
1075
	<select id="top" name="top">
1076
	<?php
1077 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1078
?>
1079
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
1080
		<?php
1081
	}
1082
?>
1083
	</select>.
1084
	</p>
1085
1086
	<p>
1087
	<label for="search"><?php esc_html_e( 'Show top search terms over', 'jetpack' ); ?></label>
1088
	<select id="search" name="search">
1089
	<?php
1090 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1091
?>
1092
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
1093
		<?php
1094
	}
1095
?>
1096
	</select>.
1097
	</p>
1098
	<?php
1099
}
1100
1101
/**
1102
 * Jetpack Stats Dashboard Widget.
1103
 *
1104
 * @access public
1105
 * @return void
1106
 */
1107
function stats_jetpack_dashboard_widget() {
1108
?>
1109
	<form id="stats_dashboard_widget_control" action="<?php echo esc_url( admin_url() ); ?>" method="post">
1110
		<?php stats_dashboard_widget_control(); ?>
1111
		<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
1112
		<input type="hidden" name="widget_id" value="dashboard_stats" />
1113
		<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
1114
	</form>
1115
	<span id="js-toggle-stats_dashboard_widget_control">
1116
		<?php esc_html_e( 'Configure', 'jetpack' ); ?>
1117
	</span>
1118
	<div id="dashboard_stats">
1119
		<div class="inside">
1120
			<div style="height: 250px;"></div>
1121
		</div>
1122
	</div>
1123
	<script>
1124
		jQuery(document).ready(function($){
1125
			var $toggle = $('#js-toggle-stats_dashboard_widget_control');
1126
1127
			$toggle.parent().prev().append( $toggle );
1128
			$toggle.show().click(function(e){
1129
				e.preventDefault();
1130
				e.stopImmediatePropagation();
1131
				$(this).parent().toggleClass('controlVisible');
1132
				$('#stats_dashboard_widget_control').slideToggle();
1133
			});
1134
		});
1135
	</script>
1136
	<style>
1137
		#js-toggle-stats_dashboard_widget_control {
1138
			display: none;
1139
			float: right;
1140
			margin-top: 0.2em;
1141
			font-weight: 400;
1142
			color: #444;
1143
			font-size: .8em;
1144
			text-decoration: underline;
1145
			cursor: pointer;
1146
		}
1147
		#stats_dashboard_widget_control {
1148
			display: none;
1149
			padding: 0 10px;
1150
			overflow: hidden;
1151
		}
1152
		#stats_dashboard_widget_control .button-primary {
1153
			float: right;
1154
		}
1155
		#dashboard_stats {
1156
			box-sizing: border-box;
1157
			width: 100%;
1158
			padding: 0 10px;
1159
		}
1160
	</style>
1161
	<?php
1162
}
1163
1164
/**
1165
 * Register Stats Widget Control Callback.
1166
 *
1167
 * @access public
1168
 * @return void
1169
 */
1170
function stats_register_widget_control_callback() {
1171
	$GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control';
1172
}
1173
1174
/**
1175
 * JavaScript and CSS for dashboard widget.
1176
 *
1177
 * @access public
1178
 * @return void
1179
 */
1180
function stats_dashboard_head() { ?>
1181
<script type="text/javascript">
1182
/* <![CDATA[ */
1183
jQuery( function($) {
1184
	var dashStats = jQuery( '#dashboard_stats div.inside' );
1185
1186
	if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
1187
		return;
1188
	}
1189
1190
	if ( ! dashStats.length ) {
1191
		dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
1192
		var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
1193
		var args = 'width=' + dashStats.width() + '&height=' + h.toString();
1194
	} else {
1195
		if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
1196
			var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
1197
		} else {
1198
			var args = 'width=' + ( dashStats.width() * 2 ).toString();
1199
		}
1200
	}
1201
1202
	dashStats
1203
		.not( '.dashboard-widget-control' )
1204
		.load( 'admin.php?page=stats&noheader&dashboard&' + args );
1205
1206
	jQuery( window ).one( 'resize', function() {
1207
		jQuery( '#stat-chart' ).css( 'width', 'auto' );
1208
	} );
1209
} );
1210
/* ]]> */
1211
</script>
1212
<style type="text/css">
1213
/* <![CDATA[ */
1214
#stat-chart {
1215
	background: none !important;
1216
}
1217
#dashboard_stats .inside {
1218
	margin: 10px 0 0 0 !important;
1219
}
1220
#dashboard_stats #stats-graph {
1221
	margin: 0;
1222
}
1223
#stats-info {
1224
	border-top: 1px solid #dfdfdf;
1225
	margin: 7px -10px 0 -10px;
1226
	padding: 10px;
1227
	background: #fcfcfc;
1228
	-moz-box-shadow:inset 0 1px 0 #fff;
1229
	-webkit-box-shadow:inset 0 1px 0 #fff;
1230
	box-shadow:inset 0 1px 0 #fff;
1231
	overflow: hidden;
1232
	border-radius: 0 0 2px 2px;
1233
	-webkit-border-radius: 0 0 2px 2px;
1234
	-moz-border-radius: 0 0 2px 2px;
1235
	-khtml-border-radius: 0 0 2px 2px;
1236
}
1237
#stats-info #top-posts, #stats-info #top-search {
1238
	float: left;
1239
	width: 50%;
1240
}
1241
#top-posts .stats-section-inner p {
1242
	white-space: nowrap;
1243
	overflow: hidden;
1244
}
1245
#top-posts .stats-section-inner p a {
1246
	overflow: hidden;
1247
	text-overflow: ellipsis;
1248
}
1249
#stats-info div#active {
1250
	border-top: 1px solid #dfdfdf;
1251
	margin: 0 -10px;
1252
	padding: 10px 10px 0 10px;
1253
	-moz-box-shadow:inset 0 1px 0 #fff;
1254
	-webkit-box-shadow:inset 0 1px 0 #fff;
1255
	box-shadow:inset 0 1px 0 #fff;
1256
	overflow: hidden;
1257
}
1258
#top-search p {
1259
	color: #999;
1260
}
1261
#stats-info h3 {
1262
	font-size: 1em;
1263
	margin: 0 0 .5em 0 !important;
1264
}
1265
#stats-info p {
1266
	margin: 0 0 .25em;
1267
	color: #999;
1268
}
1269
#stats-info p.widget-loading {
1270
	margin: 1em 0 0;
1271
	color: #333;
1272
}
1273
#stats-info p a {
1274
	display: block;
1275
}
1276
#stats-info p a.button {
1277
	display: inline;
1278
}
1279
/* ]]> */
1280
</style>
1281
<?php
1282
}
1283
1284
/**
1285
 * Stats Dashboard Widget Content.
1286
 *
1287
 * @access public
1288
 * @return void
1289
 */
1290
function stats_dashboard_widget_content() {
1291
	if ( ! isset( $_GET['width'] ) || ( ! $width = (int) ( $_GET['width'] / 2 ) ) || $width < 250 ) {
1292
		$width = 370;
1293
	}
1294
	if ( ! isset( $_GET['height'] ) || ( ! $height = (int) $_GET['height'] - 36 ) || $height < 230 ) {
1295
		$height = 180;
1296
	}
1297
1298
	$_width  = $width  - 5;
1299
	$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // Hack!
1300
1301
	$options = stats_dashboard_widget_options();
1302
	$blog_id = Jetpack_Options::get_option( 'id' );
1303
1304
	$q = array(
1305
		'noheader' => 'true',
1306
		'proxy' => '',
1307
		'blog' => $blog_id,
1308
		'page' => 'stats',
1309
		'chart' => '',
1310
		'unit' => $options['chart'],
1311
		'color' => get_user_option( 'admin_color' ),
1312
		'width' => $_width,
1313
		'height' => $_height,
1314
		'ssl' => is_ssl(),
1315
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
1316
	);
1317
1318
	$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
1319
1320
	$url = add_query_arg( $q, $url );
1321
	$method = 'GET';
1322
	$timeout = 90;
1323
	$user_id = JETPACK_MASTER_USER;
1324
1325
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1326
	$get_code = wp_remote_retrieve_response_code( $get );
1327
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1328
		stats_print_wp_remote_error( $get, $url );
1329
	} else {
1330
		$body = stats_convert_post_titles( $get['body'] );
1331
		$body = stats_convert_chart_urls( $body );
1332
		$body = stats_convert_image_urls( $body );
1333
		echo $body;
1334
	}
1335
1336
	$post_ids = array();
1337
1338
	$csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) );
1339
	$csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" );
1340
	/* Translators: Stats dashboard widget postviews list: "$post_title $views Views". */
1341
	$printf = __( '%1$s %2$s Views' , 'jetpack' );
1342
1343
	foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) {
1344
		if ( 0 === $post['post_id'] ) {
1345
			unset( $top_posts[$i] );
1346
			continue;
1347
		}
1348
		$post_ids[] = $post['post_id'];
1349
	}
1350
1351
	// Cache.
1352
	get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
1353
1354
	$searches = array();
1355
	foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) {
1356
		if ( 'encrypted_search_terms' === $search_term['searchterm'] ) {
1357
			continue;
1358
		}
1359
		$searches[] = esc_html( $search_term['searchterm'] );
1360
	}
1361
1362
?>
1363
<a class="button" href="admin.php?page=stats"><?php  esc_html_e( 'View All', 'jetpack' ); ?></a>
1364
<div id="stats-info">
1365
	<div id="top-posts" class='stats-section'>
1366
		<div class="stats-section-inner">
1367
		<h3 class="heading"><?php  esc_html_e( 'Top Posts' , 'jetpack' ); ?></h3>
1368
		<?php
1369
	if ( empty( $top_posts ) ) {
1370
?>
1371
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1372
			<?php
1373
	} else {
1374
		foreach ( $top_posts as $post ) {
1375
			if ( ! get_post( $post['post_id'] ) ) {
1376
				continue;
1377
			}
1378
?>
1379
				<p><?php printf(
1380
				$printf,
1381
				'<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',
1382
				number_format_i18n( $post['views'] )
1383
			); ?></p>
1384
				<?php
1385
		}
1386
	}
1387
?>
1388
		</div>
1389
	</div>
1390
	<div id="top-search" class='stats-section'>
1391
		<div class="stats-section-inner">
1392
		<h3 class="heading"><?php  esc_html_e( 'Top Searches' , 'jetpack' ); ?></h3>
1393
		<?php
1394
	if ( empty( $searches ) ) {
1395
?>
1396
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1397
			<?php
1398
	} else {
1399
?>
1400
			<p><?php echo join( ',&nbsp; ', $searches );?></p>
1401
			<?php
1402
	}
1403
?>
1404
		</div>
1405
	</div>
1406
</div>
1407
<div class="clear"></div>
1408
<?php
1409
	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...
1410
}
1411
1412
/**
1413
 * Stats Print WP Remote Error.
1414
 *
1415
 * @access public
1416
 * @param mixed $get Get.
1417
 * @param mixed $url URL.
1418
 * @return void
1419
 */
1420
function stats_print_wp_remote_error( $get, $url ) {
1421
	$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1422
	$previous_error = Jetpack::state( $state_name );
1423
	$error = md5( serialize( compact( 'get', 'url' ) ) );
1424
	Jetpack::state( $state_name, $error );
1425
	if ( $error !== $previous_error ) {
1426
?>
1427
	<div class="wrap">
1428
	<p><?php esc_html_e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
1429
	</div>
1430
<?php
1431
		return;
1432
	}
1433
?>
1434
	<div class="wrap">
1435
	<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>
1436
	<pre>
1437
	User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
1438
	Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
1439
	API URL: "<?php echo esc_url( $url ); ?>"
1440
<?php
1441
if ( is_wp_error( $get ) ) {
1442
	foreach ( $get->get_error_codes() as $code ) {
1443
		foreach ( $get->get_error_messages( $code ) as $message ) {
1444
?>
1445
<?php print $code . ': "' . $message . '"' ?>
1446
1447
<?php
1448
		}
1449
	}
1450
} else {
1451
	$get_code = wp_remote_retrieve_response_code( $get );
1452
	$content_length = strlen( wp_remote_retrieve_body( $get ) );
1453
?>
1454
Response code: "<?php print $get_code ?>"
1455
Content length: "<?php print $content_length ?>"
1456
1457
<?php
1458
}
1459
	?></pre>
1460
	</div>
1461
	<?php
1462
}
1463
1464
/**
1465
 * Get stats from WordPress.com
1466
 *
1467
 * @param string $table The stats which you want to retrieve: postviews, or searchterms.
1468
 * @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...
1469
 *      An associative array of arguments.
1470
 *
1471
 *      @type bool    $end        The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
1472
 *                                and default timezone is UTC date. Default value is Now.
1473
 *      @type string  $days       The length of the desired time frame. Default is 30. Maximum 90 days.
1474
 *      @type int     $limit      The maximum number of records to return. Default is 10. Maximum 100.
1475
 *      @type int     $post_id    The ID of the post to retrieve stats data for
1476
 *      @type string  $summarize  If present, summarizes all matching records. Default Null.
1477
 *
1478
 * }
1479
 *
1480
 * @return array {
1481
 *      An array of post view data, each post as an array
1482
 *
1483
 *      array {
1484
 *          The post view data for a single post
1485
 *
1486
 *          @type string  $post_id         The ID of the post
1487
 *          @type string  $post_title      The title of the post
1488
 *          @type string  $post_permalink  The permalink for the post
1489
 *          @type string  $views           The number of views for the post within the $num_days specified
1490
 *      }
1491
 * }
1492
 */
1493
function stats_get_csv( $table, $args = null ) {
1494
	$defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' );
1495
1496
	$args = wp_parse_args( $args, $defaults );
1497
	$args['table'] = $table;
1498
	$args['blog_id'] = Jetpack_Options::get_option( 'id' );
1499
1500
	$stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
1501
1502
	$key = md5( $stats_csv_url );
1503
1504
	// Get cache.
1505
	$stats_cache = get_option( 'stats_cache' );
1506
	if ( ! $stats_cache || ! is_array( $stats_cache ) ) {
1507
		$stats_cache = array();
1508
	}
1509
1510
	// Return or expire this key.
1511
	if ( isset( $stats_cache[ $key ] ) ) {
1512
		$time = key( $stats_cache[ $key ] );
1513
		if ( time() - $time < 300 ) {
1514
			return $stats_cache[ $key ][ $time ];
1515
		}
1516
		unset( $stats_cache[ $key ] );
1517
	}
1518
1519
	$stats_rows = array();
1520
	do {
1521
		if ( ! $stats = stats_get_remote_csv( $stats_csv_url ) ) {
1522
			break;
1523
		}
1524
1525
		$labels = array_shift( $stats );
1526
1527
		if ( 0 === stripos( $labels[0], 'error' ) ) {
1528
			break;
1529
		}
1530
1531
		$stats_rows = array();
1532
		for ( $s = 0; isset( $stats[ $s ] ); $s++ ) {
1533
			$row = array();
1534
			foreach ( $labels as $col => $label ) {
1535
				$row[ $label ] = $stats[ $s ][ $col ];
1536
			}
1537
			$stats_rows[] = $row;
1538
		}
1539
	} while ( 0 );
1540
1541
	// Expire old keys.
1542 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1543
		if ( ! is_array( $cache ) || 300 < time() - key( $cache ) ) {
1544
			unset( $stats_cache[ $k ] );
1545
		}
1546
	}
1547
1548
		// Set cache.
1549
		$stats_cache[ $key ] = array( time() => $stats_rows );
1550
	update_option( 'stats_cache', $stats_cache );
1551
1552
	return $stats_rows;
1553
}
1554
1555
/**
1556
 * Stats get remote CSV.
1557
 *
1558
 * @access public
1559
 * @param mixed $url URL.
1560
 * @return array
1561
 */
1562
function stats_get_remote_csv( $url ) {
1563
	$method = 'GET';
1564
	$timeout = 90;
1565
	$user_id = JETPACK_MASTER_USER;
1566
1567
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1568
	$get_code = wp_remote_retrieve_response_code( $get );
1569
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1570
		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...
1571
	} else {
1572
		return stats_str_getcsv( $get['body'] );
1573
	}
1574
}
1575
1576
/**
1577
 * Rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it.
1578
 *
1579
 * @access public
1580
 * @param mixed $csv CSV.
1581
 * @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...
1582
 */
1583
function stats_str_getcsv( $csv ) {
1584
	if ( function_exists( 'str_getcsv' ) ) {
1585
		$lines = str_getcsv( $csv, "\n" );
1586
		return array_map( 'str_getcsv', $lines );
1587
	}
1588
	if ( ! $temp = tmpfile() ) { // The tmpfile() automatically unlinks.
1589
		return false;
1590
	}
1591
1592
	$data = array();
1593
1594
	fwrite( $temp, $csv, strlen( $csv ) );
1595
	fseek( $temp, 0 );
1596
	while ( false !== $row = fgetcsv( $temp, 2000 ) ) {		
1597
		$data[] = $row;
1598
	}
1599
	fclose( $temp );
1600
1601
	return $data;
1602
}
1603
1604
/**
1605
 * Abstract out building the rest api stats path.
1606
 *
1607
 * @param  string $resource Resource.
1608
 * @return string
1609
 */
1610
function jetpack_stats_api_path( $resource = '' ) {
1611
	$resource = ltrim( $resource, '/' );
1612
	return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
1613
}
1614
1615
/**
1616
 * Fetches stats data from the REST API.  Caches locally for 5 minutes.
1617
 *
1618
 * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
1619
 * @access public
1620
 * @param array  $args (default: array())  The args that are passed to the endpoint.
1621
 * @param string $resource (default: '') Optional sub-endpoint following /stats/.
1622
 * @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...
1623
 */
1624
function stats_get_from_restapi( $args = array(), $resource = '' ) {
1625
	$endpoint    = jetpack_stats_api_path( $resource );
1626
	$api_version = '1.1';
1627
	$args        = wp_parse_args( $args, array() );
1628
	$cache_key   = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) );
1629
1630
	// Get cache.
1631
	$stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() );
1632
	if ( ! is_array( $stats_cache ) ) {
1633
		$stats_cache = array();
1634
	}
1635
1636
	// Return or expire this key.
1637
	if ( isset( $stats_cache[ $cache_key ] ) ) {
1638
		$time = key( $stats_cache[ $cache_key ] );
1639
		if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) {
1640
			$cached_stats = $stats_cache[ $cache_key ][ $time ];
1641
			$cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats );
1642
			return $cached_stats;
1643
		}
1644
		unset( $stats_cache[ $cache_key ] );
1645
	}
1646
1647
	// Do the dirty work.
1648
	$response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
1649
	if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1650
		// If bad, just return it, don't cache.
1651
		return $response;
1652
	}
1653
1654
	$data = json_decode( wp_remote_retrieve_body( $response ) );
1655
1656
	// Expire old keys.
1657 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1658
		if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) {
1659
			unset( $stats_cache[ $k ] );
1660
		}
1661
	}
1662
1663
	// Set cache.
1664
	$stats_cache[ $cache_key ] = array(
1665
		time() => $data,
1666
	);
1667
	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...
1668
1669
	return $data;
1670
}
1671
1672
/**
1673
 * Load CSS needed for Stats column width in WP-Admin area.
1674
 *
1675
 * @since 4.7.0
1676
 */
1677
function jetpack_stats_load_admin_css() {
1678
	?>
1679
	<style type="text/css">
1680
		.fixed .column-stats {
1681
			width: 5em;
1682
		}
1683
	</style>
1684
	<?php
1685
}
1686
1687
/**
1688
 * Set header for column that allows to go to WordPress.com to see an entry's stats.
1689
 *
1690
 * @param array $columns An array of column names.
1691
 *
1692
 * @since 4.7.0
1693
 *
1694
 * @return mixed
1695
 */
1696
function jetpack_stats_post_table( $columns ) { // Adds a stats link on the edit posts page
1697
	if ( ! current_user_can( 'view_stats' ) || ! Jetpack::is_user_connected() ) {
1698
		return $columns;
1699
	}
1700
	// Array-Fu to add before comments
1701
	$pos = array_search( 'comments', array_keys( $columns ) );
1702
	if ( ! is_int( $pos ) ) {
1703
		return $columns;
1704
	}
1705
	$chunks             = array_chunk( $columns, $pos, true );
1706
	$chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
1707
1708
	return call_user_func_array( 'array_merge', $chunks );
1709
}
1710
1711
/**
1712
 * Set content for cell with link to an entry's stats in WordPress.com.
1713
 *
1714
 * @param string $column  The name of the column to display.
1715
 * @param int    $post_id The current post ID.
1716
 *
1717
 * @since 4.7.0
1718
 *
1719
 * @return mixed
1720
 */
1721
function jetpack_stats_post_table_cell( $column, $post_id ) {
1722
	if ( 'stats' == $column ) {
1723
		if ( 'publish' != get_post_status( $post_id ) ) {
1724
			printf(
1725
				'<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
1726
				esc_html__( 'No stats', 'jetpack' )
1727
			);
1728
		} else {
1729
			printf(
1730
				'<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
1731
				esc_url( "https://wordpress.com/stats/post/$post_id/" . Jetpack::build_raw_urls( get_home_url() ) ),
1732
				esc_html__( 'View stats for this post in WordPress.com', 'jetpack' )
1733
			);
1734
		}
1735
	}
1736
}
1737