Completed
Push — feature/settings-overhaul ( 9ca94b...7e5f0d )
by
unknown
33:48 queued 25:34
created

stats.php ➔ jetpack_stats_api_path()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
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
62
/**
63
 * Delay conditional for current_user_can to after init.
64
 *
65
 * @access public
66
 * @return void
67
 */
68
function stats_merged_widget_admin_init() {
69
	if ( current_user_can( 'view_stats' ) ) {
70
		add_action( 'load-index.php', 'stats_enqueue_dashboard_head' );
71
		add_action( 'wp_dashboard_setup', 'stats_register_widget_control_callback' ); // Hacky but works.
72
		add_action( 'jetpack_dashboard_widget', 'stats_jetpack_dashboard_widget' );
73
	}
74
}
75
76
/**
77
 * Enqueue Stats Dashboard
78
 *
79
 * @access public
80
 * @return void
81
 */
82
function stats_enqueue_dashboard_head() {
83
	add_action( 'admin_head', 'stats_dashboard_head' );
84
}
85
86
/**
87
 * Prevent sparkline img requests being redirected to upgrade.php.
88
 * See wp-admin/admin.php where it checks $wp_db_version.
89
 *
90
 * @access public
91
 * @param mixed $version Version.
92
 * @return string $version.
93
 */
94
function stats_ignore_db_version( $version ) {
95
	if (
96
		is_admin() &&
97
		isset( $_GET['page'] ) && 'stats' === $_GET['page'] &&
98
		isset( $_GET['chart'] ) && strpos($_GET['chart'], 'admin-bar-hours') === 0
99
	) {
100
		global $wp_db_version;
101
		return $wp_db_version;
102
	}
103
	return $version;
104
}
105
106
/**
107
 * Maps view_stats cap to read cap as needed.
108
 *
109
 * @access public
110
 * @param mixed $caps Caps.
111
 * @param mixed $cap Cap.
112
 * @param mixed $user_id User ID.
113
 * @return array Possibly mapped capabilities for meta capability.
114
 */
115
function stats_map_meta_caps( $caps, $cap, $user_id ) {
116
	// Map view_stats to exists.
117
	if ( 'view_stats' === $cap ) {
118
		$user        = new WP_User( $user_id );
119
		$user_role   = array_shift( $user->roles );
120
		$stats_roles = stats_get_option( 'roles' );
121
122
		// Is the users role in the available stats roles?
123
		if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles ) ) {
124
			$caps = array( 'read' );
125
		}
126
	}
127
128
	return $caps;
129
}
130
131
/**
132
 * Stats Template Redirect.
133
 *
134
 * @access public
135
 * @return void
136
 */
137
function stats_template_redirect() {
138
	global $current_user, $stats_footer;
139
140
	if ( is_feed() || is_robots() || is_trackback() || is_preview() ) {
141
		return;
142
	}
143
144
	// Should we be counting this user's views?
145
	if ( ! empty( $current_user->ID ) ) {
146
		$count_roles = stats_get_option( 'count_roles' );
147
		if ( ! array_intersect( $current_user->roles, $count_roles ) ) {
148
			return;
149
		}
150
	}
151
152
	add_action( 'wp_footer', 'stats_footer', 101 );
153
	add_action( 'wp_head', 'stats_add_shutdown_action' );
154
155
	$script = 'https://stats.wp.com/e-' . gmdate( 'YW' ) . '.js';
156
	$data = stats_build_view_data();
157
	$data_stats_array = stats_array( $data );
158
159
	$stats_footer = <<<END
160
<script type='text/javascript' src='{$script}' async defer></script>
161
<script type='text/javascript'>
162
	_stq = window._stq || [];
163
	_stq.push([ 'view', {{$data_stats_array}} ]);
164
	_stq.push([ 'clickTrackerInit', '{$data['blog']}', '{$data['post']}' ]);
165
</script>
166
167
END;
168
}
169
170
171
/**
172
 * Stats Build View Data.
173
 *
174
 * @access public
175
 * @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...
176
 */
177
function stats_build_view_data() {
178
	global $wp_the_query;
179
180
	$blog = Jetpack_Options::get_option( 'id' );
181
	$tz = get_option( 'gmt_offset' );
182
	$v = 'ext';
183
	$blog_url = parse_url( site_url() );
184
	$srv = $blog_url['host'];
185
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
186
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
187
		// Store and reset the queried_object and queried_object_id
188
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
189
		// Repro:
190
		// 1. Set home_url = https://ExamPle.com/
191
		// 2. Set show_on_front = page
192
		// 3. Set page_on_front = something
193
		// 4. Visit https://example.com/ !
194
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
195
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
196
		$post = $wp_the_query->get_queried_object_id();
197
		$wp_the_query->queried_object = $queried_object;
198
		$wp_the_query->queried_object_id = $queried_object_id;
199
	} else {
200
		$post = '0';
201
	}
202
203
	return compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
204
}
205
206
/**
207
 * Stats Add Shutdown Action.
208
 *
209
 * @access public
210
 * @return void
211
 */
212
function stats_add_shutdown_action() {
213
	// Just in case wp_footer isn't in your theme.
214
	add_action( 'shutdown',  'stats_footer', 101 );
215
}
216
217
/**
218
 * Stats Footer.
219
 *
220
 * @access public
221
 * @return void
222
 */
223
function stats_footer() {
224
	global $stats_footer;
225
	print $stats_footer;
226
	$stats_footer = '';
227
}
228
229
/**
230
 * Stats Get Options.
231
 *
232
 * @access public
233
 * @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...
234
 */
235
function stats_get_options() {
236
	$options = get_option( 'stats_options' );
237
238
	if ( ! isset( $options['version'] ) || $options['version'] < STATS_VERSION ) {
239
		$options = stats_upgrade_options( $options );
240
	}
241
242
	return $options;
243
}
244
245
/**
246
 * Get Stats Options.
247
 *
248
 * @access public
249
 * @param mixed $option Option.
250
 * @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...
251
 */
252
function stats_get_option( $option ) {
253
	$options = stats_get_options();
254
255
	if ( 'blog_id' === $option ) {
256
		return Jetpack_Options::get_option( 'id' );
257
	}
258
259
	if ( isset( $options[ $option ] ) ) {
260
		return $options[ $option ];
261
	}
262
263
	return null;
264
}
265
266
/**
267
 * Stats Set Options.
268
 *
269
 * @access public
270
 * @param mixed $option Option.
271
 * @param mixed $value Value.
272
 * @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...
273
 */
274
function stats_set_option( $option, $value ) {
275
	$options = stats_get_options();
276
277
	$options[ $option ] = $value;
278
279
	return stats_set_options( $options );
280
}
281
282
/**
283
 * Stats Set Options.
284
 *
285
 * @access public
286
 * @param mixed $options Options.
287
 * @return bool
288
 */
289
function stats_set_options( $options ) {
290
	return update_option( 'stats_options', $options );
291
}
292
293
/**
294
 * Stats Upgrade Options.
295
 *
296
 * @access public
297
 * @param mixed $options Options.
298
 * @return array|bool
299
 */
300
function stats_upgrade_options( $options ) {
301
	$defaults = array(
302
		'admin_bar'    => true,
303
		'roles'        => array( 'administrator' ),
304
		'count_roles'  => array(),
305
		'blog_id'      => Jetpack_Options::get_option( 'id' ),
306
		'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...
307
		'hide_smile'   => true,
308
	);
309
310
	if ( isset( $options['reg_users'] ) ) {
311
		if ( ! function_exists( 'get_editable_roles' ) ) {
312
			require_once ABSPATH . 'wp-admin/includes/user.php';
313
		}
314
		if ( $options['reg_users'] ) {
315
			$options['count_roles'] = array_keys( get_editable_roles() );
316
		}
317
		unset( $options['reg_users'] );
318
	}
319
320
	if ( is_array( $options ) && ! empty( $options ) ) {
321
		$new_options = array_merge( $defaults, $options );
322
	} else { $new_options = $defaults;
323
	}
324
325
	$new_options['version'] = STATS_VERSION;
326
327
	if ( ! stats_set_options( $new_options ) ) {
328
		return false;
329
	}
330
331
	stats_update_blog();
332
333
	return $new_options;
334
}
335
336
/**
337
 * Stats Array.
338
 *
339
 * @access public
340
 * @param mixed $kvs KVS.
341
 * @return array
342
 */
343
function stats_array( $kvs ) {
344
	/**
345
	 * Filter the options added to the JavaScript Stats tracking code.
346
	 *
347
	 * @module stats
348
	 *
349
	 * @since 1.1.0
350
	 *
351
	 * @param array $kvs Array of options about the site and page you're on.
352
	 */
353
	$kvs = apply_filters( 'stats_array', $kvs );
354
	$kvs = array_map( 'addslashes', $kvs );
355
	foreach ( $kvs as $k => $v ) {
356
		$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...
357
	}
358
	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...
359
}
360
361
/**
362
 * Admin Pages.
363
 *
364
 * @access public
365
 * @return void
366
 */
367
function stats_admin_menu() {
368
	global $pagenow;
369
370
	// If we're at an old Stats URL, redirect to the new one.
371
	// Don't even bother with caps, menu_page_url(), etc.  Just do it.
372
	if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'stats' === $_GET['page'] ) {
373
		$redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] );
374
		$relative_pos = strpos( $redirect_url, '/wp-admin/' );
375
		if ( false !== $relative_pos ) {
376
			wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
377
			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...
378
		}
379
	}
380
381
	$hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' );
382
	add_action( "load-$hook", 'stats_reports_load' );
383
}
384
385
/**
386
 * Stats Admin Path.
387
 *
388
 * @access public
389
 * @return string
390
 */
391
function stats_admin_path() {
392
	return Jetpack::module_configuration_url( __FILE__ );
393
}
394
395
/**
396
 * Stats Reports Load.
397
 *
398
 * @access public
399
 * @return void
400
 */
401
function stats_reports_load() {
402
	wp_enqueue_script( 'jquery' );
403
	wp_enqueue_script( 'postbox' );
404
	wp_enqueue_script( 'underscore' );
405
406
	add_action( 'admin_print_styles', 'stats_reports_css' );
407
408
	if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) {
409
		$parsed = parse_url( admin_url() );
410
		// Remember user doesn't want JS.
411
		setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days.
412
	}
413
414
	if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) {
415
		// Detect if JS is on.  If so, remove cookie so next page load is via JS.
416
		add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
417
	} else if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) {
418
		// Normal page load.  Load page content via JS.
419
		add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
420
	}
421
}
422
423
/**
424
 * Stats Reports CSS.
425
 *
426
 * @access public
427
 * @return void
428
 */
429
function stats_reports_css() {
430
?>
431
<style type="text/css">
432
#stats-loading-wrap p {
433
	text-align: center;
434
	font-size: 2em;
435
	margin: 7.5em 15px 0 0;
436
	height: 64px;
437
	line-height: 64px;
438
}
439
</style>
440
<?php
441
}
442
443
444
/**
445
 * Detect if JS is on.  If so, remove cookie so next page load is via JS.
446
 *
447
 * @access public
448
 * @return void
449
 */
450
function stats_js_remove_stnojs_cookie() {
451
	$parsed = parse_url( admin_url() );
452
?>
453
<script type="text/javascript">
454
/* <![CDATA[ */
455
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
456
/* ]]> */
457
</script>
458
<?php
459
}
460
461
/**
462
 * Normal page load.  Load page content via JS.
463
 *
464
 * @access public
465
 * @return void
466
 */
467
function stats_js_load_page_via_ajax() {
468
?>
469
<script type="text/javascript">
470
/* <![CDATA[ */
471
if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
472
	jQuery( function( $ ) {
473
		$.get( document.location.href + '&noheader', function( responseText ) {
474
			$( '#stats-loading-wrap' ).replaceWith( responseText );
475
		} );
476
	} );
477
}
478
/* ]]> */
479
</script>
480
<?php
481
}
482
483
/**
484
 * Stats Report Page.
485
 *
486
 * @access public
487
 * @param bool $main_chart_only (default: false) Main Chart Only.
488
 */
489
function stats_reports_page( $main_chart_only = false ) {
490
491
	if ( isset( $_GET['dashboard'] ) ) {
492
		return stats_dashboard_widget_content();
493
	}
494
495
	$blog_id = stats_get_option( 'blog_id' );
496
	$domain = Jetpack::build_raw_urls( get_home_url() );
497
498
	if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) {
499
		$nojs_url = add_query_arg( 'nojs', '1' );
500
		$http = is_ssl() ? 'https' : 'http';
501
		// Loading message. No JS fallback message.
502
?>
503
<div class="wrap">
504
	<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>
505
</div>
506
<div id="stats-loading-wrap" class="wrap">
507
<p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php
508
		echo esc_url(
509
			/**
510
			 * Sets external resource URL.
511
			 *
512
			 * @module stats
513
			 *
514
			 * @since 1.4.0
515
			 *
516
			 * @param string $args URL of external resource.
517
			 */
518
			apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" )
519
		); ?>" /></p>
520
<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>
521
<p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
522
<a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without JavaScript', 'jetpack' ); ?></a>.</p>
523
</div>
524
<?php
525
		return;
526
	}
527
528
	$day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false;
529
	$q = array(
530
		'noheader' => 'true',
531
		'proxy' => '',
532
		'page' => 'stats',
533
		'day' => $day,
534
		'blog' => $blog_id,
535
		'charset' => get_option( 'blog_charset' ),
536
		'color' => get_user_option( 'admin_color' ),
537
		'ssl' => is_ssl(),
538
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
539
	);
540
	if ( get_locale() !== 'en_US' ) {
541
		$q['jp_lang'] = get_locale();
542
	}
543
	// Only show the main chart, without extra header data, or metaboxes.
544
	$q['main_chart_only'] = $main_chart_only;
545
	$args = array(
546
		'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),
547
		'numdays' => 'int',
548
		'day' => 'date',
549
		'unit' => array( 1, 7, 31, 'human' ),
550
		'humanize' => array( 'true' ),
551
		'num' => 'int',
552
		'summarize' => null,
553
		'post' => 'int',
554
		'width' => 'int',
555
		'height' => 'int',
556
		'data' => 'data',
557
		'blog_subscribers' => 'int',
558
		'comment_subscribers' => null,
559
		'type' => array( 'wpcom', 'email', 'pending' ),
560
		'pagenum' => 'int',
561
	);
562
	foreach ( $args as $var => $vals ) {
563
		if ( ! isset( $_REQUEST[$var] ) )
564
			continue;
565
		if ( is_array( $vals ) ) {
566
			if ( in_array( $_REQUEST[$var], $vals ) )
567
				$q[$var] = $_REQUEST[$var];
568
		} elseif ( 'int' === $vals ) {
569
			$q[$var] = intval( $_REQUEST[$var] );
570
		} elseif ( 'date' === $vals ) {
571
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) )
572
				$q[$var] = $_REQUEST[$var];
573
		} elseif ( null === $vals ) {
574
			$q[$var] = '';
575
		} elseif ( 'data' === $vals ) {
576
			if ( 'index.php' === substr( $_REQUEST[$var], 0, 9 ) )
577
				$q[$var] = $_REQUEST[$var];
578
		}
579
	}
580
581
	if ( isset( $_GET['chart'] ) ) {
582
		if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) {
583
			$chart = sanitize_title( $_GET['chart'] );
584
			$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
585
		}
586
	} else {
587
		$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
588
	}
589
590
	$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...
591
	$method = 'GET';
592
	$timeout = 90;
593
	$user_id = JETPACK_MASTER_USER; // means send the wp.com user_id
594
595
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
596
	$get_code = wp_remote_retrieve_response_code( $get );
597
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
598
		stats_print_wp_remote_error( $get, $url );
599
	} else {
600
		if ( ! empty( $get['headers']['content-type'] ) ) {
601
			$type = $get['headers']['content-type'];
602
			if ( substr( $type, 0, 5 ) === 'image' ) {
603
				$img = $get['body'];
604
				header( 'Content-Type: ' . $type );
605
				header( 'Content-Length: ' . strlen( $img ) );
606
				echo $img;
607
				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...
608
			}
609
		}
610
		$body = stats_convert_post_titles( $get['body'] );
611
		$body = stats_convert_chart_urls( $body );
612
		$body = stats_convert_image_urls( $body );
613
		$body = stats_convert_admin_urls( $body );
614
		echo $body;
615
	}
616
617
	if ( isset( $_GET['page'] ) && 'stats' === $_GET['page'] && ! isset( $_GET['chart'] ) ) {
618
		JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_stats' ) );
619
	}
620
621
	if ( isset( $_GET['noheader'] ) ) {
622
		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...
623
	}
624
}
625
626
/**
627
 * Stats Convert Admin Urls.
628
 *
629
 * @access public
630
 * @param mixed $html HTML.
631
 * @return string
632
 */
633
function stats_convert_admin_urls( $html ) {
634
	return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
635
}
636
637
/**
638
 * Stats Convert Image URLs.
639
 *
640
 * @access public
641
 * @param mixed $html HTML.
642
 * @return string
643
 */
644
function stats_convert_image_urls( $html ) {
645
	$url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER );
646
	$html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html );
647
	return $html;
648
}
649
650
/**
651
 * Stats Convert Chart URLs.
652
 *
653
 * @access public
654
 * @param mixed $html HTML.
655
 * @return string
656
 */
657
function stats_convert_chart_urls( $html ) {
658
	$html = preg_replace_callback( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
659
		create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

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

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

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