Completed
Push — fix/stats-deprecated-create_fu... ( 327f7f )
by
unknown
08:55
created

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