Completed
Push — add/80443split ( 25998f...41be19 )
by
unknown
09:21
created

stats.php ➔ stats_reports_page()   F

Complexity

Conditions 29
Paths 1853

Size

Total Lines 117
Code Lines 93

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 29
eloc 93
nc 1853
nop 0
dl 0
loc 117
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 $wp_the_query, $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
	$blog = Jetpack_Options::get_option( 'id' );
149
	$tz = get_option( 'gmt_offset' );
150
	$v = 'ext';
151
	$blog_url = parse_url( site_url() );
152
	$srv = $blog_url['host'];
153
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
154
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
155
		// Store and reset the queried_object and queried_object_id
156
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
157
		// Repro:
158
		// 1. Set home_url = http://ExamPle.com/
159
		// 2. Set show_on_front = page
160
		// 3. Set page_on_front = something
161
		// 4. Visit http://example.com/
162
163
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
164
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
165
		$post = $wp_the_query->get_queried_object_id();
166
		$wp_the_query->queried_object = $queried_object;
167
		$wp_the_query->queried_object_id = $queried_object_id;
168
	} else {
169
		$post = '0';
170
	}
171
172
	$script = set_url_scheme( '//stats.wp.com/e-' . gmdate( 'YW' ) . '.js' );
173
	$data = stats_array( compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' ) );
174
175
	$stats_footer = <<<END
176
<script type='text/javascript' src='{$script}' async defer></script>
177
<script type='text/javascript'>
178
	_stq = window._stq || [];
179
	_stq.push([ 'view', {{$data}} ]);
180
	_stq.push([ 'clickTrackerInit', '{$blog}', '{$post}' ]);
181
</script>
182
183
END;
184
}
185
186
function stats_add_shutdown_action() {
187
	// just in case wp_footer isn't in your theme
188
	add_action( 'shutdown',  'stats_footer', 101 );
189
}
190
191
function stats_footer() {
192
	global $stats_footer;
193
	print $stats_footer;
194
	$stats_footer = '';
195
}
196
197
function stats_get_options() {
198
	$options = get_option( 'stats_options' );
199
200
	if ( !isset( $options['version'] ) || $options['version'] < STATS_VERSION )
201
		$options = stats_upgrade_options( $options );
202
203
	return $options;
204
}
205
206
function stats_get_option( $option ) {
207
	$options = stats_get_options();
208
209
	if ( $option == 'blog_id' )
210
		return Jetpack_Options::get_option( 'id' );
211
212
	if ( isset( $options[$option] ) )
213
		return $options[$option];
214
215
	return null;
216
}
217
218
function stats_set_option( $option, $value ) {
219
	$options = stats_get_options();
220
221
	$options[$option] = $value;
222
223
	stats_set_options($options);
224
}
225
226
function stats_set_options($options) {
227
	update_option( 'stats_options', $options );
228
}
229
230
function stats_upgrade_options( $options ) {
231
	$defaults = array(
232
		'admin_bar'    => true,
233
		'roles'        => array( 'administrator' ),
234
		'count_roles'  => array(),
235
		'blog_id'      => Jetpack_Options::get_option( 'id' ),
236
		'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...
237
		'hide_smile'   => true,
238
	);
239
240
	if ( isset( $options['reg_users'] ) ) {
241
		if ( ! function_exists( 'get_editable_roles' ) )
242
			require_once( ABSPATH . 'wp-admin/includes/user.php' );
243
		if ( $options['reg_users'] )
244
			$options['count_roles'] = array_keys( get_editable_roles() );
245
		unset( $options['reg_users'] );
246
	}
247
248
	if ( is_array( $options ) && !empty( $options ) )
249
		$new_options = array_merge( $defaults, $options );
250
	else
251
		$new_options = $defaults;
252
253
	$new_options['version'] = STATS_VERSION;
254
255
	stats_set_options( $new_options );
256
257
	stats_update_blog();
258
259
	return $new_options;
260
}
261
262
function stats_array( $kvs ) {
263
	/**
264
	 * Filter the options added to the JavaScript Stats tracking code.
265
	 *
266
	 * @module stats
267
	 *
268
	 * @since 1.1.0
269
	 *
270
	 * @param array $kvs Array of options about the site and page you're on.
271
	 */
272
	$kvs = apply_filters( 'stats_array', $kvs );
273
	$kvs = array_map( 'addslashes', $kvs );
274
	foreach ( $kvs as $k => $v )
275
		$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...
276
	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...
277
}
278
279
/**
280
 * Admin Pages
281
 */
282
function stats_admin_menu() {
283
	global $pagenow;
284
285
	// If we're at an old Stats URL, redirect to the new one.
286
	// Don't even bother with caps, menu_page_url(), etc.  Just do it.
287
	if ( 'index.php' == $pagenow && isset( $_GET['page'] ) && 'stats' == $_GET['page'] ) {
288
		$redirect_url =	str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] );
289
		$relative_pos = strpos(	$redirect_url, '/wp-admin/' );
290
		if ( false !== $relative_pos ) {
291
			wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
292
			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...
293
		}
294
	}
295
296
	$hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' );
297
	add_action( "load-$hook", 'stats_reports_load' );
298
}
299
300
function stats_admin_path() {
301
	return Jetpack::module_configuration_url( __FILE__ );
302
}
303
304
function stats_reports_load() {
305
	wp_enqueue_script( 'jquery' );
306
	wp_enqueue_script( 'postbox' );
307
	wp_enqueue_script( 'underscore' );
308
309
	add_action( 'admin_print_styles', 'stats_reports_css' );
310
311
	if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) {
312
		$parsed = parse_url( admin_url() );
313
		// Remember user doesn't want JS
314
		setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days
315
	}
316
317
	if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) {
318
		// Detect if JS is on.  If so, remove cookie so next page load is via JS
319
		add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
320
	} else if ( !isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) {
321
		// Normal page load.  Load page content via JS.
322
		add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
323
	}
324
}
325
326
function stats_reports_css() {
327
?>
328
<style type="text/css">
329
#stats-loading-wrap p {
330
	text-align: center;
331
	font-size: 2em;
332
	margin: 7.5em 15px 0 0;
333
	height: 64px;
334
	line-height: 64px;
335
}
336
</style>
337
<?php
338
}
339
340
// Detect if JS is on.  If so, remove cookie so next page load is via JS.
341
function stats_js_remove_stnojs_cookie() {
342
	$parsed = parse_url( admin_url() );
343
?>
344
<script type="text/javascript">
345
/* <![CDATA[ */
346
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
347
/* ]]> */
348
</script>
349
<?php
350
}
351
352
// Normal page load.  Load page content via JS.
353
function stats_js_load_page_via_ajax() {
354
?>
355
<script type="text/javascript">
356
/* <![CDATA[ */
357
if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
358
	jQuery( function( $ ) {
359
		$.get( document.location.href + '&noheader', function( responseText ) {
360
			$( '#stats-loading-wrap' ).replaceWith( responseText );
361
		} );
362
	} );
363
}
364
/* ]]> */
365
</script>
366
<?php
367
}
368
369
function stats_reports_page() {
370
	if ( isset( $_GET['dashboard'] ) )
371
		return stats_dashboard_widget_content();
372
373
	$blog_id = stats_get_option( 'blog_id' );
374
	$domain = Jetpack::build_raw_urls( get_home_url() );
375
376
	if ( !isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) {
377
		$nojs_url = add_query_arg( 'nojs', '1' );
378
		$http = is_ssl() ? 'https' : 'http';
379
		// Loading message
380
		// No JS fallback message
381
?>
382
<div class="wrap">
383
	<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>
384
</div>
385
<div id="stats-loading-wrap" class="wrap">
386
<p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php
387
/** This filter is documented in modules/shortcodes/audio.php */
388
echo esc_url( apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" ) ); ?>" /></p>
389
<p style="font-size: 11pt; margin: 0;"><a href="https://wordpress.com/stats/<?php echo $domain; ?>"><?php esc_html_e( 'View stats on WordPress.com right now', 'jetpack' ); ?></a></p>
390
<p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
391
<a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without JavaScript', 'jetpack' ); ?></a>.</p>
392
</div>
393
<?php
394
		return;
395
	}
396
397
	$day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false;
398
	$q = array(
399
		'noheader' => 'true',
400
		'proxy' => '',
401
		'page' => 'stats',
402
		'day' => $day,
403
		'blog' => $blog_id,
404
		'charset' => get_option( 'blog_charset' ),
405
		'color' => get_user_option( 'admin_color' ),
406
		'ssl' => is_ssl(),
407
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
408
	);
409
	if ( get_locale() !== 'en_US' ) {
410
		$q['jp_lang'] = get_locale();
411
	}
412
	$args = array(
413
		'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),
414
		'numdays' => 'int',
415
		'day' => 'date',
416
		'unit' => array( 1, 7, 31, 'human' ),
417
		'humanize' => array( 'true' ),
418
		'num' => 'int',
419
		'summarize' => null,
420
		'post' => 'int',
421
		'width' => 'int',
422
		'height' => 'int',
423
		'data' => 'data',
424
		'blog_subscribers' => 'int',
425
		'comment_subscribers' => null,
426
		'type' => array( 'wpcom', 'email', 'pending' ),
427
		'pagenum' => 'int',
428
	);
429
	foreach ( $args as $var => $vals ) {
430
		if ( !isset( $_REQUEST[$var] ) )
431
			continue;
432
		if ( is_array( $vals ) ) {
433
			if ( in_array( $_REQUEST[$var], $vals ) )
434
				$q[$var] = $_REQUEST[$var];
435
		} elseif ( $vals == 'int' ) {
436
			$q[$var] = intval( $_REQUEST[$var] );
437
		} elseif ( $vals == 'date' ) {
438
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) )
439
				$q[$var] = $_REQUEST[$var];
440
		} 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...
441
			$q[$var] = '';
442
		} elseif ( $vals == 'data' ) {
443
			if ( substr( $_REQUEST[$var], 0, 9 ) == 'index.php' )
444
				$q[$var] = $_REQUEST[$var];
445
		}
446
	}
447
448
	if ( isset( $_GET['chart'] ) ) {
449
		if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) {
450
			$chart = sanitize_title( $_GET['chart'] );
451
			$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
452
		}
453
	} else {
454
		$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
455
	}
456
457
	$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...
458
	$method = 'GET';
459
	$timeout = 90;
460
	$user_id = JETPACK_MASTER_USER; // means send the wp.com user_id
461
462
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
463
	$get_code = wp_remote_retrieve_response_code( $get );
464
	if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) {
465
		stats_print_wp_remote_error( $get, $url );
466
	} else {
467
		if ( !empty( $get['headers']['content-type'] ) ) {
468
			$type = $get['headers']['content-type'];
469
			if ( substr( $type, 0, 5 ) == 'image' ) {
470
				$img = $get['body'];
471
				header( 'Content-Type: ' . $type );
472
				header( 'Content-Length: ' . strlen( $img ) );
473
				echo $img;
474
				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...
475
			}
476
		}
477
		$body = stats_convert_post_titles( $get['body'] );
478
		$body = stats_convert_chart_urls( $body );
479
		$body = stats_convert_image_urls( $body );
480
		$body = stats_convert_admin_urls( $body );
481
		echo $body;
482
	}
483
	if ( isset( $_GET['noheader'] ) )
484
		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...
485
}
486
487
function stats_convert_admin_urls( $html ) {
488
	return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
489
}
490
491
function stats_convert_image_urls( $html ) {
492
	$url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER );
493
	$html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html );
494
	return $html;
495
}
496
497
function stats_convert_chart_urls( $html ) {
498
	$html = preg_replace_callback( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
499
			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...
500
				'$matches',
501
				// If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string
502
				'return "admin.php?page=stats&noheader&chart=" . $matches[1] . str_replace( "?", "&", $matches[2] );'
503
			),
504
			$html );
505
	return $html;
506
}
507
508
function stats_convert_post_titles( $html ) {
509
	global $wpdb, $stats_posts;
510
	$pattern = "<span class='post-(\d+)-link'>.*?</span>";
511
	if ( !preg_match_all( "!$pattern!", $html, $matches ) )
512
		return $html;
513
	$posts = get_posts( array(
514
		'include' => implode( ',', $matches[1] ),
515
		'post_type' => 'any',
516
		'post_status' => 'any',
517
		'numberposts' => -1,
518
	));
519
	foreach ( $posts as $post )
520
		$stats_posts[$post->ID] = $post;
521
	$html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html );
522
	return $html;
523
}
524
525
function stats_convert_post_title( $matches ) {
526
	global $stats_posts;
527
	$post_id = $matches[1];
528
	if ( isset( $stats_posts[$post_id] ) )
529
		return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>';
530
	return $matches[0];
531
}
532
533
function stats_configuration_load() {
534
	if ( isset( $_POST['action'] ) && $_POST['action'] == 'save_options' && $_POST['_wpnonce'] == wp_create_nonce( 'stats' ) ) {
535
		$options = stats_get_options();
536
		$options['admin_bar']  = isset( $_POST['admin_bar']  ) && $_POST['admin_bar'];
537
		$options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile'];
538
539
		$options['roles'] = array( 'administrator' );
540 View Code Duplication
		foreach ( get_editable_roles() as $role => $details )
541
			if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] )
542
				$options['roles'][] = $role;
543
544
		$options['count_roles'] = array();
545 View Code Duplication
		foreach ( get_editable_roles() as $role => $details )
546
			if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] )
547
				$options['count_roles'][] = $role;
548
549
		stats_set_options( $options );
550
		stats_update_blog();
551
		Jetpack::state( 'message', 'module_configured' );
552
		wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) );
553
		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...
554
	}
555
}
556
557
function stats_configuration_head() {
558
	?>
559
	<style type="text/css">
560
		#statserror {
561
			border: 1px solid #766;
562
			background-color: #d22;
563
			padding: 1em 3em;
564
		}
565
		.stats-smiley {
566
			vertical-align: 1px;
567
		}
568
	</style>
569
	<?php
570
}
571
572
function stats_configuration_screen() {
573
	$options = stats_get_options();
574
	?>
575
	<div class="narrow">
576
		<p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p>
577
		<form method="post">
578
		<input type='hidden' name='action' value='save_options' />
579
		<?php wp_nonce_field( 'stats' ); ?>
580
		<table id="menu" class="form-table">
581
		<tr valign="top"><th scope="row"><label for="admin_bar"><?php _e( 'Admin bar' , 'jetpack' ); ?></label></th>
582
		<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>
583
		<tr valign="top"><th scope="row"><?php _e( 'Registered users', 'jetpack' ); ?></th>
584
		<td>
585
			<?php _e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/>
586
			<?php
587
			$count_roles = stats_get_option( 'count_roles' );
588
			foreach ( get_editable_roles() as $role => $details ) {
589
				?>
590
				<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/>
591
				<?php
592
			}
593
			?>
594
		</td></tr>
595
		<tr valign="top"><th scope="row"><?php _e( 'Smiley' , 'jetpack' ); ?></th>
596
		<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>
597
		<tr valign="top"><th scope="row"><?php _e( 'Report visibility' , 'jetpack' ); ?></th>
598
		<td>
599
			<?php _e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/>
600
			<?php
601
			$stats_roles = stats_get_option( 'roles' );
602
			foreach ( get_editable_roles() as $role => $details ) {
603
				?>
604
				<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/>
605
				<?php
606
			}
607
			?>
608
		</td></tr>
609
		</table>
610
		<p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p>
611
		</form>
612
	</div>
613
	<?php
614
}
615
616
function stats_hide_smile_css() {
617
	$options = stats_get_options();
618
	if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) {
619
	?>
620
<style type='text/css'>img#wpstats{display:none}</style><?php
621
	}
622
}
623
624
function stats_admin_bar_head() {
625
	if ( !stats_get_option( 'admin_bar' ) )
626
		return;
627
628
	if ( !current_user_can( 'view_stats' ) )
629
		return;
630
631
	if ( function_exists( 'is_admin_bar_showing' ) && !is_admin_bar_showing() ) {
632
		return;
633
	}
634
635
	add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 );
636
	?>
637
638
<style type='text/css'>
639
#wpadminbar .quicklinks li#wp-admin-bar-stats {
640
	height: 28px;
641
}
642
#wpadminbar .quicklinks li#wp-admin-bar-stats a {
643
	height: 28px;
644
	padding: 0;
645
}
646
#wpadminbar .quicklinks li#wp-admin-bar-stats a div {
647
	height: 28px;
648
	width: 95px;
649
	overflow: hidden;
650
	margin: 0 10px;
651
}
652
#wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
653
	width: auto;
654
	margin: 0 8px 0 10px;
655
}
656
#wpadminbar .quicklinks li#wp-admin-bar-stats a img {
657
	height: 24px;
658
	padding: 2px 0;
659
	max-width: none;
660
	border: none;
661
}
662
</style>
663
<?php
664
}
665
666
function stats_admin_bar_menu( &$wp_admin_bar ) {
667
	$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
668
669
	$img_src = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale' ), $url ) );
670
	$img_src_2x = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale-2x' ), $url ) );
671
672
	$alt = esc_attr( __( 'Stats', 'jetpack' ) );
673
674
	$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
675
676
	$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 );
677
678
	$wp_admin_bar->add_menu( $menu );
679
}
680
681
function stats_update_blog() {
682
	Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() );
683
}
684
685
function stats_get_blog() {
686
	$home = parse_url( trailingslashit( get_option( 'home' ) ) );
687
	$blog = array(
688
		'host'                => $home['host'],
689
		'path'                => $home['path'],
690
		'blogname'            => get_option( 'blogname' ),
691
		'blogdescription'     => get_option( 'blogdescription' ),
692
		'siteurl'             => get_option( 'siteurl' ),
693
		'gmt_offset'          => get_option( 'gmt_offset' ),
694
		'timezone_string'     => get_option( 'timezone_string' ),
695
		'stats_version'       => STATS_VERSION,
696
		'stats_api'           => 'jetpack',
697
		'page_on_front'       => get_option( 'page_on_front' ),
698
		'permalink_structure' => get_option( 'permalink_structure' ),
699
		'category_base'       => get_option( 'category_base' ),
700
		'tag_base'            => get_option( 'tag_base' ),
701
	);
702
	$blog = array_merge( stats_get_options(), $blog );
703
	unset( $blog['roles'], $blog['blog_id'] );
704
	return stats_esc_html_deep( $blog );
705
}
706
707
/**
708
 * Modified from stripslashes_deep()
709
 */
710
function stats_esc_html_deep( $value ) {
711
	if ( is_array( $value ) ) {
712
		$value = array_map( 'stats_esc_html_deep', $value );
713
	} elseif ( is_object( $value ) ) {
714
		$vars = get_object_vars( $value );
715
		foreach ( $vars as $key => $data ) {
716
			$value->{$key} = stats_esc_html_deep( $data );
717
		}
718
	} elseif ( is_string( $value ) ) {
719
		$value = esc_html( $value );
720
	}
721
722
	return $value;
723
}
724
725
function stats_xmlrpc_methods( $methods ) {
726
	$my_methods = array(
727
		'jetpack.getBlog' => 'stats_get_blog',
728
	);
729
730
	return array_merge( $methods, $my_methods );
731
}
732
733
function stats_register_dashboard_widget() {
734
	if ( ! current_user_can( 'view_stats' ) )
735
		return;
736
737
	// wp_dashboard_empty: we load in the content after the page load via JS
738
	wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' );
739
740
	add_action( 'admin_head', 'stats_dashboard_head' );
741
}
742
743
function stats_dashboard_widget_options() {
744
	$defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 );
745
	if ( ( !$options = get_option( 'stats_dashboard_widget' ) ) || !is_array( $options ) )
746
		$options = array();
747
748
	// Ignore obsolete option values
749
	$intervals = array( 1, 7, 31, 90, 365 );
750
	foreach ( array( 'top', 'search' ) as $key )
751
		if ( isset( $options[$key] ) && !in_array( $options[$key], $intervals ) )
752
			unset( $options[$key] );
753
754
	return array_merge( $defaults, $options );
755
}
756
757
function stats_dashboard_widget_control() {
758
	$periods   = array(
759
		'1' => __( 'day', 'jetpack' ),
760
		'7' => __( 'week', 'jetpack' ),
761
		'31' => __( 'month', 'jetpack' ),
762
	);
763
	$intervals = array(
764
		'1' => __( 'the past day', 'jetpack' ),
765
		'7' => __( 'the past week', 'jetpack' ),
766
		'31' => __( 'the past month', 'jetpack' ),
767
		'90' => __( 'the past quarter', 'jetpack' ),
768
		'365' => __( 'the past year', 'jetpack' ),
769
	);
770
	$defaults = array(
771
		'top' => 1,
772
		'search' => 7,
773
	);
774
775
	$options = stats_dashboard_widget_options();
776
777
	if ( 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' == $_POST['widget_id'] ) {
778
		if ( isset( $periods[ $_POST['chart'] ] ) )
779
			$options['chart'] = $_POST['chart'];
780
		foreach ( array( 'top', 'search' ) as $key ) {
781
			if ( isset( $intervals[ $_POST[$key] ] ) )
782
				$options[$key] = $_POST[$key];
783
			else
784
				$options[$key] = $defaults[$key];
785
		}
786
		update_option( 'stats_dashboard_widget', $options );
787
	}
788
	?>
789
	<p>
790
	<label for="chart"><?php _e( 'Chart stats by' , 'jetpack' ); ?></label>
791
	<select id="chart" name="chart">
792
	<?php
793
	foreach ( $periods as $val => $label ) {
794
		?>
795
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
796
		<?php
797
	}
798
	?>
799
	</select>.
800
	</p>
801
802
	<p>
803
	<label for="top"><?php _e( 'Show top posts over', 'jetpack' ); ?></label>
804
	<select id="top" name="top">
805
	<?php
806 View Code Duplication
	foreach ( $intervals as $val => $label ) {
807
		?>
808
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
809
		<?php
810
	}
811
	?>
812
	</select>.
813
	</p>
814
815
	<p>
816
	<label for="search"><?php _e( 'Show top search terms over', 'jetpack' ); ?></label>
817
	<select id="search" name="search">
818
	<?php
819 View Code Duplication
	foreach ( $intervals as $val => $label ) {
820
		?>
821
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
822
		<?php
823
	}
824
	?>
825
	</select>.
826
	</p>
827
	<?php
828
}
829
830
function stats_jetpack_dashboard_widget() {
831
	?>
832
	<form id="stats_dashboard_widget_control" action="<?php esc_url( admin_url() ); ?>" method="post">
833
		<?php stats_dashboard_widget_control(); ?>
834
		<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
835
		<input type="hidden" name="widget_id" value="dashboard_stats" />
836
		<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
837
	</form>
838
	<span id="js-toggle-stats_dashboard_widget_control">
839
		<?php esc_html_e( 'Configure', 'jetpack' ); ?>
840
	</span>
841
	<div id="dashboard_stats">
842
		<div class="inside">
843
			<div style="height: 250px;"></div>
844
		</div>
845
	</div>
846
	<script>
847
		jQuery(document).ready(function($){
848
			var $toggle = $('#js-toggle-stats_dashboard_widget_control');
849
850
			$toggle.parent().prev().append( $toggle );
851
			$toggle.show().click(function(e){
852
				e.preventDefault();
853
				e.stopImmediatePropagation();
854
				$(this).parent().toggleClass('controlVisible');
855
				$('#stats_dashboard_widget_control').slideToggle();
856
			});
857
		});
858
	</script>
859
	<style>
860
		#js-toggle-stats_dashboard_widget_control {
861
			display: none;
862
			float: right;
863
			margin-top: 0.2em;
864
			font-weight: 400;
865
			color: #444;
866
			font-size: .8em;
867
			text-decoration: underline;
868
			cursor: pointer;
869
		}
870
		#stats_dashboard_widget_control {
871
			display: none;
872
			padding: 0 10px;
873
			overflow: hidden;
874
		}
875
		#stats_dashboard_widget_control .button-primary {
876
			float: right;
877
		}
878
		#dashboard_stats {
879
			box-sizing: border-box;
880
			width: 100%;
881
			padding: 0 10px;
882
		}
883
	</style>
884
	<?php
885
}
886
887
function stats_register_widget_control_callback() {
888
	$GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control';
889
}
890
// JavaScript and CSS for dashboard widget
891
function stats_dashboard_head() { ?>
892
<script type="text/javascript">
893
/* <![CDATA[ */
894
jQuery( function($) {
895
	var dashStats = jQuery( '#dashboard_stats div.inside' );
896
897
	if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
898
		return;
899
	}
900
901
	if ( ! dashStats.length ) {
902
		dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
903
		var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
904
		var args = 'width=' + dashStats.width() + '&height=' + h.toString();
905
	} else {
906
		if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
907
			var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
908
		} else {
909
			var args = 'width=' + ( dashStats.width() * 2 ).toString();
910
		}
911
	}
912
913
	dashStats
914
		.not( '.dashboard-widget-control' )
915
		.load( 'admin.php?page=stats&noheader&dashboard&' + args );
916
917
	jQuery( window ).one( 'resize', function() {
918
		jQuery( '#stat-chart' ).css( 'width', 'auto' );
919
	} );
920
} );
921
/* ]]> */
922
</script>
923
<style type="text/css">
924
/* <![CDATA[ */
925
#stat-chart {
926
	background: none !important;
927
}
928
#dashboard_stats .inside {
929
	margin: 10px 0 0 0 !important;
930
}
931
#dashboard_stats #stats-graph {
932
	margin: 0;
933
}
934
#stats-info {
935
	border-top: 1px solid #dfdfdf;
936
	margin: 7px -10px 0 -10px;
937
	padding: 10px;
938
	background: #fcfcfc;
939
	-moz-box-shadow:inset 0 1px 0 #fff;
940
	-webkit-box-shadow:inset 0 1px 0 #fff;
941
	box-shadow:inset 0 1px 0 #fff;
942
	overflow: hidden;
943
	border-radius: 0 0 2px 2px;
944
	-webkit-border-radius: 0 0 2px 2px;
945
	-moz-border-radius: 0 0 2px 2px;
946
	-khtml-border-radius: 0 0 2px 2px;
947
}
948
#stats-info #top-posts, #stats-info #top-search {
949
	float: left;
950
	width: 50%;
951
}
952
#top-posts .stats-section-inner p {
953
	white-space: nowrap;
954
	overflow: hidden;
955
}
956
#top-posts .stats-section-inner p a {
957
	overflow: hidden;
958
	text-overflow: ellipsis;
959
}
960
#stats-info div#active {
961
	border-top: 1px solid #dfdfdf;
962
	margin: 0 -10px;
963
	padding: 10px 10px 0 10px;
964
	-moz-box-shadow:inset 0 1px 0 #fff;
965
	-webkit-box-shadow:inset 0 1px 0 #fff;
966
	box-shadow:inset 0 1px 0 #fff;
967
	overflow: hidden;
968
}
969
#top-search p {
970
	color: #999;
971
}
972
#stats-info h3 {
973
	font-size: 1em;
974
	margin: 0 0 .5em 0 !important;
975
}
976
#stats-info p {
977
	margin: 0 0 .25em;
978
	color: #999;
979
}
980
#stats-info p.widget-loading {
981
	margin: 1em 0 0;
982
	color: #333;
983
}
984
#stats-info p a {
985
	display: block;
986
}
987
#stats-info p a.button {
988
	display: inline;
989
}
990
/* ]]> */
991
</style>
992
<?php
993
}
994
995
function stats_dashboard_widget_content() {
996
	if ( !isset( $_GET['width'] ) || ( !$width  = (int) ( $_GET['width'] / 2 ) ) || $width  < 250 )
997
		$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...
998
	if ( !isset( $_GET['height'] ) || ( !$height = (int) $_GET['height'] - 36 )   || $height < 230 )
999
		$height = 180;
1000
1001
	$_width  = $width  - 5;
1002
	$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // hack!
1003
1004
	$options = stats_dashboard_widget_options();
1005
	$blog_id = Jetpack_Options::get_option( 'id' );
1006
1007
	$q = array(
1008
		'noheader' => 'true',
1009
		'proxy' => '',
1010
		'blog' => $blog_id,
1011
		'page' => 'stats',
1012
		'chart' => '',
1013
		'unit' => $options['chart'],
1014
		'color' => get_user_option( 'admin_color' ),
1015
		'width' => $_width,
1016
		'height' => $_height,
1017
		'ssl' => is_ssl(),
1018
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
1019
	);
1020
1021
	$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
1022
1023
	$url = add_query_arg( $q, $url );
1024
	$method = 'GET';
1025
	$timeout = 90;
1026
	$user_id = JETPACK_MASTER_USER;
1027
1028
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1029
	$get_code = wp_remote_retrieve_response_code( $get );
1030
	if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) {
1031
		stats_print_wp_remote_error( $get, $url );
1032
	} else {
1033
		$body = stats_convert_post_titles($get['body']);
1034
		$body = stats_convert_chart_urls($body);
1035
		$body = stats_convert_image_urls($body);
1036
		echo $body;
1037
	}
1038
1039
	$post_ids = array();
1040
1041
	$csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) );
1042
	$csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" );
1043
	/* translators: Stats dashboard widget postviews list: "$post_title $views Views" */
1044
	$printf = __( '%1$s %2$s Views' , 'jetpack' );
1045
1046
	foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) {
1047
		if ( $post['post_id'] == 0 ) {
1048
			unset( $top_posts[$i] );
1049
			continue;
1050
		}
1051
		$post_ids[] = $post['post_id'];
1052
	}
1053
1054
	// cache
1055
	get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
1056
1057
	$searches = array();
1058
	foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) {
1059
		if ( $search_term['searchterm'] == 'encrypted_search_terms' )
1060
			continue;
1061
		$searches[] = esc_html( $search_term['searchterm'] );
1062
	}
1063
1064
?>
1065
<a class="button" href="admin.php?page=stats"><?php _e( 'View All', 'jetpack' ); ?></a>
1066
<div id="stats-info">
1067
	<div id="top-posts" class='stats-section'>
1068
		<div class="stats-section-inner">
1069
		<h3 class="heading"><?php _e( 'Top Posts' , 'jetpack' ); ?></h3>
1070
		<?php
1071
		if ( empty( $top_posts ) ) {
1072
			?>
1073
			<p class="nothing"><?php _e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1074
			<?php
1075
		} else {
1076
			foreach ( $top_posts as $post ) {
1077
				if ( !get_post( $post['post_id'] ) )
1078
					continue;
1079
				?>
1080
				<p><?php printf(
1081
					$printf,
1082
					'<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',
1083
					number_format_i18n( $post['views'] )
1084
				); ?></p>
1085
				<?php
1086
			}
1087
		}
1088
		?>
1089
		</div>
1090
	</div>
1091
	<div id="top-search" class='stats-section'>
1092
		<div class="stats-section-inner">
1093
		<h3 class="heading"><?php _e( 'Top Searches' , 'jetpack' ); ?></h3>
1094
		<?php
1095
		if ( empty( $searches ) ) {
1096
			?>
1097
			<p class="nothing"><?php _e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1098
			<?php
1099
		} else {
1100
			?>
1101
			<p><?php echo join( ',&nbsp; ', $searches );?></p>
1102
			<?php
1103
		}
1104
		?>
1105
		</div>
1106
	</div>
1107
</div>
1108
<div class="clear"></div>
1109
<?php
1110
	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...
1111
}
1112
1113
function stats_print_wp_remote_error( $get, $url ) {
1114
	$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1115
	$previous_error = Jetpack::state( $state_name );
1116
	$error = md5( serialize( compact( 'get', 'url' ) ) );
1117
	Jetpack::state( $state_name, $error );
1118
	if ( $error !== $previous_error ) {
1119
?>
1120
	<div class="wrap">
1121
	<p><?php _e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
1122
	</div>
1123
<?php
1124
		return;
1125
	}
1126
?>
1127
	<div class="wrap">
1128
	<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">contact support</a>. In your report please include the information below.', 'jetpack' ), 'http://support.wordpress.com/contact/?jetpack=needs-service' ); ?></p>
1129
	<pre>
1130
	User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
1131
	Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
1132
	API URL: "<?php echo esc_url( $url ); ?>"
1133
<?php
1134
	if ( is_wp_error( $get ) ) {
1135
		foreach ( $get->get_error_codes() as $code ) {
1136
			foreach ( $get->get_error_messages($code) as $message ) {
1137
				?>
1138
	<?php print $code . ': "' . $message . '"' ?>
1139
1140
<?php
1141
			}
1142
		}
1143
	} else {
1144
		$get_code = wp_remote_retrieve_response_code( $get );
1145
		$content_length = strlen( wp_remote_retrieve_body( $get ) );
1146
		?>
1147
	Response code: "<?php print $get_code ?>"
1148
	Content length: "<?php print $content_length ?>"
1149
1150
<?php
1151
	}
1152
	?></pre>
1153
	</div>
1154
	<?php
1155
}
1156
1157
/**
1158
 * Get stats from WordPress.com
1159
 *
1160
 * @param string $table The stats which you want to retrieve: postviews, or searchterms
1161
 * @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...
1162
 *     An associative array of arguments.
1163
 *
1164
 *      @type bool    $end        The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
1165
 *                                and default timezone is UTC date. Default value is Now.
1166
 *      @type string  $days       The length of the desired time frame. Default is 30. Maximum 90 days.
1167
 *      @type int     $limit      The maximum number of records to return. Default is 10. Maximum 100.
1168
 *      @type int     $post_id    The ID of the post to retrieve stats data for
1169
 *      @type string  $summarize  If present, summarizes all matching records. Default Null.
1170
 *
1171
 * }
1172
 *
1173
 * @return array {
1174
 *      An array of post view data, each post as an array
1175
 *
1176
 *      array {
1177
 *          The post view data for a single post
1178
 *
1179
 *          @type string  $post_id         The ID of the post
1180
 *          @type string  $post_title      The title of the post
1181
 *          @type string  $post_permalink  The permalink for the post
1182
 *          @type string  $views           The number of views for the post within the $num_days specified
1183
 *      }
1184
 * }
1185
 */
1186
function stats_get_csv( $table, $args = null ) {
1187
	$defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' );
1188
1189
	$args = wp_parse_args( $args, $defaults );
1190
	$args['table'] = $table;
1191
	$args['blog_id'] = Jetpack_Options::get_option( 'id' );
1192
1193
	$stats_csv_url = add_query_arg( $args, 'http://stats.wordpress.com/csv.php' );
1194
1195
	$key = md5( $stats_csv_url );
1196
1197
	// Get cache
1198
	$stats_cache = get_option( 'stats_cache' );
1199
	if ( !$stats_cache || !is_array( $stats_cache ) )
1200
		$stats_cache = array();
1201
1202
	// Return or expire this key
1203
	if ( isset( $stats_cache[$key] ) ) {
1204
		$time = key( $stats_cache[$key] );
1205
		if ( time() - $time < 300 )
1206
			return $stats_cache[$key][$time];
1207
		unset( $stats_cache[$key] );
1208
	}
1209
1210
	$stats_rows = array();
1211
	do {
1212
		if ( !$stats = stats_get_remote_csv( $stats_csv_url ) )
1213
			break;
1214
1215
		$labels = array_shift( $stats );
1216
1217
		if ( 0 === stripos( $labels[0], 'error' ) )
1218
			break;
1219
1220
		$stats_rows = array();
1221
		for ( $s = 0; isset( $stats[$s] ); $s++ ) {
1222
			$row = array();
1223
			foreach ( $labels as $col => $label )
1224
				$row[$label] = $stats[$s][$col];
1225
			$stats_rows[] = $row;
1226
		}
1227
	} while( 0 );
1228
1229
	// Expire old keys
1230 View Code Duplication
	foreach ( $stats_cache as $k => $cache )
1231
		if ( !is_array( $cache ) || 300 < time() - key($cache) )
1232
			unset( $stats_cache[$k] );
1233
1234
	// Set cache
1235
	$stats_cache[$key] = array( time() => $stats_rows );
1236
	update_option( 'stats_cache', $stats_cache );
1237
1238
	return $stats_rows;
1239
}
1240
1241
function stats_get_remote_csv( $url ) {
1242
	$method = 'GET';
1243
	$timeout = 90;
1244
	$user_id = JETPACK_MASTER_USER;
1245
1246
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1247
	$get_code = wp_remote_retrieve_response_code( $get );
1248
	if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) {
1249
		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...
1250
	} else {
1251
		return stats_str_getcsv( $get['body'] );
1252
	}
1253
}
1254
1255
// rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it.
1256
function stats_str_getcsv( $csv ) {
1257
	if ( function_exists( 'str_getcsv' ) ) {
1258
		$lines = str_getcsv( $csv, "\n" );
1259
		return array_map( 'str_getcsv', $lines );
1260
	}
1261
	if ( !$temp = tmpfile() ) // tmpfile() automatically unlinks
1262
		return false;
1263
1264
	$data = array();
1265
1266
	fwrite( $temp, $csv, strlen( $csv ) );
1267
	fseek( $temp, 0 );
1268
	while ( false !== $row = fgetcsv( $temp, 2000 ) )
1269
		$data[] = $row;
1270
	fclose( $temp );
1271
1272
	return $data;
1273
}
1274
1275
/**
1276
 * Abstract out building the rest api stats path.
1277
 *
1278
 * @param  string $resource
1279
 * @return string
1280
 */
1281
function jetpack_stats_api_path( $resource = '' ) {
1282
	$resource = ltrim( $resource, '/' );
1283
	return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
1284
}
1285
1286
/**
1287
 * Fetches stats data from the REST API.  Caches locally for 5 minutes.
1288
 *
1289
 * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
1290
 *
1291
 * @param  array|string   $args     The args that are passed to the endpoint
1292
 * @param  string         $resource Optional sub-endpoint following /stats/
1293
 * @return array|WP_Error
1294
 */
1295
function stats_get_from_restapi( $args = array(), $resource = '' ) {
1296
	$endpoint    = jetpack_stats_api_path( $resource );
1297
	$api_version = '1.1';
1298
	$args        = wp_parse_args( $args, array() );
1299
	$cache_key   = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) );
1300
1301
	// Get cache
1302
	$stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() );
1303
	if ( ! is_array( $stats_cache ) ) {
1304
		$stats_cache = array();
1305
	}
1306
1307
	// Return or expire this key
1308
	if ( isset( $stats_cache[ $cache_key ] ) ) {
1309
		$time = key( $stats_cache[ $cache_key ] );
1310
		if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) {
1311
			$cached_stats = $stats_cache[ $cache_key ][ $time ];
1312
			$cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats );
1313
			return $cached_stats;
1314
		}
1315
		unset( $stats_cache[ $cache_key ] );
1316
	}
1317
1318
	// Do the dirty work.
1319
	$response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
1320
	if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1321
		// If bad, just return it, don't cache.
1322
		return $response;
1323
	}
1324
1325
	$data = json_decode( wp_remote_retrieve_body( $response ) );
1326
1327
	// Expire old keys
1328 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1329
		if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) {
1330
			unset( $stats_cache[ $k ] );
1331
		}
1332
	}
1333
1334
	// Set cache
1335
	$stats_cache[ $cache_key ] = array(
1336
		time() => $data,
1337
	);
1338
	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...
1339
1340
	return $data;
1341
}
1342