Completed
Push — fix/4104-prevent-stats-option-... ( 393edd )
by
unknown
88:30 queued 78:02
created

stats.php ➔ stats_get_options()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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