Completed
Push — update/remove-omni-search-test... ( 6db49e...ed8043 )
by
unknown
30:51 queued 20:39
created

stats.php ➔ jetpack_is_dnt_enabled()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 4
nop 0
dl 0
loc 21
rs 8.7624
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
 * Checks if filter is set and dnt is enabled.
95
 *
96
 * @return bool
97
 */
98
function jetpack_is_dnt_enabled() {
99
	/**
100
	 * Filter the option which decides honor DNT or not.
101
	 *
102
	 * @module stats
103
	 * @since 6.1.0
104
	 *
105
	 * @param bool false If config honors DNT and client doesn't want to tracked, false if not.
106
	 */
107
	if ( false === apply_filters( 'jetpack_honor_dnt_header_for_stats', false ) ) {
108
		return false;
109
	}
110
111
	foreach ( $_SERVER as $name => $value ) {
112
		if ( 'http_dnt' == strtolower( $name ) && 1 == $value ) {
113
			return true;
114
		}
115
	}
116
117
	return false;
118
}
119
120
/**
121
 * Prevent sparkline img requests being redirected to upgrade.php.
122
 * See wp-admin/admin.php where it checks $wp_db_version.
123
 *
124
 * @access public
125
 * @param mixed $version Version.
126
 * @return string $version.
127
 */
128
function stats_ignore_db_version( $version ) {
129
	if (
130
		is_admin() &&
131
		isset( $_GET['page'] ) && 'stats' === $_GET['page'] &&
132
		isset( $_GET['chart'] ) && strpos($_GET['chart'], 'admin-bar-hours') === 0
133
	) {
134
		global $wp_db_version;
135
		return $wp_db_version;
136
	}
137
	return $version;
138
}
139
140
/**
141
 * Maps view_stats cap to read cap as needed.
142
 *
143
 * @access public
144
 * @param mixed $caps Caps.
145
 * @param mixed $cap Cap.
146
 * @param mixed $user_id User ID.
147
 * @return array Possibly mapped capabilities for meta capability.
148
 */
149
function stats_map_meta_caps( $caps, $cap, $user_id ) {
150
	// Map view_stats to exists.
151
	if ( 'view_stats' === $cap ) {
152
		$user        = new WP_User( $user_id );
153
		$user_role   = array_shift( $user->roles );
154
		$stats_roles = stats_get_option( 'roles' );
155
156
		// Is the users role in the available stats roles?
157
		if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles ) ) {
158
			$caps = array( 'read' );
159
		}
160
	}
161
162
	return $caps;
163
}
164
165
/**
166
 * Stats Template Redirect.
167
 *
168
 * @access public
169
 * @return void
170
 */
171
function stats_template_redirect() {
172
	global $current_user, $stats_footer;
173
174
	if ( is_feed() || is_robots() || is_trackback() || is_preview() || jetpack_is_dnt_enabled() ) {
175
		return;
176
	}
177
178
	// Should we be counting this user's views?
179
	if ( ! empty( $current_user->ID ) ) {
180
		$count_roles = stats_get_option( 'count_roles' );
181
		if ( ! is_array( $count_roles ) || ! array_intersect( $current_user->roles, $count_roles ) ) {
182
			return;
183
		}
184
	}
185
186
	add_action( 'wp_footer', 'stats_footer', 101 );
187
	add_action( 'wp_head', 'stats_add_shutdown_action' );
188
189
	$script = 'https://stats.wp.com/e-' . gmdate( 'YW' ) . '.js';
190
	$data = stats_build_view_data();
191
	$data_stats_array = stats_array( $data );
192
193
	$stats_footer = <<<END
194
<script type='text/javascript' src='{$script}' async='async' defer='defer'></script>
195
<script type='text/javascript'>
196
	_stq = window._stq || [];
197
	_stq.push([ 'view', {{$data_stats_array}} ]);
198
	_stq.push([ 'clickTrackerInit', '{$data['blog']}', '{$data['post']}' ]);
199
</script>
200
201
END;
202
}
203
204
205
/**
206
 * Stats Build View Data.
207
 *
208
 * @access public
209
 * @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...
210
 */
211
function stats_build_view_data() {
212
	global $wp_the_query;
213
214
	$blog = Jetpack_Options::get_option( 'id' );
215
	$tz = get_option( 'gmt_offset' );
216
	$v = 'ext';
217
	$blog_url = parse_url( site_url() );
218
	$srv = $blog_url['host'];
219
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
220
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
221
		// Store and reset the queried_object and queried_object_id
222
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
223
		// Repro:
224
		// 1. Set home_url = https://ExamPle.com/
225
		// 2. Set show_on_front = page
226
		// 3. Set page_on_front = something
227
		// 4. Visit https://example.com/ !
228
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
229
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
230
		$post = $wp_the_query->get_queried_object_id();
231
		$wp_the_query->queried_object = $queried_object;
232
		$wp_the_query->queried_object_id = $queried_object_id;
233
	} else {
234
		$post = '0';
235
	}
236
237
	return compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
238
}
239
240
/**
241
 * Stats Add Shutdown Action.
242
 *
243
 * @access public
244
 * @return void
245
 */
246
function stats_add_shutdown_action() {
247
	// Just in case wp_footer isn't in your theme.
248
	add_action( 'shutdown',  'stats_footer', 101 );
249
}
250
251
/**
252
 * Stats Footer.
253
 *
254
 * @access public
255
 * @return void
256
 */
257
function stats_footer() {
258
	global $stats_footer;
259
	print $stats_footer;
260
	$stats_footer = '';
261
}
262
263
/**
264
 * Stats Get Options.
265
 *
266
 * @access public
267
 * @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...
268
 */
269
function stats_get_options() {
270
	$options = get_option( 'stats_options' );
271
272
	if ( ! isset( $options['version'] ) || $options['version'] < STATS_VERSION ) {
273
		$options = stats_upgrade_options( $options );
274
	}
275
276
	return $options;
277
}
278
279
/**
280
 * Get Stats Options.
281
 *
282
 * @access public
283
 * @param mixed $option Option.
284
 * @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...
285
 */
286
function stats_get_option( $option ) {
287
	$options = stats_get_options();
288
289
	if ( 'blog_id' === $option ) {
290
		return Jetpack_Options::get_option( 'id' );
291
	}
292
293
	if ( isset( $options[ $option ] ) ) {
294
		return $options[ $option ];
295
	}
296
297
	return null;
298
}
299
300
/**
301
 * Stats Set Options.
302
 *
303
 * @access public
304
 * @param mixed $option Option.
305
 * @param mixed $value Value.
306
 * @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...
307
 */
308
function stats_set_option( $option, $value ) {
309
	$options = stats_get_options();
310
311
	$options[ $option ] = $value;
312
313
	return stats_set_options( $options );
314
}
315
316
/**
317
 * Stats Set Options.
318
 *
319
 * @access public
320
 * @param mixed $options Options.
321
 * @return bool
322
 */
323
function stats_set_options( $options ) {
324
	return update_option( 'stats_options', $options );
325
}
326
327
/**
328
 * Stats Upgrade Options.
329
 *
330
 * @access public
331
 * @param mixed $options Options.
332
 * @return array|bool
333
 */
334
function stats_upgrade_options( $options ) {
335
	$defaults = array(
336
		'admin_bar'    => true,
337
		'roles'        => array( 'administrator' ),
338
		'count_roles'  => array(),
339
		'blog_id'      => Jetpack_Options::get_option( 'id' ),
340
		'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...
341
		'hide_smile'   => true,
342
	);
343
344
	if ( isset( $options['reg_users'] ) ) {
345
		if ( ! function_exists( 'get_editable_roles' ) ) {
346
			require_once ABSPATH . 'wp-admin/includes/user.php';
347
		}
348
		if ( $options['reg_users'] ) {
349
			$options['count_roles'] = array_keys( get_editable_roles() );
350
		}
351
		unset( $options['reg_users'] );
352
	}
353
354
	if ( is_array( $options ) && ! empty( $options ) ) {
355
		$new_options = array_merge( $defaults, $options );
356
	} else { $new_options = $defaults;
357
	}
358
359
	$new_options['version'] = STATS_VERSION;
360
361
	if ( ! stats_set_options( $new_options ) ) {
362
		return false;
363
	}
364
365
	stats_update_blog();
366
367
	return $new_options;
368
}
369
370
/**
371
 * Stats Array.
372
 *
373
 * @access public
374
 * @param mixed $kvs KVS.
375
 * @return array
376
 */
377
function stats_array( $kvs ) {
378
	/**
379
	 * Filter the options added to the JavaScript Stats tracking code.
380
	 *
381
	 * @module stats
382
	 *
383
	 * @since 1.1.0
384
	 *
385
	 * @param array $kvs Array of options about the site and page you're on.
386
	 */
387
	$kvs = apply_filters( 'stats_array', $kvs );
388
	$kvs = array_map( 'addslashes', $kvs );
389
	foreach ( $kvs as $k => $v ) {
390
		$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...
391
	}
392
	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...
393
}
394
395
/**
396
 * Admin Pages.
397
 *
398
 * @access public
399
 * @return void
400
 */
401
function stats_admin_menu() {
402
	global $pagenow;
403
404
	// If we're at an old Stats URL, redirect to the new one.
405
	// Don't even bother with caps, menu_page_url(), etc.  Just do it.
406
	if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'stats' === $_GET['page'] ) {
407
		$redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] );
408
		$relative_pos = strpos( $redirect_url, '/wp-admin/' );
409
		if ( false !== $relative_pos ) {
410
			wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
411
			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...
412
		}
413
	}
414
415
	$hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' );
416
	add_action( "load-$hook", 'stats_reports_load' );
417
}
418
419
/**
420
 * Stats Admin Path.
421
 *
422
 * @access public
423
 * @return string
424
 */
425
function stats_admin_path() {
426
	return Jetpack::module_configuration_url( __FILE__ );
427
}
428
429
/**
430
 * Stats Reports Load.
431
 *
432
 * @access public
433
 * @return void
434
 */
435
function stats_reports_load() {
436
	wp_enqueue_script( 'jquery' );
437
	wp_enqueue_script( 'postbox' );
438
	wp_enqueue_script( 'underscore' );
439
440
	add_action( 'admin_print_styles', 'stats_reports_css' );
441
442
	if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) {
443
		$parsed = parse_url( admin_url() );
444
		// Remember user doesn't want JS.
445
		setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days.
446
	}
447
448
	if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) {
449
		// Detect if JS is on.  If so, remove cookie so next page load is via JS.
450
		add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
451
	} else if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) {
452
		// Normal page load.  Load page content via JS.
453
		add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
454
	}
455
}
456
457
/**
458
 * Stats Reports CSS.
459
 *
460
 * @access public
461
 * @return void
462
 */
463
function stats_reports_css() {
464
?>
465
<style type="text/css">
466
#stats-loading-wrap p {
467
	text-align: center;
468
	font-size: 2em;
469
	margin: 7.5em 15px 0 0;
470
	height: 64px;
471
	line-height: 64px;
472
}
473
</style>
474
<?php
475
}
476
477
478
/**
479
 * Detect if JS is on.  If so, remove cookie so next page load is via JS.
480
 *
481
 * @access public
482
 * @return void
483
 */
484
function stats_js_remove_stnojs_cookie() {
485
	$parsed = parse_url( admin_url() );
486
?>
487
<script type="text/javascript">
488
/* <![CDATA[ */
489
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
490
/* ]]> */
491
</script>
492
<?php
493
}
494
495
/**
496
 * Normal page load.  Load page content via JS.
497
 *
498
 * @access public
499
 * @return void
500
 */
501
function stats_js_load_page_via_ajax() {
502
?>
503
<script type="text/javascript">
504
/* <![CDATA[ */
505
if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
506
	jQuery( function( $ ) {
507
		$.get( document.location.href + '&noheader', function( responseText ) {
508
			$( '#stats-loading-wrap' ).replaceWith( responseText );
509
		} );
510
	} );
511
}
512
/* ]]> */
513
</script>
514
<?php
515
}
516
517
/**
518
 * Stats Report Page.
519
 *
520
 * @access public
521
 * @param bool $main_chart_only (default: false) Main Chart Only.
522
 */
523
function stats_reports_page( $main_chart_only = false ) {
524
525
	if ( isset( $_GET['dashboard'] ) ) {
526
		return stats_dashboard_widget_content();
527
	}
528
529
	$blog_id = stats_get_option( 'blog_id' );
530
	$domain = Jetpack::build_raw_urls( get_home_url() );
531
532
	if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) {
533
		$nojs_url = add_query_arg( 'nojs', '1' );
534
		$http = is_ssl() ? 'https' : 'http';
535
		// Loading message. No JS fallback message.
536
?>
537
<div class="wrap">
538
	<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>
539
</div>
540
<div id="stats-loading-wrap" class="wrap">
541
<p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php
542
		echo esc_url(
543
			/**
544
			 * Sets external resource URL.
545
			 *
546
			 * @module stats
547
			 *
548
			 * @since 1.4.0
549
			 *
550
			 * @param string $args URL of external resource.
551
			 */
552
			apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" )
553
		); ?>" /></p>
554
<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>
555
<p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
556
<a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without JavaScript', 'jetpack' ); ?></a>.</p>
557
</div>
558
<?php
559
		return;
560
	}
561
562
	$day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false;
563
	$q = array(
564
		'noheader' => 'true',
565
		'proxy' => '',
566
		'page' => 'stats',
567
		'day' => $day,
568
		'blog' => $blog_id,
569
		'charset' => get_option( 'blog_charset' ),
570
		'color' => get_user_option( 'admin_color' ),
571
		'ssl' => is_ssl(),
572
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
573
	);
574
	if ( get_locale() !== 'en_US' ) {
575
		$q['jp_lang'] = get_locale();
576
	}
577
	// Only show the main chart, without extra header data, or metaboxes.
578
	$q['main_chart_only'] = $main_chart_only;
579
	$args = array(
580
		'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),
581
		'numdays' => 'int',
582
		'day' => 'date',
583
		'unit' => array( 1, 7, 31, 'human' ),
584
		'humanize' => array( 'true' ),
585
		'num' => 'int',
586
		'summarize' => null,
587
		'post' => 'int',
588
		'width' => 'int',
589
		'height' => 'int',
590
		'data' => 'data',
591
		'blog_subscribers' => 'int',
592
		'comment_subscribers' => null,
593
		'type' => array( 'wpcom', 'email', 'pending' ),
594
		'pagenum' => 'int',
595
	);
596
	foreach ( $args as $var => $vals ) {
597
		if ( ! isset( $_REQUEST[$var] ) )
598
			continue;
599
		if ( is_array( $vals ) ) {
600
			if ( in_array( $_REQUEST[$var], $vals ) )
601
				$q[$var] = $_REQUEST[$var];
602
		} elseif ( 'int' === $vals ) {
603
			$q[$var] = intval( $_REQUEST[$var] );
604
		} elseif ( 'date' === $vals ) {
605
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) )
606
				$q[$var] = $_REQUEST[$var];
607
		} elseif ( null === $vals ) {
608
			$q[$var] = '';
609
		} elseif ( 'data' === $vals ) {
610
			if ( 'index.php' === substr( $_REQUEST[$var], 0, 9 ) )
611
				$q[$var] = $_REQUEST[$var];
612
		}
613
	}
614
615
	if ( isset( $_GET['chart'] ) ) {
616
		if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) {
617
			$chart = sanitize_title( $_GET['chart'] );
618
			$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
619
		}
620
	} else {
621
		$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
622
	}
623
624
	$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...
625
	$method = 'GET';
626
	$timeout = 90;
627
	$user_id = JETPACK_MASTER_USER; // means send the wp.com user_id
628
629
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
630
	$get_code = wp_remote_retrieve_response_code( $get );
631
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
632
		stats_print_wp_remote_error( $get, $url );
633
	} else {
634
		if ( ! empty( $get['headers']['content-type'] ) ) {
635
			$type = $get['headers']['content-type'];
636
			if ( substr( $type, 0, 5 ) === 'image' ) {
637
				$img = $get['body'];
638
				header( 'Content-Type: ' . $type );
639
				header( 'Content-Length: ' . strlen( $img ) );
640
				echo $img;
641
				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...
642
			}
643
		}
644
		$body = stats_convert_post_titles( $get['body'] );
645
		$body = stats_convert_chart_urls( $body );
646
		$body = stats_convert_image_urls( $body );
647
		$body = stats_convert_admin_urls( $body );
648
		echo $body;
649
	}
650
651
	if ( isset( $_GET['page'] ) && 'stats' === $_GET['page'] && ! isset( $_GET['chart'] ) ) {
652
		JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_stats' ) );
653
	}
654
655
	if ( isset( $_GET['noheader'] ) ) {
656
		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...
657
	}
658
}
659
660
/**
661
 * Stats Convert Admin Urls.
662
 *
663
 * @access public
664
 * @param mixed $html HTML.
665
 * @return string
666
 */
667
function stats_convert_admin_urls( $html ) {
668
	return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
669
}
670
671
/**
672
 * Stats Convert Image URLs.
673
 *
674
 * @access public
675
 * @param mixed $html HTML.
676
 * @return string
677
 */
678
function stats_convert_image_urls( $html ) {
679
	$url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER );
680
	$html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html );
681
	return $html;
682
}
683
684
/**
685
 * Callback for preg_replace_callback used in stats_convert_chart_urls()
686
 *
687
 * @since 5.6.0
688
 *
689
 * @param  array  $matches The matches resulting from the preg_replace_callback call.
690
 * @return string          The admin url for the chart.
691
 */
692
function jetpack_stats_convert_chart_urls_callback( $matches ) {
693
	// If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string.
694
	return 'admin.php?page=stats&noheader&chart=' . $matches[1] . str_replace( '?', '&', $matches[2] );
695
}
696
697
/**
698
 * Stats Convert Chart URLs.
699
 *
700
 * @access public
701
 * @param mixed $html HTML.
702
 * @return string
703
 */
704
function stats_convert_chart_urls( $html ) {
705
	$html = preg_replace_callback(
706
		'|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
707
		'jetpack_stats_convert_chart_urls_callback',
708
		$html
709
	);
710
	return $html;
711
}
712
713
/**
714
 * Stats Convert Post Title HTML
715
 *
716
 * @access public
717
 * @param mixed $html HTML.
718
 * @return string
719
 */
720
function stats_convert_post_titles( $html ) {
721
	global $stats_posts;
722
	$pattern = "<span class='post-(\d+)-link'>.*?</span>";
723
	if ( ! preg_match_all( "!$pattern!", $html, $matches ) ) {
724
		return $html;
725
	}
726
	$posts = get_posts(
727
		array(
728
			'include' => implode( ',', $matches[1] ),
729
			'post_type' => 'any',
730
			'post_status' => 'any',
731
			'numberposts' => -1,
732
			'suppress_filters' => false,
733
		)
734
	);
735
	foreach ( $posts as $post ) {
736
		$stats_posts[ $post->ID ] = $post;
737
	}
738
	$html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html );
739
	return $html;
740
}
741
742
/**
743
 * Stats Convert Post Title Matches.
744
 *
745
 * @access public
746
 * @param mixed $matches Matches.
747
 * @return string
748
 */
749
function stats_convert_post_title( $matches ) {
750
	global $stats_posts;
751
	$post_id = $matches[1];
752
	if ( isset( $stats_posts[$post_id] ) )
753
		return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>';
754
	return $matches[0];
755
}
756
757
/**
758
 * Stats Configuration Load.
759
 *
760
 * @access public
761
 * @return void
762
 */
763
function stats_configuration_load() {
764
	if ( isset( $_POST['action'] ) && 'save_options' === $_POST['action'] && $_POST['_wpnonce'] === wp_create_nonce( 'stats' ) ) {
765
		$options = stats_get_options();
766
		$options['admin_bar']  = isset( $_POST['admin_bar']  ) && $_POST['admin_bar'];
767
		$options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile'];
768
769
		$options['roles'] = array( 'administrator' );
770 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
771
			if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] ) {
772
				$options['roles'][] = $role;
773
			}
774
		}
775
776
		$options['count_roles'] = array();
777 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
778
			if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] ) {
779
				$options['count_roles'][] = $role;
780
			}
781
		}
782
783
		stats_set_options( $options );
784
		stats_update_blog();
785
		Jetpack::state( 'message', 'module_configured' );
786
		wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) );
787
		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...
788
	}
789
}
790
791
/**
792
 * Stats Configuration Head.
793
 *
794
 * @access public
795
 * @return void
796
 */
797
function stats_configuration_head() {
798
?>
799
	<style type="text/css">
800
		#statserror {
801
			border: 1px solid #766;
802
			background-color: #d22;
803
			padding: 1em 3em;
804
		}
805
		.stats-smiley {
806
			vertical-align: 1px;
807
		}
808
	</style>
809
	<?php
810
}
811
812
/**
813
 * Stats Configuration Screen.
814
 *
815
 * @access public
816
 * @return void
817
 */
818
function stats_configuration_screen() {
819
	$options = stats_get_options();
820
?>
821
	<div class="narrow">
822
		<p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p>
823
		<form method="post">
824
		<input type='hidden' name='action' value='save_options' />
825
		<?php wp_nonce_field( 'stats' ); ?>
826
		<table id="menu" class="form-table">
827
		<tr valign="top"><th scope="row"><label for="admin_bar"><?php esc_html_e( 'Admin bar' , 'jetpack' ); ?></label></th>
828
		<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>
829
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Registered users', 'jetpack' ); ?></th>
830
		<td>
831
			<?php esc_html_e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/>
832
			<?php
833
	$count_roles = stats_get_option( 'count_roles' );
834
	foreach ( get_editable_roles() as $role => $details ) {
835
?>
836
				<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/>
837
				<?php
838
	}
839
?>
840
		</td></tr>
841
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Smiley' , 'jetpack' ); ?></th>
842
		<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>
843
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Report visibility' , 'jetpack' ); ?></th>
844
		<td>
845
			<?php esc_html_e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/>
846
			<?php
847
	$stats_roles = stats_get_option( 'roles' );
848
	foreach ( get_editable_roles() as $role => $details ) {
849
?>
850
				<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/>
851
				<?php
852
	}
853
?>
854
		</td></tr>
855
		</table>
856
		<p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p>
857
		</form>
858
	</div>
859
	<?php
860
}
861
862
/**
863
 * Stats Hide Smile.
864
 *
865
 * @access public
866
 * @return void
867
 */
868
function stats_hide_smile_css() {
869
	$options = stats_get_options();
870
	if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) {
871
?>
872
<style type='text/css'>img#wpstats{display:none}</style><?php
873
	}
874
}
875
876
/**
877
 * Stats Admin Bar Head.
878
 *
879
 * @access public
880
 * @return void
881
 */
882
function stats_admin_bar_head() {
883
	if ( ! stats_get_option( 'admin_bar' ) )
884
		return;
885
886
	if ( ! current_user_can( 'view_stats' ) )
887
		return;
888
889
	if ( function_exists( 'is_admin_bar_showing' ) && ! is_admin_bar_showing() ) {
890
		return;
891
	}
892
893
	add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 );
894
?>
895
896
<style type='text/css'>
897
#wpadminbar .quicklinks li#wp-admin-bar-stats {
898
	height: 32px;
899
}
900
#wpadminbar .quicklinks li#wp-admin-bar-stats a {
901
	height: 32px;
902
	padding: 0;
903
}
904
#wpadminbar .quicklinks li#wp-admin-bar-stats a div {
905
	height: 32px;
906
	width: 95px;
907
	overflow: hidden;
908
	margin: 0 10px;
909
}
910
#wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
911
	width: auto;
912
	margin: 0 8px 0 10px;
913
}
914
#wpadminbar .quicklinks li#wp-admin-bar-stats a img {
915
	height: 24px;
916
	padding: 4px 0;
917
	max-width: none;
918
	border: none;
919
}
920
</style>
921
<?php
922
}
923
924
/**
925
 * Stats AdminBar.
926
 *
927
 * @access public
928
 * @param mixed $wp_admin_bar WPAdminBar.
929
 * @return void
930
 */
931
function stats_admin_bar_menu( &$wp_admin_bar ) {
932
	$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
933
934
	$img_src = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale' ), $url ) );
935
	$img_src_2x = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale-2x' ), $url ) );
936
937
	$alt = esc_attr( __( 'Stats', 'jetpack' ) );
938
939
	$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
940
941
	$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 );
942
943
	$wp_admin_bar->add_menu( $menu );
944
}
945
946
/**
947
 * Stats Update Blog.
948
 *
949
 * @access public
950
 * @return void
951
 */
952
function stats_update_blog() {
953
	Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() );
954
}
955
956
/**
957
 * Stats Get Blog.
958
 *
959
 * @access public
960
 * @return string
961
 */
962
function stats_get_blog() {
963
	$home = parse_url( trailingslashit( get_option( 'home' ) ) );
964
	$blog = array(
965
		'host'                => $home['host'],
966
		'path'                => $home['path'],
967
		'blogname'            => get_option( 'blogname' ),
968
		'blogdescription'     => get_option( 'blogdescription' ),
969
		'siteurl'             => get_option( 'siteurl' ),
970
		'gmt_offset'          => get_option( 'gmt_offset' ),
971
		'timezone_string'     => get_option( 'timezone_string' ),
972
		'stats_version'       => STATS_VERSION,
973
		'stats_api'           => 'jetpack',
974
		'page_on_front'       => get_option( 'page_on_front' ),
975
		'permalink_structure' => get_option( 'permalink_structure' ),
976
		'category_base'       => get_option( 'category_base' ),
977
		'tag_base'            => get_option( 'tag_base' ),
978
	);
979
	$blog = array_merge( stats_get_options(), $blog );
980
	unset( $blog['roles'], $blog['blog_id'] );
981
	return stats_esc_html_deep( $blog );
982
}
983
984
/**
985
 * Modified from stripslashes_deep()
986
 *
987
 * @access public
988
 * @param mixed $value Value.
989
 * @return string
990
 */
991
function stats_esc_html_deep( $value ) {
992
	if ( is_array( $value ) ) {
993
		$value = array_map( 'stats_esc_html_deep', $value );
994
	} elseif ( is_object( $value ) ) {
995
		$vars = get_object_vars( $value );
996
		foreach ( $vars as $key => $data ) {
997
			$value->{$key} = stats_esc_html_deep( $data );
998
		}
999
	} elseif ( is_string( $value ) ) {
1000
		$value = esc_html( $value );
1001
	}
1002
1003
	return $value;
1004
}
1005
1006
/**
1007
 * Stats xmlrpc_methods function.
1008
 *
1009
 * @access public
1010
 * @param mixed $methods Methods.
1011
 * @return array
1012
 */
1013
function stats_xmlrpc_methods( $methods ) {
1014
	$my_methods = array(
1015
		'jetpack.getBlog' => 'stats_get_blog',
1016
	);
1017
1018
	return array_merge( $methods, $my_methods );
1019
}
1020
1021
/**
1022
 * Register Stats Dashboard Widget.
1023
 *
1024
 * @access public
1025
 * @return void
1026
 */
1027
function stats_register_dashboard_widget() {
1028
	if ( ! current_user_can( 'view_stats' ) )
1029
		return;
1030
1031
	// With wp_dashboard_empty: we load in the content after the page load via JS.
1032
	wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' );
1033
1034
	add_action( 'admin_head', 'stats_dashboard_head' );
1035
}
1036
1037
/**
1038
 * Stats Dashboard Widget Options.
1039
 *
1040
 * @access public
1041
 * @return array
1042
 */
1043
function stats_dashboard_widget_options() {
1044
	$defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 );
1045
	if ( ( ! $options = get_option( 'stats_dashboard_widget' ) ) || ! is_array( $options ) ) {
1046
		$options = array();
1047
	}
1048
1049
	// Ignore obsolete option values.
1050
	$intervals = array( 1, 7, 31, 90, 365 );
1051
	foreach ( array( 'top', 'search' ) as $key ) {
1052
		if ( isset( $options[ $key ] ) && ! in_array( $options[ $key ], $intervals ) ) {
1053
			unset( $options[ $key ] );
1054
		}
1055
	}
1056
1057
		return array_merge( $defaults, $options );
1058
}
1059
1060
/**
1061
 * Stats Dashboard Widget Control.
1062
 *
1063
 * @access public
1064
 * @return void
1065
 */
1066
function stats_dashboard_widget_control() {
1067
	$periods   = array(
1068
		'1' => __( 'day', 'jetpack' ),
1069
		'7' => __( 'week', 'jetpack' ),
1070
		'31' => __( 'month', 'jetpack' ),
1071
	);
1072
	$intervals = array(
1073
		'1' => __( 'the past day', 'jetpack' ),
1074
		'7' => __( 'the past week', 'jetpack' ),
1075
		'31' => __( 'the past month', 'jetpack' ),
1076
		'90' => __( 'the past quarter', 'jetpack' ),
1077
		'365' => __( 'the past year', 'jetpack' ),
1078
	);
1079
	$defaults = array(
1080
		'top' => 1,
1081
		'search' => 7,
1082
	);
1083
1084
	$options = stats_dashboard_widget_options();
1085
1086
	if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' === $_POST['widget_id'] ) {
1087
		if ( isset( $periods[ $_POST['chart'] ] ) ) {
1088
			$options['chart'] = $_POST['chart'];
1089
		}
1090
		foreach ( array( 'top', 'search' ) as $key ) {
1091
			if ( isset( $intervals[ $_POST[ $key ] ] ) ) {
1092
				$options[ $key ] = $_POST[ $key ];
1093
			} else { $options[ $key ] = $defaults[ $key ];
1094
			}
1095
		}
1096
		update_option( 'stats_dashboard_widget', $options );
1097
	}
1098
?>
1099
	<p>
1100
	<label for="chart"><?php esc_html_e( 'Chart stats by' , 'jetpack' ); ?></label>
1101
	<select id="chart" name="chart">
1102
	<?php
1103
	foreach ( $periods as $val => $label ) {
1104
?>
1105
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
1106
		<?php
1107
	}
1108
?>
1109
	</select>.
1110
	</p>
1111
1112
	<p>
1113
	<label for="top"><?php esc_html_e( 'Show top posts over', 'jetpack' ); ?></label>
1114
	<select id="top" name="top">
1115
	<?php
1116 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1117
?>
1118
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
1119
		<?php
1120
	}
1121
?>
1122
	</select>.
1123
	</p>
1124
1125
	<p>
1126
	<label for="search"><?php esc_html_e( 'Show top search terms over', 'jetpack' ); ?></label>
1127
	<select id="search" name="search">
1128
	<?php
1129 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1130
?>
1131
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
1132
		<?php
1133
	}
1134
?>
1135
	</select>.
1136
	</p>
1137
	<?php
1138
}
1139
1140
/**
1141
 * Jetpack Stats Dashboard Widget.
1142
 *
1143
 * @access public
1144
 * @return void
1145
 */
1146
function stats_jetpack_dashboard_widget() {
1147
?>
1148
	<form id="stats_dashboard_widget_control" action="<?php echo esc_url( admin_url() ); ?>" method="post">
1149
		<?php stats_dashboard_widget_control(); ?>
1150
		<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
1151
		<input type="hidden" name="widget_id" value="dashboard_stats" />
1152
		<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
1153
	</form>
1154
	<span id="js-toggle-stats_dashboard_widget_control">
1155
		<?php esc_html_e( 'Configure', 'jetpack' ); ?>
1156
	</span>
1157
	<div id="dashboard_stats">
1158
		<div class="inside">
1159
			<div style="height: 250px;"></div>
1160
		</div>
1161
	</div>
1162
	<script>
1163
		jQuery(document).ready(function($){
1164
			var $toggle = $('#js-toggle-stats_dashboard_widget_control');
1165
1166
			$toggle.parent().prev().append( $toggle );
1167
			$toggle.show().click(function(e){
1168
				e.preventDefault();
1169
				e.stopImmediatePropagation();
1170
				$(this).parent().toggleClass('controlVisible');
1171
				$('#stats_dashboard_widget_control').slideToggle();
1172
			});
1173
		});
1174
	</script>
1175
	<style>
1176
		#js-toggle-stats_dashboard_widget_control {
1177
			display: none;
1178
			float: right;
1179
			margin-top: 0.2em;
1180
			font-weight: 400;
1181
			color: #444;
1182
			font-size: .8em;
1183
			text-decoration: underline;
1184
			cursor: pointer;
1185
		}
1186
		#stats_dashboard_widget_control {
1187
			display: none;
1188
			padding: 0 10px;
1189
			overflow: hidden;
1190
		}
1191
		#stats_dashboard_widget_control .button-primary {
1192
			float: right;
1193
		}
1194
		#dashboard_stats {
1195
			box-sizing: border-box;
1196
			width: 100%;
1197
			padding: 0 10px;
1198
		}
1199
	</style>
1200
	<?php
1201
}
1202
1203
/**
1204
 * Register Stats Widget Control Callback.
1205
 *
1206
 * @access public
1207
 * @return void
1208
 */
1209
function stats_register_widget_control_callback() {
1210
	$GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control';
1211
}
1212
1213
/**
1214
 * JavaScript and CSS for dashboard widget.
1215
 *
1216
 * @access public
1217
 * @return void
1218
 */
1219
function stats_dashboard_head() { ?>
1220
<script type="text/javascript">
1221
/* <![CDATA[ */
1222
jQuery( function($) {
1223
	var dashStats = jQuery( '#dashboard_stats div.inside' );
1224
1225
	if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
1226
		return;
1227
	}
1228
1229
	if ( ! dashStats.length ) {
1230
		dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
1231
		var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
1232
		var args = 'width=' + dashStats.width() + '&height=' + h.toString();
1233
	} else {
1234
		if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
1235
			var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
1236
		} else {
1237
			var args = 'width=' + ( dashStats.width() * 2 ).toString();
1238
		}
1239
	}
1240
1241
	dashStats
1242
		.not( '.dashboard-widget-control' )
1243
		.load( 'admin.php?page=stats&noheader&dashboard&' + args );
1244
1245
	jQuery( window ).one( 'resize', function() {
1246
		jQuery( '#stat-chart' ).css( 'width', 'auto' );
1247
	} );
1248
} );
1249
/* ]]> */
1250
</script>
1251
<style type="text/css">
1252
/* <![CDATA[ */
1253
#stat-chart {
1254
	background: none !important;
1255
}
1256
#dashboard_stats .inside {
1257
	margin: 10px 0 0 0 !important;
1258
}
1259
#dashboard_stats #stats-graph {
1260
	margin: 0;
1261
}
1262
#stats-info {
1263
	border-top: 1px solid #dfdfdf;
1264
	margin: 7px -10px 0 -10px;
1265
	padding: 10px;
1266
	background: #fcfcfc;
1267
	-moz-box-shadow:inset 0 1px 0 #fff;
1268
	-webkit-box-shadow:inset 0 1px 0 #fff;
1269
	box-shadow:inset 0 1px 0 #fff;
1270
	overflow: hidden;
1271
	border-radius: 0 0 2px 2px;
1272
	-webkit-border-radius: 0 0 2px 2px;
1273
	-moz-border-radius: 0 0 2px 2px;
1274
	-khtml-border-radius: 0 0 2px 2px;
1275
}
1276
#stats-info #top-posts, #stats-info #top-search {
1277
	float: left;
1278
	width: 50%;
1279
}
1280
#stats-info #top-posts {
1281
	padding-right: 3%;
1282
}
1283
#top-posts .stats-section-inner p {
1284
	white-space: nowrap;
1285
	overflow: hidden;
1286
}
1287
#top-posts .stats-section-inner p a {
1288
	overflow: hidden;
1289
	text-overflow: ellipsis;
1290
}
1291
#stats-info div#active {
1292
	border-top: 1px solid #dfdfdf;
1293
	margin: 0 -10px;
1294
	padding: 10px 10px 0 10px;
1295
	-moz-box-shadow:inset 0 1px 0 #fff;
1296
	-webkit-box-shadow:inset 0 1px 0 #fff;
1297
	box-shadow:inset 0 1px 0 #fff;
1298
	overflow: hidden;
1299
}
1300
#top-search p {
1301
	color: #999;
1302
}
1303
#stats-info h3 {
1304
	font-size: 1em;
1305
	margin: 0 0 .5em 0 !important;
1306
}
1307
#stats-info p {
1308
	margin: 0 0 .25em;
1309
	color: #999;
1310
}
1311
#stats-info p.widget-loading {
1312
	margin: 1em 0 0;
1313
	color: #333;
1314
}
1315
#stats-info p a {
1316
	display: block;
1317
}
1318
#stats-info p a.button {
1319
	display: inline;
1320
}
1321
/* ]]> */
1322
</style>
1323
<?php
1324
}
1325
1326
/**
1327
 * Stats Dashboard Widget Content.
1328
 *
1329
 * @access public
1330
 * @return void
1331
 */
1332
function stats_dashboard_widget_content() {
1333
	if ( ! isset( $_GET['width'] ) || ( ! $width = (int) ( $_GET['width'] / 2 ) ) || $width < 250 ) {
1334
		$width = 370;
1335
	}
1336
	if ( ! isset( $_GET['height'] ) || ( ! $height = (int) $_GET['height'] - 36 ) || $height < 230 ) {
1337
		$height = 180;
1338
	}
1339
1340
	$_width  = $width  - 5;
1341
	$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // Hack!
1342
1343
	$options = stats_dashboard_widget_options();
1344
	$blog_id = Jetpack_Options::get_option( 'id' );
1345
1346
	$q = array(
1347
		'noheader' => 'true',
1348
		'proxy' => '',
1349
		'blog' => $blog_id,
1350
		'page' => 'stats',
1351
		'chart' => '',
1352
		'unit' => $options['chart'],
1353
		'color' => get_user_option( 'admin_color' ),
1354
		'width' => $_width,
1355
		'height' => $_height,
1356
		'ssl' => is_ssl(),
1357
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
1358
	);
1359
1360
	$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
1361
1362
	$url = add_query_arg( $q, $url );
1363
	$method = 'GET';
1364
	$timeout = 90;
1365
	$user_id = JETPACK_MASTER_USER;
1366
1367
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1368
	$get_code = wp_remote_retrieve_response_code( $get );
1369
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1370
		stats_print_wp_remote_error( $get, $url );
1371
	} else {
1372
		$body = stats_convert_post_titles( $get['body'] );
1373
		$body = stats_convert_chart_urls( $body );
1374
		$body = stats_convert_image_urls( $body );
1375
		echo $body;
1376
	}
1377
1378
	$post_ids = array();
1379
1380
	$csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) );
1381
	$csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" );
1382
	/* Translators: Stats dashboard widget postviews list: "$post_title $views Views". */
1383
	$printf = __( '%1$s %2$s Views' , 'jetpack' );
1384
1385
	foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) {
1386
		if ( 0 === $post['post_id'] ) {
1387
			unset( $top_posts[$i] );
1388
			continue;
1389
		}
1390
		$post_ids[] = $post['post_id'];
1391
	}
1392
1393
	// Cache.
1394
	get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
1395
1396
	$searches = array();
1397
	foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) {
1398
		if ( 'encrypted_search_terms' === $search_term['searchterm'] ) {
1399
			continue;
1400
		}
1401
		$searches[] = esc_html( $search_term['searchterm'] );
1402
	}
1403
1404
?>
1405
<a class="button" href="admin.php?page=stats"><?php  esc_html_e( 'View All', 'jetpack' ); ?></a>
1406
<div id="stats-info">
1407
	<div id="top-posts" class='stats-section'>
1408
		<div class="stats-section-inner">
1409
		<h3 class="heading"><?php  esc_html_e( 'Top Posts' , 'jetpack' ); ?></h3>
1410
		<?php
1411
	if ( empty( $top_posts ) ) {
1412
?>
1413
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1414
			<?php
1415
	} else {
1416
		foreach ( $top_posts as $post ) {
1417
			if ( ! get_post( $post['post_id'] ) ) {
1418
				continue;
1419
			}
1420
?>
1421
				<p><?php printf(
1422
				$printf,
1423
				'<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',
1424
				number_format_i18n( $post['views'] )
1425
			); ?></p>
1426
				<?php
1427
		}
1428
	}
1429
?>
1430
		</div>
1431
	</div>
1432
	<div id="top-search" class='stats-section'>
1433
		<div class="stats-section-inner">
1434
		<h3 class="heading"><?php  esc_html_e( 'Top Searches' , 'jetpack' ); ?></h3>
1435
		<?php
1436
	if ( empty( $searches ) ) {
1437
?>
1438
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1439
			<?php
1440
	} else {
1441
?>
1442
			<p><?php echo join( ',&nbsp; ', $searches );?></p>
1443
			<?php
1444
	}
1445
?>
1446
		</div>
1447
	</div>
1448
</div>
1449
<div class="clear"></div>
1450
<?php
1451
	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...
1452
}
1453
1454
/**
1455
 * Stats Print WP Remote Error.
1456
 *
1457
 * @access public
1458
 * @param mixed $get Get.
1459
 * @param mixed $url URL.
1460
 * @return void
1461
 */
1462
function stats_print_wp_remote_error( $get, $url ) {
1463
	$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1464
	$previous_error = Jetpack::state( $state_name );
1465
	$error = md5( serialize( compact( 'get', 'url' ) ) );
1466
	Jetpack::state( $state_name, $error );
1467
	if ( $error !== $previous_error ) {
1468
?>
1469
	<div class="wrap">
1470
	<p><?php esc_html_e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
1471
	</div>
1472
<?php
1473
		return;
1474
	}
1475
?>
1476
	<div class="wrap">
1477
	<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>
1478
	<pre>
1479
	User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
1480
	Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
1481
	API URL: "<?php echo esc_url( $url ); ?>"
1482
<?php
1483
if ( is_wp_error( $get ) ) {
1484
	foreach ( $get->get_error_codes() as $code ) {
1485
		foreach ( $get->get_error_messages( $code ) as $message ) {
1486
?>
1487
<?php print $code . ': "' . $message . '"' ?>
1488
1489
<?php
1490
		}
1491
	}
1492
} else {
1493
	$get_code = wp_remote_retrieve_response_code( $get );
1494
	$content_length = strlen( wp_remote_retrieve_body( $get ) );
1495
?>
1496
Response code: "<?php print $get_code ?>"
1497
Content length: "<?php print $content_length ?>"
1498
1499
<?php
1500
}
1501
	?></pre>
1502
	</div>
1503
	<?php
1504
}
1505
1506
/**
1507
 * Get stats from WordPress.com
1508
 *
1509
 * @param string $table The stats which you want to retrieve: postviews, or searchterms.
1510
 * @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...
1511
 *      An associative array of arguments.
1512
 *
1513
 *      @type bool    $end        The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
1514
 *                                and default timezone is UTC date. Default value is Now.
1515
 *      @type string  $days       The length of the desired time frame. Default is 30. Maximum 90 days.
1516
 *      @type int     $limit      The maximum number of records to return. Default is 10. Maximum 100.
1517
 *      @type int     $post_id    The ID of the post to retrieve stats data for
1518
 *      @type string  $summarize  If present, summarizes all matching records. Default Null.
1519
 *
1520
 * }
1521
 *
1522
 * @return array {
1523
 *      An array of post view data, each post as an array
1524
 *
1525
 *      array {
1526
 *          The post view data for a single post
1527
 *
1528
 *          @type string  $post_id         The ID of the post
1529
 *          @type string  $post_title      The title of the post
1530
 *          @type string  $post_permalink  The permalink for the post
1531
 *          @type string  $views           The number of views for the post within the $num_days specified
1532
 *      }
1533
 * }
1534
 */
1535
function stats_get_csv( $table, $args = null ) {
1536
	$defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' );
1537
1538
	$args = wp_parse_args( $args, $defaults );
1539
	$args['table'] = $table;
1540
	$args['blog_id'] = Jetpack_Options::get_option( 'id' );
1541
1542
	$stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
1543
1544
	$key = md5( $stats_csv_url );
1545
1546
	// Get cache.
1547
	$stats_cache = get_option( 'stats_cache' );
1548
	if ( ! $stats_cache || ! is_array( $stats_cache ) ) {
1549
		$stats_cache = array();
1550
	}
1551
1552
	// Return or expire this key.
1553
	if ( isset( $stats_cache[ $key ] ) ) {
1554
		$time = key( $stats_cache[ $key ] );
1555
		if ( time() - $time < 300 ) {
1556
			return $stats_cache[ $key ][ $time ];
1557
		}
1558
		unset( $stats_cache[ $key ] );
1559
	}
1560
1561
	$stats_rows = array();
1562
	do {
1563
		if ( ! $stats = stats_get_remote_csv( $stats_csv_url ) ) {
1564
			break;
1565
		}
1566
1567
		$labels = array_shift( $stats );
1568
1569
		if ( 0 === stripos( $labels[0], 'error' ) ) {
1570
			break;
1571
		}
1572
1573
		$stats_rows = array();
1574
		for ( $s = 0; isset( $stats[ $s ] ); $s++ ) {
1575
			$row = array();
1576
			foreach ( $labels as $col => $label ) {
1577
				$row[ $label ] = $stats[ $s ][ $col ];
1578
			}
1579
			$stats_rows[] = $row;
1580
		}
1581
	} while ( 0 );
1582
1583
	// Expire old keys.
1584 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1585
		if ( ! is_array( $cache ) || 300 < time() - key( $cache ) ) {
1586
			unset( $stats_cache[ $k ] );
1587
		}
1588
	}
1589
1590
		// Set cache.
1591
		$stats_cache[ $key ] = array( time() => $stats_rows );
1592
	update_option( 'stats_cache', $stats_cache );
1593
1594
	return $stats_rows;
1595
}
1596
1597
/**
1598
 * Stats get remote CSV.
1599
 *
1600
 * @access public
1601
 * @param mixed $url URL.
1602
 * @return array
1603
 */
1604
function stats_get_remote_csv( $url ) {
1605
	$method = 'GET';
1606
	$timeout = 90;
1607
	$user_id = JETPACK_MASTER_USER;
1608
1609
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1610
	$get_code = wp_remote_retrieve_response_code( $get );
1611
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1612
		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...
1613
	} else {
1614
		return stats_str_getcsv( $get['body'] );
1615
	}
1616
}
1617
1618
/**
1619
 * Rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it.
1620
 *
1621
 * @access public
1622
 * @param mixed $csv CSV.
1623
 * @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...
1624
 */
1625
function stats_str_getcsv( $csv ) {
1626
	if ( function_exists( 'str_getcsv' ) ) {
1627
		$lines = str_getcsv( $csv, "\n" );
1628
		return array_map( 'str_getcsv', $lines );
1629
	}
1630
	if ( ! $temp = tmpfile() ) { // The tmpfile() automatically unlinks.
1631
		return false;
1632
	}
1633
1634
	$data = array();
1635
1636
	fwrite( $temp, $csv, strlen( $csv ) );
1637
	fseek( $temp, 0 );
1638
	while ( false !== $row = fgetcsv( $temp, 2000 ) ) {
1639
		$data[] = $row;
1640
	}
1641
	fclose( $temp );
1642
1643
	return $data;
1644
}
1645
1646
/**
1647
 * Abstract out building the rest api stats path.
1648
 *
1649
 * @param  string $resource Resource.
1650
 * @return string
1651
 */
1652
function jetpack_stats_api_path( $resource = '' ) {
1653
	$resource = ltrim( $resource, '/' );
1654
	return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
1655
}
1656
1657
/**
1658
 * Fetches stats data from the REST API.  Caches locally for 5 minutes.
1659
 *
1660
 * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
1661
 * @access public
1662
 * @param array  $args (default: array())  The args that are passed to the endpoint.
1663
 * @param string $resource (default: '') Optional sub-endpoint following /stats/.
1664
 * @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...
1665
 */
1666
function stats_get_from_restapi( $args = array(), $resource = '' ) {
1667
	$endpoint    = jetpack_stats_api_path( $resource );
1668
	$api_version = '1.1';
1669
	$args        = wp_parse_args( $args, array() );
1670
	$cache_key   = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) );
1671
1672
	// Get cache.
1673
	$stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() );
1674
	if ( ! is_array( $stats_cache ) ) {
1675
		$stats_cache = array();
1676
	}
1677
1678
	// Return or expire this key.
1679
	if ( isset( $stats_cache[ $cache_key ] ) ) {
1680
		$time = key( $stats_cache[ $cache_key ] );
1681
		if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) {
1682
			$cached_stats = $stats_cache[ $cache_key ][ $time ];
1683
			$cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats );
1684
			return $cached_stats;
1685
		}
1686
		unset( $stats_cache[ $cache_key ] );
1687
	}
1688
1689
	// Do the dirty work.
1690
	$response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
1691
	if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1692
		// If bad, just return it, don't cache.
1693
		return $response;
1694
	}
1695
1696
	$data = json_decode( wp_remote_retrieve_body( $response ) );
1697
1698
	// Expire old keys.
1699 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1700
		if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) {
1701
			unset( $stats_cache[ $k ] );
1702
		}
1703
	}
1704
1705
	// Set cache.
1706
	$stats_cache[ $cache_key ] = array(
1707
		time() => $data,
1708
	);
1709
	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...
1710
1711
	return $data;
1712
}
1713
1714
/**
1715
 * Load CSS needed for Stats column width in WP-Admin area.
1716
 *
1717
 * @since 4.7.0
1718
 */
1719
function jetpack_stats_load_admin_css() {
1720
	?>
1721
	<style type="text/css">
1722
		.fixed .column-stats {
1723
			width: 5em;
1724
		}
1725
	</style>
1726
	<?php
1727
}
1728
1729
/**
1730
 * Set header for column that allows to go to WordPress.com to see an entry's stats.
1731
 *
1732
 * @param array $columns An array of column names.
1733
 *
1734
 * @since 4.7.0
1735
 *
1736
 * @return mixed
1737
 */
1738
function jetpack_stats_post_table( $columns ) { // Adds a stats link on the edit posts page
1739
	if ( ! current_user_can( 'view_stats' ) || ! Jetpack::is_user_connected() ) {
1740
		return $columns;
1741
	}
1742
	// Array-Fu to add before comments
1743
	$pos = array_search( 'comments', array_keys( $columns ) );
1744
	if ( ! is_int( $pos ) ) {
1745
		return $columns;
1746
	}
1747
	$chunks             = array_chunk( $columns, $pos, true );
1748
	$chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
1749
1750
	return call_user_func_array( 'array_merge', $chunks );
1751
}
1752
1753
/**
1754
 * Set content for cell with link to an entry's stats in WordPress.com.
1755
 *
1756
 * @param string $column  The name of the column to display.
1757
 * @param int    $post_id The current post ID.
1758
 *
1759
 * @since 4.7.0
1760
 *
1761
 * @return mixed
1762
 */
1763
function jetpack_stats_post_table_cell( $column, $post_id ) {
1764
	if ( 'stats' == $column ) {
1765
		if ( 'publish' != get_post_status( $post_id ) ) {
1766
			printf(
1767
				'<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
1768
				esc_html__( 'No stats', 'jetpack' )
1769
			);
1770
		} else {
1771
			printf(
1772
				'<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
1773
				esc_url( "https://wordpress.com/stats/post/$post_id/" . Jetpack::build_raw_urls( get_home_url() ) ),
1774
				esc_html__( 'View stats for this post in WordPress.com', 'jetpack' )
1775
			);
1776
		}
1777
	}
1778
}
1779