Completed
Push — fix/stats-deprecated-create_fu... ( 327f7f...adfad1 )
by
unknown
19:59 queued 10:15
created

stats.php ➔ jetpack_stats_convert_chart_urls_callback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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