Completed
Push — fix/4104-prevent-stats-option-... ( 63a3cb )
by
unknown
06:47
created

stats.php ➔ stats_get_default_options()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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