Completed
Push — add/blogname-locale-connect-ur... ( 948a6a...8e4901 )
by
unknown
30:01 queued 20:34
created

stats.php ➔ stats_reports_page()   F

Complexity

Conditions 30
Paths 1853

Size

Total Lines 119
Code Lines 94

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 30
eloc 94
nc 1853
nop 1
dl 0
loc 119
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Module Name: Site Stats
4
 * Module Description: Collect 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: Recommended, Traffic
12
 * Additional Search Queries: statistics, tracking, analytics, views, traffic, stats
13
 */
14
15
if ( defined( 'STATS_VERSION' ) ) {
16
	return;
17
}
18
19
define( 'STATS_VERSION', '9' );
20
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...
21
22
add_action( 'jetpack_modules_loaded', 'stats_load' );
23
24
// Tell HQ about changed settings
25
Jetpack_Sync::sync_options( __FILE__,
26
	'stats_options',
27
	'home',
28
	'siteurl',
29
	'blogname',
30
	'blogdescription',
31
	'gmt_offset',
32
	'timezone_string',
33
	'page_on_front',
34
	'permalink_structure',
35
	'category_base',
36
	'tag_base'
37
);
38
39
function stats_load() {
40
	global $wp_roles;
41
42
	Jetpack::enable_module_configurable( __FILE__ );
43
	Jetpack::module_configuration_load( __FILE__, 'stats_configuration_load' );
44
	Jetpack::module_configuration_head( __FILE__, 'stats_configuration_head' );
45
	Jetpack::module_configuration_screen( __FILE__, 'stats_configuration_screen' );
46
47
	// Tell HQ about changed posts
48
	$post_stati = get_post_stati( array( 'public' => true ) ); // All public post stati
49
	$post_stati[] = 'private';                                 // Content from private stati will be redacted
50
	Jetpack_Sync::sync_posts( __FILE__, array(
51
		'post_types' => get_post_types( array( 'public' => true ) ), // All public post types
52
		'post_stati' => $post_stati,
53
	) );
54
55
	// Generate the tracking code after wp() has queried for posts.
56
	add_action( 'template_redirect', 'stats_template_redirect', 1 );
57
58
	add_action( 'wp_head', 'stats_admin_bar_head', 100 );
59
60
	add_action( 'wp_head', 'stats_hide_smile_css' );
61
62
	add_action( 'jetpack_admin_menu', 'stats_admin_menu' );
63
64
	// Map stats caps
65
	add_filter( 'map_meta_cap', 'stats_map_meta_caps', 10, 4 );
66
67
	if ( isset( $_GET['oldwidget'] ) ) {
68
		// Old one.
69
		add_action( 'wp_dashboard_setup', 'stats_register_dashboard_widget' );
70
	} else {
71
		add_action( 'admin_init', 'stats_merged_widget_admin_init' );
72
	}
73
74
	add_filter( 'jetpack_xmlrpc_methods', 'stats_xmlrpc_methods' );
75
76
77
	add_filter( 'pre_option_db_version', 'stats_ignore_db_version' );
78
}
79
80
/**
81
 * Delay conditional for current_user_can to after init.
82
 */
83
function stats_merged_widget_admin_init() {
84
	if ( current_user_can( 'view_stats' ) ) {
85
		add_action( 'load-index.php', 'stats_enqueue_dashboard_head' );
86
		add_action( 'wp_dashboard_setup', 'stats_register_widget_control_callback' ); // hacky but works
87
		add_action( 'jetpack_dashboard_widget', 'stats_jetpack_dashboard_widget' );
88
	}
89
}
90
91
function stats_enqueue_dashboard_head() {
92
	add_action( 'admin_head', 'stats_dashboard_head' );
93
}
94
95
/**
96
 * Prevent sparkline img requests being redirected to upgrade.php.
97
 * See wp-admin/admin.php where it checks $wp_db_version.
98
 */
99
function stats_ignore_db_version( $version ) {
100
	if (
101
		is_admin() &&
102
		isset( $_GET['page'] ) && $_GET['page'] == 'stats' &&
103
		isset( $_GET['chart'] ) && strpos($_GET['chart'], 'admin-bar-hours') === 0
104
	) {
105
		global $wp_db_version;
106
		return $wp_db_version;
107
	}
108
	return $version;
109
}
110
111
/**
112
 * Maps view_stats cap to read cap as needed
113
 *
114
 * @return array Possibly mapped capabilities for meta capability
115
 */
116
function stats_map_meta_caps( $caps, $cap, $user_id, $args ) {
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
117
	// Map view_stats to exists
118
	if ( 'view_stats' == $cap ) {
119
		$user        = new WP_User( $user_id );
120
		$user_role   = array_shift( $user->roles );
121
		$stats_roles = stats_get_option( 'roles' );
122
123
		// Is the users role in the available stats roles?
124
		if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles ) ) {
125
			$caps = array( 'read' );
126
		}
127
	}
128
129
	return $caps;
130
}
131
132
function stats_template_redirect() {
133
	global $current_user, $stats_footer;
134
135
	if ( is_feed() || is_robots() || is_trackback() || is_preview() )
136
		return;
137
138
	// Should we be counting this user's views?
139
	if ( !empty( $current_user->ID ) ) {
140
		$count_roles = stats_get_option( 'count_roles' );
141
		if ( ! array_intersect( $current_user->roles, $count_roles ) )
142
			return;
143
	}
144
145
	add_action( 'wp_footer', 'stats_footer', 101 );
146
	add_action( 'wp_head', 'stats_add_shutdown_action' );
147
148
	$script = set_url_scheme( '//stats.wp.com/e-' . gmdate( 'YW' ) . '.js' );
149
	$data = stats_build_view_data();
150
	$data_stats_array = stats_array( $data );
151
152
	$stats_footer = <<<END
153
<script type='text/javascript' src='{$script}' async defer></script>
154
<script type='text/javascript'>
155
	_stq = window._stq || [];
156
	_stq.push([ 'view', {{$data_stats_array}} ]);
157
	_stq.push([ 'clickTrackerInit', '{$data['blog']}', '{$data['post']}' ]);
158
</script>
159
160
END;
161
}
162
163
function stats_build_view_data() {
164
	global $wp_the_query;
165
166
	$blog = Jetpack_Options::get_option( 'id' );
167
	$tz = get_option( 'gmt_offset' );
168
	$v = 'ext';
169
	$blog_url = parse_url( site_url() );
170
	$srv = $blog_url['host'];
171
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
172
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
173
		// Store and reset the queried_object and queried_object_id
174
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
175
		// Repro:
176
		// 1. Set home_url = http://ExamPle.com/
177
		// 2. Set show_on_front = page
178
		// 3. Set page_on_front = something
179
		// 4. Visit http://example.com/
180
181
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
182
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
183
		$post = $wp_the_query->get_queried_object_id();
184
		$wp_the_query->queried_object = $queried_object;
185
		$wp_the_query->queried_object_id = $queried_object_id;
186
	} else {
187
		$post = '0';
188
	}
189
190
	return compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
191
}
192
193
function stats_add_shutdown_action() {
194
	// just in case wp_footer isn't in your theme
195
	add_action( 'shutdown',  'stats_footer', 101 );
196
}
197
198
function stats_footer() {
199
	global $stats_footer;
200
	print $stats_footer;
201
	$stats_footer = '';
202
}
203
204
function stats_get_options() {
205
	$options = get_option( 'stats_options' );
206
207
	if ( !isset( $options['version'] ) || $options['version'] < STATS_VERSION )
208
		$options = stats_upgrade_options( $options );
209
210
	return $options;
211
}
212
213
function stats_get_option( $option ) {
214
	$options = stats_get_options();
215
216
	if ( $option == 'blog_id' )
217
		return Jetpack_Options::get_option( 'id' );
218
219
	if ( isset( $options[$option] ) )
220
		return $options[$option];
221
222
	return null;
223
}
224
225
function stats_set_option( $option, $value ) {
226
	$options = stats_get_options();
227
228
	$options[$option] = $value;
229
230
	stats_set_options($options);
231
}
232
233
function stats_set_options($options) {
234
	update_option( 'stats_options', $options );
235
}
236
237
function stats_upgrade_options( $options ) {
238
	$defaults = array(
239
		'admin_bar'    => true,
240
		'roles'        => array( 'administrator' ),
241
		'count_roles'  => array(),
242
		'blog_id'      => Jetpack_Options::get_option( 'id' ),
243
		'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...
244
		'hide_smile'   => true,
245
	);
246
247
	if ( isset( $options['reg_users'] ) ) {
248
		if ( ! function_exists( 'get_editable_roles' ) )
249
			require_once( ABSPATH . 'wp-admin/includes/user.php' );
250
		if ( $options['reg_users'] )
251
			$options['count_roles'] = array_keys( get_editable_roles() );
252
		unset( $options['reg_users'] );
253
	}
254
255
	if ( is_array( $options ) && !empty( $options ) )
256
		$new_options = array_merge( $defaults, $options );
257
	else
258
		$new_options = $defaults;
259
260
	$new_options['version'] = STATS_VERSION;
261
262
	stats_set_options( $new_options );
263
264
	stats_update_blog();
265
266
	return $new_options;
267
}
268
269
function stats_array( $kvs ) {
270
	/**
271
	 * Filter the options added to the JavaScript Stats tracking code.
272
	 *
273
	 * @module stats
274
	 *
275
	 * @since 1.1.0
276
	 *
277
	 * @param array $kvs Array of options about the site and page you're on.
278
	 */
279
	$kvs = apply_filters( 'stats_array', $kvs );
280
	$kvs = array_map( 'addslashes', $kvs );
281
	foreach ( $kvs as $k => $v )
282
		$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...
283
	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...
284
}
285
286
/**
287
 * Admin Pages
288
 */
289
function stats_admin_menu() {
290
	global $pagenow;
291
292
	// If we're at an old Stats URL, redirect to the new one.
293
	// Don't even bother with caps, menu_page_url(), etc.  Just do it.
294
	if ( 'index.php' == $pagenow && isset( $_GET['page'] ) && 'stats' == $_GET['page'] ) {
295
		$redirect_url =	str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] );
296
		$relative_pos = strpos(	$redirect_url, '/wp-admin/' );
297
		if ( false !== $relative_pos ) {
298
			wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
299
			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...
300
		}
301
	}
302
303
	$hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' );
304
	add_action( "load-$hook", 'stats_reports_load' );
305
}
306
307
function stats_admin_path() {
308
	return Jetpack::module_configuration_url( __FILE__ );
309
}
310
311
function stats_reports_load() {
312
	wp_enqueue_script( 'jquery' );
313
	wp_enqueue_script( 'postbox' );
314
	wp_enqueue_script( 'underscore' );
315
316
	add_action( 'admin_print_styles', 'stats_reports_css' );
317
318
	if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) {
319
		$parsed = parse_url( admin_url() );
320
		// Remember user doesn't want JS
321
		setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days
322
	}
323
324
	if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) {
325
		// Detect if JS is on.  If so, remove cookie so next page load is via JS
326
		add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
327
	} else if ( !isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) {
328
		// Normal page load.  Load page content via JS.
329
		add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
330
	}
331
}
332
333
function stats_reports_css() {
334
?>
335
<style type="text/css">
336
#stats-loading-wrap p {
337
	text-align: center;
338
	font-size: 2em;
339
	margin: 7.5em 15px 0 0;
340
	height: 64px;
341
	line-height: 64px;
342
}
343
</style>
344
<?php
345
}
346
347
// Detect if JS is on.  If so, remove cookie so next page load is via JS.
348
function stats_js_remove_stnojs_cookie() {
349
	$parsed = parse_url( admin_url() );
350
?>
351
<script type="text/javascript">
352
/* <![CDATA[ */
353
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
354
/* ]]> */
355
</script>
356
<?php
357
}
358
359
// Normal page load.  Load page content via JS.
360
function stats_js_load_page_via_ajax() {
361
?>
362
<script type="text/javascript">
363
/* <![CDATA[ */
364
if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
365
	jQuery( function( $ ) {
366
		$.get( document.location.href + '&noheader', function( responseText ) {
367
			$( '#stats-loading-wrap' ).replaceWith( responseText );
368
		} );
369
	} );
370
}
371
/* ]]> */
372
</script>
373
<?php
374
}
375
376
function stats_reports_page( $main_chart_only = false ) {
377
	if ( isset( $_GET['dashboard'] ) )
378
		return stats_dashboard_widget_content();
379
380
	$blog_id = stats_get_option( 'blog_id' );
381
	$domain = Jetpack::build_raw_urls( get_home_url() );
382
383
	if ( ! $main_chart_only && !isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) {
384
		$nojs_url = add_query_arg( 'nojs', '1' );
385
		$http = is_ssl() ? 'https' : 'http';
386
		// Loading message
387
		// No JS fallback message
388
?>
389
<div class="wrap">
390
	<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>
391
</div>
392
<div id="stats-loading-wrap" class="wrap">
393
<p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php
394
/** This filter is documented in modules/shortcodes/audio.php */
395
echo esc_url( apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" ) ); ?>" /></p>
396
<p style="font-size: 11pt; margin: 0;"><a href="https://wordpress.com/stats/<?php echo $domain; ?>" target="_blank"><?php esc_html_e( 'View stats on WordPress.com right now', 'jetpack' ); ?></a></p>
397
<p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
398
<a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without JavaScript', 'jetpack' ); ?></a>.</p>
399
</div>
400
<?php
401
		return;
402
	}
403
404
	$day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false;
405
	$q = array(
406
		'noheader' => 'true',
407
		'proxy' => '',
408
		'page' => 'stats',
409
		'day' => $day,
410
		'blog' => $blog_id,
411
		'charset' => get_option( 'blog_charset' ),
412
		'color' => get_user_option( 'admin_color' ),
413
		'ssl' => is_ssl(),
414
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
415
	);
416
	if ( get_locale() !== 'en_US' ) {
417
		$q['jp_lang'] = get_locale();
418
	}
419
	// Only show the main chart, without extra header data, or metaboxes.
420
	$q['main_chart_only'] = $main_chart_only;
421
	$args = array(
422
		'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),
423
		'numdays' => 'int',
424
		'day' => 'date',
425
		'unit' => array( 1, 7, 31, 'human' ),
426
		'humanize' => array( 'true' ),
427
		'num' => 'int',
428
		'summarize' => null,
429
		'post' => 'int',
430
		'width' => 'int',
431
		'height' => 'int',
432
		'data' => 'data',
433
		'blog_subscribers' => 'int',
434
		'comment_subscribers' => null,
435
		'type' => array( 'wpcom', 'email', 'pending' ),
436
		'pagenum' => 'int',
437
	);
438
	foreach ( $args as $var => $vals ) {
439
		if ( !isset( $_REQUEST[$var] ) )
440
			continue;
441
		if ( is_array( $vals ) ) {
442
			if ( in_array( $_REQUEST[$var], $vals ) )
443
				$q[$var] = $_REQUEST[$var];
444
		} elseif ( $vals == 'int' ) {
445
			$q[$var] = intval( $_REQUEST[$var] );
446
		} elseif ( $vals == 'date' ) {
447
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) )
448
				$q[$var] = $_REQUEST[$var];
449
		} elseif ( $vals == null ) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $vals of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
450
			$q[$var] = '';
451
		} elseif ( $vals == 'data' ) {
452
			if ( substr( $_REQUEST[$var], 0, 9 ) == 'index.php' )
453
				$q[$var] = $_REQUEST[$var];
454
		}
455
	}
456
457
	if ( isset( $_GET['chart'] ) ) {
458
		if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) {
459
			$chart = sanitize_title( $_GET['chart'] );
460
			$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
461
		}
462
	} else {
463
		$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
464
	}
465
466
	$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...
467
	$method = 'GET';
468
	$timeout = 90;
469
	$user_id = JETPACK_MASTER_USER; // means send the wp.com user_id
470
471
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
472
	$get_code = wp_remote_retrieve_response_code( $get );
473
	if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) {
474
		stats_print_wp_remote_error( $get, $url );
475
	} else {
476
		if ( !empty( $get['headers']['content-type'] ) ) {
477
			$type = $get['headers']['content-type'];
478
			if ( substr( $type, 0, 5 ) == 'image' ) {
479
				$img = $get['body'];
480
				header( 'Content-Type: ' . $type );
481
				header( 'Content-Length: ' . strlen( $img ) );
482
				echo $img;
483
				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...
484
			}
485
		}
486
		$body = stats_convert_post_titles( $get['body'] );
487
		$body = stats_convert_chart_urls( $body );
488
		$body = stats_convert_image_urls( $body );
489
		$body = stats_convert_admin_urls( $body );
490
		echo $body;
491
	}
492
	if ( isset( $_GET['noheader'] ) )
493
		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...
494
}
495
496
function stats_convert_admin_urls( $html ) {
497
	return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
498
}
499
500
function stats_convert_image_urls( $html ) {
501
	$url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER );
502
	$html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html );
503
	return $html;
504
}
505
506
function stats_convert_chart_urls( $html ) {
507
	$html = preg_replace_callback( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
508
			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...
509
				'$matches',
510
				// If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string
511
				'return "admin.php?page=stats&noheader&chart=" . $matches[1] . str_replace( "?", "&", $matches[2] );'
512
			),
513
			$html );
514
	return $html;
515
}
516
517
function stats_convert_post_titles( $html ) {
518
	global $wpdb, $stats_posts;
519
	$pattern = "<span class='post-(\d+)-link'>.*?</span>";
520
	if ( !preg_match_all( "!$pattern!", $html, $matches ) )
521
		return $html;
522
	$posts = get_posts( array(
523
		'include' => implode( ',', $matches[1] ),
524
		'post_type' => 'any',
525
		'post_status' => 'any',
526
		'numberposts' => -1,
527
	));
528
	foreach ( $posts as $post )
529
		$stats_posts[$post->ID] = $post;
530
	$html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html );
531
	return $html;
532
}
533
534
function stats_convert_post_title( $matches ) {
535
	global $stats_posts;
536
	$post_id = $matches[1];
537
	if ( isset( $stats_posts[$post_id] ) )
538
		return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>';
539
	return $matches[0];
540
}
541
542
function stats_configuration_load() {
543
	if ( isset( $_POST['action'] ) && $_POST['action'] == 'save_options' && $_POST['_wpnonce'] == wp_create_nonce( 'stats' ) ) {
544
		$options = stats_get_options();
545
		$options['admin_bar']  = isset( $_POST['admin_bar']  ) && $_POST['admin_bar'];
546
		$options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile'];
547
548
		$options['roles'] = array( 'administrator' );
549 View Code Duplication
		foreach ( get_editable_roles() as $role => $details )
550
			if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] )
551
				$options['roles'][] = $role;
552
553
		$options['count_roles'] = array();
554 View Code Duplication
		foreach ( get_editable_roles() as $role => $details )
555
			if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] )
556
				$options['count_roles'][] = $role;
557
558
		stats_set_options( $options );
559
		stats_update_blog();
560
		Jetpack::state( 'message', 'module_configured' );
561
		wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) );
562
		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...
563
	}
564
}
565
566
function stats_configuration_head() {
567
	?>
568
	<style type="text/css">
569
		#statserror {
570
			border: 1px solid #766;
571
			background-color: #d22;
572
			padding: 1em 3em;
573
		}
574
		.stats-smiley {
575
			vertical-align: 1px;
576
		}
577
	</style>
578
	<?php
579
}
580
581
function stats_configuration_screen() {
582
	$options = stats_get_options();
583
	?>
584
	<div class="narrow">
585
		<p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p>
586
		<form method="post">
587
		<input type='hidden' name='action' value='save_options' />
588
		<?php wp_nonce_field( 'stats' ); ?>
589
		<table id="menu" class="form-table">
590
		<tr valign="top"><th scope="row"><label for="admin_bar"><?php _e( 'Admin bar' , 'jetpack' ); ?></label></th>
591
		<td><label><input type='checkbox'<?php checked( $options['admin_bar'] ); ?> name='admin_bar' id='admin_bar' /> <?php _e( "Put a chart showing 48 hours of views in the admin bar.", 'jetpack' ); ?></label></td></tr>
592
		<tr valign="top"><th scope="row"><?php _e( 'Registered users', 'jetpack' ); ?></th>
593
		<td>
594
			<?php _e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/>
595
			<?php
596
			$count_roles = stats_get_option( 'count_roles' );
597
			foreach ( get_editable_roles() as $role => $details ) {
598
				?>
599
				<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/>
600
				<?php
601
			}
602
			?>
603
		</td></tr>
604
		<tr valign="top"><th scope="row"><?php _e( 'Smiley' , 'jetpack' ); ?></th>
605
		<td><label><input type='checkbox'<?php checked( isset( $options['hide_smile'] ) && $options['hide_smile'] ); ?> name='hide_smile' id='hide_smile' /> <?php _e( 'Hide the stats smiley face image.', 'jetpack' ); ?></label><br /> <span class="description"><?php _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>
606
		<tr valign="top"><th scope="row"><?php _e( 'Report visibility' , 'jetpack' ); ?></th>
607
		<td>
608
			<?php _e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/>
609
			<?php
610
			$stats_roles = stats_get_option( 'roles' );
611
			foreach ( get_editable_roles() as $role => $details ) {
612
				?>
613
				<label><input type='checkbox' <?php if ( $role == 'administrator' ) echo "disabled='disabled' "; ?>name='role_<?php echo $role; ?>'<?php checked( $role == 'administrator' || in_array( $role, $stats_roles ) ); ?> /> <?php echo translate_user_role( $details['name'] ); ?></label><br/>
614
				<?php
615
			}
616
			?>
617
		</td></tr>
618
		</table>
619
		<p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p>
620
		</form>
621
	</div>
622
	<?php
623
}
624
625
function stats_hide_smile_css() {
626
	$options = stats_get_options();
627
	if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) {
628
	?>
629
<style type='text/css'>img#wpstats{display:none}</style><?php
630
	}
631
}
632
633
function stats_admin_bar_head() {
634
	if ( !stats_get_option( 'admin_bar' ) )
635
		return;
636
637
	if ( !current_user_can( 'view_stats' ) )
638
		return;
639
640
	if ( function_exists( 'is_admin_bar_showing' ) && !is_admin_bar_showing() ) {
641
		return;
642
	}
643
644
	add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 );
645
	?>
646
647
<style type='text/css'>
648
#wpadminbar .quicklinks li#wp-admin-bar-stats {
649
	height: 28px;
650
}
651
#wpadminbar .quicklinks li#wp-admin-bar-stats a {
652
	height: 28px;
653
	padding: 0;
654
}
655
#wpadminbar .quicklinks li#wp-admin-bar-stats a div {
656
	height: 28px;
657
	width: 95px;
658
	overflow: hidden;
659
	margin: 0 10px;
660
}
661
#wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
662
	width: auto;
663
	margin: 0 8px 0 10px;
664
}
665
#wpadminbar .quicklinks li#wp-admin-bar-stats a img {
666
	height: 24px;
667
	padding: 2px 0;
668
	max-width: none;
669
	border: none;
670
}
671
</style>
672
<?php
673
}
674
675
function stats_admin_bar_menu( &$wp_admin_bar ) {
676
	$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
677
678
	$img_src = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale' ), $url ) );
679
	$img_src_2x = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale-2x' ), $url ) );
680
681
	$alt = esc_attr( __( 'Stats', 'jetpack' ) );
682
683
	$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
684
685
	$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 );
686
687
	$wp_admin_bar->add_menu( $menu );
688
}
689
690
function stats_update_blog() {
691
	Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() );
692
}
693
694
function stats_get_blog() {
695
	$home = parse_url( trailingslashit( get_option( 'home' ) ) );
696
	$blog = array(
697
		'host'                => $home['host'],
698
		'path'                => $home['path'],
699
		'blogname'            => get_option( 'blogname' ),
700
		'blogdescription'     => get_option( 'blogdescription' ),
701
		'siteurl'             => get_option( 'siteurl' ),
702
		'gmt_offset'          => get_option( 'gmt_offset' ),
703
		'timezone_string'     => get_option( 'timezone_string' ),
704
		'stats_version'       => STATS_VERSION,
705
		'stats_api'           => 'jetpack',
706
		'page_on_front'       => get_option( 'page_on_front' ),
707
		'permalink_structure' => get_option( 'permalink_structure' ),
708
		'category_base'       => get_option( 'category_base' ),
709
		'tag_base'            => get_option( 'tag_base' ),
710
	);
711
	$blog = array_merge( stats_get_options(), $blog );
712
	unset( $blog['roles'], $blog['blog_id'] );
713
	return stats_esc_html_deep( $blog );
714
}
715
716
/**
717
 * Modified from stripslashes_deep()
718
 */
719
function stats_esc_html_deep( $value ) {
720
	if ( is_array( $value ) ) {
721
		$value = array_map( 'stats_esc_html_deep', $value );
722
	} elseif ( is_object( $value ) ) {
723
		$vars = get_object_vars( $value );
724
		foreach ( $vars as $key => $data ) {
725
			$value->{$key} = stats_esc_html_deep( $data );
726
		}
727
	} elseif ( is_string( $value ) ) {
728
		$value = esc_html( $value );
729
	}
730
731
	return $value;
732
}
733
734
function stats_xmlrpc_methods( $methods ) {
735
	$my_methods = array(
736
		'jetpack.getBlog' => 'stats_get_blog',
737
	);
738
739
	return array_merge( $methods, $my_methods );
740
}
741
742
function stats_register_dashboard_widget() {
743
	if ( ! current_user_can( 'view_stats' ) )
744
		return;
745
746
	// wp_dashboard_empty: we load in the content after the page load via JS
747
	wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' );
748
749
	add_action( 'admin_head', 'stats_dashboard_head' );
750
}
751
752
function stats_dashboard_widget_options() {
753
	$defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 );
754
	if ( ( !$options = get_option( 'stats_dashboard_widget' ) ) || !is_array( $options ) )
755
		$options = array();
756
757
	// Ignore obsolete option values
758
	$intervals = array( 1, 7, 31, 90, 365 );
759
	foreach ( array( 'top', 'search' ) as $key )
760
		if ( isset( $options[$key] ) && !in_array( $options[$key], $intervals ) )
761
			unset( $options[$key] );
762
763
	return array_merge( $defaults, $options );
764
}
765
766
function stats_dashboard_widget_control() {
767
	$periods   = array(
768
		'1' => __( 'day', 'jetpack' ),
769
		'7' => __( 'week', 'jetpack' ),
770
		'31' => __( 'month', 'jetpack' ),
771
	);
772
	$intervals = array(
773
		'1' => __( 'the past day', 'jetpack' ),
774
		'7' => __( 'the past week', 'jetpack' ),
775
		'31' => __( 'the past month', 'jetpack' ),
776
		'90' => __( 'the past quarter', 'jetpack' ),
777
		'365' => __( 'the past year', 'jetpack' ),
778
	);
779
	$defaults = array(
780
		'top' => 1,
781
		'search' => 7,
782
	);
783
784
	$options = stats_dashboard_widget_options();
785
786
	if ( 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' == $_POST['widget_id'] ) {
787
		if ( isset( $periods[ $_POST['chart'] ] ) )
788
			$options['chart'] = $_POST['chart'];
789
		foreach ( array( 'top', 'search' ) as $key ) {
790
			if ( isset( $intervals[ $_POST[$key] ] ) )
791
				$options[$key] = $_POST[$key];
792
			else
793
				$options[$key] = $defaults[$key];
794
		}
795
		update_option( 'stats_dashboard_widget', $options );
796
	}
797
	?>
798
	<p>
799
	<label for="chart"><?php _e( 'Chart stats by' , 'jetpack' ); ?></label>
800
	<select id="chart" name="chart">
801
	<?php
802
	foreach ( $periods as $val => $label ) {
803
		?>
804
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
805
		<?php
806
	}
807
	?>
808
	</select>.
809
	</p>
810
811
	<p>
812
	<label for="top"><?php _e( 'Show top posts over', 'jetpack' ); ?></label>
813
	<select id="top" name="top">
814
	<?php
815 View Code Duplication
	foreach ( $intervals as $val => $label ) {
816
		?>
817
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
818
		<?php
819
	}
820
	?>
821
	</select>.
822
	</p>
823
824
	<p>
825
	<label for="search"><?php _e( 'Show top search terms over', 'jetpack' ); ?></label>
826
	<select id="search" name="search">
827
	<?php
828 View Code Duplication
	foreach ( $intervals as $val => $label ) {
829
		?>
830
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
831
		<?php
832
	}
833
	?>
834
	</select>.
835
	</p>
836
	<?php
837
}
838
839
function stats_jetpack_dashboard_widget() {
840
	?>
841
	<form id="stats_dashboard_widget_control" action="<?php echo esc_url( admin_url() ); ?>" method="post">
842
		<?php stats_dashboard_widget_control(); ?>
843
		<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
844
		<input type="hidden" name="widget_id" value="dashboard_stats" />
845
		<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
846
	</form>
847
	<span id="js-toggle-stats_dashboard_widget_control">
848
		<?php esc_html_e( 'Configure', 'jetpack' ); ?>
849
	</span>
850
	<div id="dashboard_stats">
851
		<div class="inside">
852
			<div style="height: 250px;"></div>
853
		</div>
854
	</div>
855
	<script>
856
		jQuery(document).ready(function($){
857
			var $toggle = $('#js-toggle-stats_dashboard_widget_control');
858
859
			$toggle.parent().prev().append( $toggle );
860
			$toggle.show().click(function(e){
861
				e.preventDefault();
862
				e.stopImmediatePropagation();
863
				$(this).parent().toggleClass('controlVisible');
864
				$('#stats_dashboard_widget_control').slideToggle();
865
			});
866
		});
867
	</script>
868
	<style>
869
		#js-toggle-stats_dashboard_widget_control {
870
			display: none;
871
			float: right;
872
			margin-top: 0.2em;
873
			font-weight: 400;
874
			color: #444;
875
			font-size: .8em;
876
			text-decoration: underline;
877
			cursor: pointer;
878
		}
879
		#stats_dashboard_widget_control {
880
			display: none;
881
			padding: 0 10px;
882
			overflow: hidden;
883
		}
884
		#stats_dashboard_widget_control .button-primary {
885
			float: right;
886
		}
887
		#dashboard_stats {
888
			box-sizing: border-box;
889
			width: 100%;
890
			padding: 0 10px;
891
		}
892
	</style>
893
	<?php
894
}
895
896
function stats_register_widget_control_callback() {
897
	$GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control';
898
}
899
// JavaScript and CSS for dashboard widget
900
function stats_dashboard_head() { ?>
901
<script type="text/javascript">
902
/* <![CDATA[ */
903
jQuery( function($) {
904
	var dashStats = jQuery( '#dashboard_stats div.inside' );
905
906
	if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
907
		return;
908
	}
909
910
	if ( ! dashStats.length ) {
911
		dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
912
		var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
913
		var args = 'width=' + dashStats.width() + '&height=' + h.toString();
914
	} else {
915
		if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
916
			var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
917
		} else {
918
			var args = 'width=' + ( dashStats.width() * 2 ).toString();
919
		}
920
	}
921
922
	dashStats
923
		.not( '.dashboard-widget-control' )
924
		.load( 'admin.php?page=stats&noheader&dashboard&' + args );
925
926
	jQuery( window ).one( 'resize', function() {
927
		jQuery( '#stat-chart' ).css( 'width', 'auto' );
928
	} );
929
} );
930
/* ]]> */
931
</script>
932
<style type="text/css">
933
/* <![CDATA[ */
934
#stat-chart {
935
	background: none !important;
936
}
937
#dashboard_stats .inside {
938
	margin: 10px 0 0 0 !important;
939
}
940
#dashboard_stats #stats-graph {
941
	margin: 0;
942
}
943
#stats-info {
944
	border-top: 1px solid #dfdfdf;
945
	margin: 7px -10px 0 -10px;
946
	padding: 10px;
947
	background: #fcfcfc;
948
	-moz-box-shadow:inset 0 1px 0 #fff;
949
	-webkit-box-shadow:inset 0 1px 0 #fff;
950
	box-shadow:inset 0 1px 0 #fff;
951
	overflow: hidden;
952
	border-radius: 0 0 2px 2px;
953
	-webkit-border-radius: 0 0 2px 2px;
954
	-moz-border-radius: 0 0 2px 2px;
955
	-khtml-border-radius: 0 0 2px 2px;
956
}
957
#stats-info #top-posts, #stats-info #top-search {
958
	float: left;
959
	width: 50%;
960
}
961
#top-posts .stats-section-inner p {
962
	white-space: nowrap;
963
	overflow: hidden;
964
}
965
#top-posts .stats-section-inner p a {
966
	overflow: hidden;
967
	text-overflow: ellipsis;
968
}
969
#stats-info div#active {
970
	border-top: 1px solid #dfdfdf;
971
	margin: 0 -10px;
972
	padding: 10px 10px 0 10px;
973
	-moz-box-shadow:inset 0 1px 0 #fff;
974
	-webkit-box-shadow:inset 0 1px 0 #fff;
975
	box-shadow:inset 0 1px 0 #fff;
976
	overflow: hidden;
977
}
978
#top-search p {
979
	color: #999;
980
}
981
#stats-info h3 {
982
	font-size: 1em;
983
	margin: 0 0 .5em 0 !important;
984
}
985
#stats-info p {
986
	margin: 0 0 .25em;
987
	color: #999;
988
}
989
#stats-info p.widget-loading {
990
	margin: 1em 0 0;
991
	color: #333;
992
}
993
#stats-info p a {
994
	display: block;
995
}
996
#stats-info p a.button {
997
	display: inline;
998
}
999
/* ]]> */
1000
</style>
1001
<?php
1002
}
1003
1004
function stats_dashboard_widget_content() {
1005
	if ( !isset( $_GET['width'] ) || ( !$width  = (int) ( $_GET['width'] / 2 ) ) || $width  < 250 )
1006
		$width  = 370;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
1007
	if ( !isset( $_GET['height'] ) || ( !$height = (int) $_GET['height'] - 36 )   || $height < 230 )
1008
		$height = 180;
1009
1010
	$_width  = $width  - 5;
1011
	$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // hack!
1012
1013
	$options = stats_dashboard_widget_options();
1014
	$blog_id = Jetpack_Options::get_option( 'id' );
1015
1016
	$q = array(
1017
		'noheader' => 'true',
1018
		'proxy' => '',
1019
		'blog' => $blog_id,
1020
		'page' => 'stats',
1021
		'chart' => '',
1022
		'unit' => $options['chart'],
1023
		'color' => get_user_option( 'admin_color' ),
1024
		'width' => $_width,
1025
		'height' => $_height,
1026
		'ssl' => is_ssl(),
1027
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
1028
	);
1029
1030
	$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
1031
1032
	$url = add_query_arg( $q, $url );
1033
	$method = 'GET';
1034
	$timeout = 90;
1035
	$user_id = JETPACK_MASTER_USER;
1036
1037
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1038
	$get_code = wp_remote_retrieve_response_code( $get );
1039
	if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) {
1040
		stats_print_wp_remote_error( $get, $url );
1041
	} else {
1042
		$body = stats_convert_post_titles($get['body']);
1043
		$body = stats_convert_chart_urls($body);
1044
		$body = stats_convert_image_urls($body);
1045
		echo $body;
1046
	}
1047
1048
	$post_ids = array();
1049
1050
	$csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) );
1051
	$csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" );
1052
	/* translators: Stats dashboard widget postviews list: "$post_title $views Views" */
1053
	$printf = __( '%1$s %2$s Views' , 'jetpack' );
1054
1055
	foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) {
1056
		if ( $post['post_id'] == 0 ) {
1057
			unset( $top_posts[$i] );
1058
			continue;
1059
		}
1060
		$post_ids[] = $post['post_id'];
1061
	}
1062
1063
	// cache
1064
	get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
1065
1066
	$searches = array();
1067
	foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) {
1068
		if ( $search_term['searchterm'] == 'encrypted_search_terms' )
1069
			continue;
1070
		$searches[] = esc_html( $search_term['searchterm'] );
1071
	}
1072
1073
?>
1074
<a class="button" href="admin.php?page=stats"><?php _e( 'View All', 'jetpack' ); ?></a>
1075
<div id="stats-info">
1076
	<div id="top-posts" class='stats-section'>
1077
		<div class="stats-section-inner">
1078
		<h3 class="heading"><?php _e( 'Top Posts' , 'jetpack' ); ?></h3>
1079
		<?php
1080
		if ( empty( $top_posts ) ) {
1081
			?>
1082
			<p class="nothing"><?php _e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1083
			<?php
1084
		} else {
1085
			foreach ( $top_posts as $post ) {
1086
				if ( !get_post( $post['post_id'] ) )
1087
					continue;
1088
				?>
1089
				<p><?php printf(
1090
					$printf,
1091
					'<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',
1092
					number_format_i18n( $post['views'] )
1093
				); ?></p>
1094
				<?php
1095
			}
1096
		}
1097
		?>
1098
		</div>
1099
	</div>
1100
	<div id="top-search" class='stats-section'>
1101
		<div class="stats-section-inner">
1102
		<h3 class="heading"><?php _e( 'Top Searches' , 'jetpack' ); ?></h3>
1103
		<?php
1104
		if ( empty( $searches ) ) {
1105
			?>
1106
			<p class="nothing"><?php _e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1107
			<?php
1108
		} else {
1109
			?>
1110
			<p><?php echo join( ',&nbsp; ', $searches );?></p>
1111
			<?php
1112
		}
1113
		?>
1114
		</div>
1115
	</div>
1116
</div>
1117
<div class="clear"></div>
1118
<?php
1119
	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...
1120
}
1121
1122
function stats_print_wp_remote_error( $get, $url ) {
1123
	$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1124
	$previous_error = Jetpack::state( $state_name );
1125
	$error = md5( serialize( compact( 'get', 'url' ) ) );
1126
	Jetpack::state( $state_name, $error );
1127
	if ( $error !== $previous_error ) {
1128
?>
1129
	<div class="wrap">
1130
	<p><?php _e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
1131
	</div>
1132
<?php
1133
		return;
1134
	}
1135
?>
1136
	<div class="wrap">
1137
	<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' ), 'http://support.wordpress.com/contact/?jetpack=needs-service' ); ?></p>
1138
	<pre>
1139
	User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
1140
	Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
1141
	API URL: "<?php echo esc_url( $url ); ?>"
1142
<?php
1143
	if ( is_wp_error( $get ) ) {
1144
		foreach ( $get->get_error_codes() as $code ) {
1145
			foreach ( $get->get_error_messages($code) as $message ) {
1146
				?>
1147
	<?php print $code . ': "' . $message . '"' ?>
1148
1149
<?php
1150
			}
1151
		}
1152
	} else {
1153
		$get_code = wp_remote_retrieve_response_code( $get );
1154
		$content_length = strlen( wp_remote_retrieve_body( $get ) );
1155
		?>
1156
	Response code: "<?php print $get_code ?>"
1157
	Content length: "<?php print $content_length ?>"
1158
1159
<?php
1160
	}
1161
	?></pre>
1162
	</div>
1163
	<?php
1164
}
1165
1166
/**
1167
 * Get stats from WordPress.com
1168
 *
1169
 * @param string $table The stats which you want to retrieve: postviews, or searchterms
1170
 * @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...
1171
 *     An associative array of arguments.
1172
 *
1173
 *      @type bool    $end        The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
1174
 *                                and default timezone is UTC date. Default value is Now.
1175
 *      @type string  $days       The length of the desired time frame. Default is 30. Maximum 90 days.
1176
 *      @type int     $limit      The maximum number of records to return. Default is 10. Maximum 100.
1177
 *      @type int     $post_id    The ID of the post to retrieve stats data for
1178
 *      @type string  $summarize  If present, summarizes all matching records. Default Null.
1179
 *
1180
 * }
1181
 *
1182
 * @return array {
1183
 *      An array of post view data, each post as an array
1184
 *
1185
 *      array {
1186
 *          The post view data for a single post
1187
 *
1188
 *          @type string  $post_id         The ID of the post
1189
 *          @type string  $post_title      The title of the post
1190
 *          @type string  $post_permalink  The permalink for the post
1191
 *          @type string  $views           The number of views for the post within the $num_days specified
1192
 *      }
1193
 * }
1194
 */
1195
function stats_get_csv( $table, $args = null ) {
1196
	$defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' );
1197
1198
	$args = wp_parse_args( $args, $defaults );
1199
	$args['table'] = $table;
1200
	$args['blog_id'] = Jetpack_Options::get_option( 'id' );
1201
1202
	$stats_csv_url = add_query_arg( $args, 'http://stats.wordpress.com/csv.php' );
1203
1204
	$key = md5( $stats_csv_url );
1205
1206
	// Get cache
1207
	$stats_cache = get_option( 'stats_cache' );
1208
	if ( !$stats_cache || !is_array( $stats_cache ) )
1209
		$stats_cache = array();
1210
1211
	// Return or expire this key
1212
	if ( isset( $stats_cache[$key] ) ) {
1213
		$time = key( $stats_cache[$key] );
1214
		if ( time() - $time < 300 )
1215
			return $stats_cache[$key][$time];
1216
		unset( $stats_cache[$key] );
1217
	}
1218
1219
	$stats_rows = array();
1220
	do {
1221
		if ( !$stats = stats_get_remote_csv( $stats_csv_url ) )
1222
			break;
1223
1224
		$labels = array_shift( $stats );
1225
1226
		if ( 0 === stripos( $labels[0], 'error' ) )
1227
			break;
1228
1229
		$stats_rows = array();
1230
		for ( $s = 0; isset( $stats[$s] ); $s++ ) {
1231
			$row = array();
1232
			foreach ( $labels as $col => $label )
1233
				$row[$label] = $stats[$s][$col];
1234
			$stats_rows[] = $row;
1235
		}
1236
	} while( 0 );
1237
1238
	// Expire old keys
1239 View Code Duplication
	foreach ( $stats_cache as $k => $cache )
1240
		if ( !is_array( $cache ) || 300 < time() - key($cache) )
1241
			unset( $stats_cache[$k] );
1242
1243
	// Set cache
1244
	$stats_cache[$key] = array( time() => $stats_rows );
1245
	update_option( 'stats_cache', $stats_cache );
1246
1247
	return $stats_rows;
1248
}
1249
1250
function stats_get_remote_csv( $url ) {
1251
	$method = 'GET';
1252
	$timeout = 90;
1253
	$user_id = JETPACK_MASTER_USER;
1254
1255
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1256
	$get_code = wp_remote_retrieve_response_code( $get );
1257
	if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) {
1258
		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...
1259
	} else {
1260
		return stats_str_getcsv( $get['body'] );
1261
	}
1262
}
1263
1264
// rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it.
1265
function stats_str_getcsv( $csv ) {
1266
	if ( function_exists( 'str_getcsv' ) ) {
1267
		$lines = str_getcsv( $csv, "\n" );
1268
		return array_map( 'str_getcsv', $lines );
1269
	}
1270
	if ( !$temp = tmpfile() ) // tmpfile() automatically unlinks
1271
		return false;
1272
1273
	$data = array();
1274
1275
	fwrite( $temp, $csv, strlen( $csv ) );
1276
	fseek( $temp, 0 );
1277
	while ( false !== $row = fgetcsv( $temp, 2000 ) )
1278
		$data[] = $row;
1279
	fclose( $temp );
1280
1281
	return $data;
1282
}
1283
1284
/**
1285
 * Abstract out building the rest api stats path.
1286
 *
1287
 * @param  string $resource
1288
 * @return string
1289
 */
1290
function jetpack_stats_api_path( $resource = '' ) {
1291
	$resource = ltrim( $resource, '/' );
1292
	return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
1293
}
1294
1295
/**
1296
 * Fetches stats data from the REST API.  Caches locally for 5 minutes.
1297
 *
1298
 * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
1299
 *
1300
 * @param  array|string   $args     The args that are passed to the endpoint
1301
 * @param  string         $resource Optional sub-endpoint following /stats/
1302
 * @return array|WP_Error
1303
 */
1304
function stats_get_from_restapi( $args = array(), $resource = '' ) {
1305
	$endpoint    = jetpack_stats_api_path( $resource );
1306
	$api_version = '1.1';
1307
	$args        = wp_parse_args( $args, array() );
1308
	$cache_key   = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) );
1309
1310
	// Get cache
1311
	$stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() );
1312
	if ( ! is_array( $stats_cache ) ) {
1313
		$stats_cache = array();
1314
	}
1315
1316
	// Return or expire this key
1317
	if ( isset( $stats_cache[ $cache_key ] ) ) {
1318
		$time = key( $stats_cache[ $cache_key ] );
1319
		if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) {
1320
			$cached_stats = $stats_cache[ $cache_key ][ $time ];
1321
			$cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats );
1322
			return $cached_stats;
1323
		}
1324
		unset( $stats_cache[ $cache_key ] );
1325
	}
1326
1327
	// Do the dirty work.
1328
	$response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
1329
	if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1330
		// If bad, just return it, don't cache.
1331
		return $response;
1332
	}
1333
1334
	$data = json_decode( wp_remote_retrieve_body( $response ) );
1335
1336
	// Expire old keys
1337 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1338
		if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) {
1339
			unset( $stats_cache[ $k ] );
1340
		}
1341
	}
1342
1343
	// Set cache
1344
	$stats_cache[ $cache_key ] = array(
1345
		time() => $data,
1346
	);
1347
	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...
1348
1349
	return $data;
1350
}
1351