Completed
Push — update/stats-page-background ( 32d546 )
by
unknown
10:09 queued 42s
created

stats.php ➔ stats_reports_css()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * Module Name: Site Stats
4
 * Module Description: Collect valuable traffic stats and insights.
5
 * Sort Order: 1
6
 * Recommendation Order: 2
7
 * First Introduced: 1.1
8
 * Requires Connection: Yes
9
 * Auto Activate: Yes
10
 * Module Tags: Site Stats, Recommended
11
 * Feature: Engagement
12
 * Additional Search Queries: statistics, tracking, analytics, views, traffic, stats
13
 *
14
 * @package Jetpack
15
 */
16
17
if ( defined( 'STATS_VERSION' ) ) {
18
	return;
19
}
20
21
define( 'STATS_VERSION', '9' );
22
defined( 'STATS_DASHBOARD_SERVER' ) or define( 'STATS_DASHBOARD_SERVER', 'dashboard.wordpress.com' );
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
23
24
add_action( 'jetpack_modules_loaded', 'stats_load' );
25
26
/**
27
 * Load Stats.
28
 *
29
 * @access public
30
 * @return void
31
 */
32
function stats_load() {
33
	Jetpack::enable_module_configurable( __FILE__ );
34
	Jetpack::module_configuration_load( __FILE__, 'stats_configuration_load' );
35
	Jetpack::module_configuration_head( __FILE__, 'stats_configuration_head' );
36
	Jetpack::module_configuration_screen( __FILE__, 'stats_configuration_screen' );
37
38
	// Generate the tracking code after wp() has queried for posts.
39
	add_action( 'template_redirect', 'stats_template_redirect', 1 );
40
41
	add_action( 'wp_head', 'stats_admin_bar_head', 100 );
42
43
	add_action( 'wp_head', 'stats_hide_smile_css' );
44
45
	add_action( 'jetpack_admin_menu', 'stats_admin_menu' );
46
47
	// Map stats caps.
48
	add_filter( 'map_meta_cap', 'stats_map_meta_caps', 10, 3 );
49
50
	if ( isset( $_GET['oldwidget'] ) ) {
51
		// Old one.
52
		add_action( 'wp_dashboard_setup', 'stats_register_dashboard_widget' );
53
	} else {
54
		add_action( 'admin_init', 'stats_merged_widget_admin_init' );
55
	}
56
57
	add_filter( 'jetpack_xmlrpc_methods', 'stats_xmlrpc_methods' );
58
59
	add_filter( 'pre_option_db_version', 'stats_ignore_db_version' );
60
61
	// Add an icon to see stats in WordPress.com for a particular post
62
	add_action( 'admin_print_styles-edit.php', 'jetpack_stats_load_admin_css' );
63
	add_filter( 'manage_posts_columns', 'jetpack_stats_post_table' );
64
	add_filter( 'manage_pages_columns', 'jetpack_stats_post_table' );
65
	add_action( 'manage_posts_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
66
	add_action( 'manage_pages_custom_column', 'jetpack_stats_post_table_cell', 10, 2 );
67
}
68
69
/**
70
 * Delay conditional for current_user_can to after init.
71
 *
72
 * @access public
73
 * @return void
74
 */
75
function stats_merged_widget_admin_init() {
76
	if ( current_user_can( 'view_stats' ) ) {
77
		add_action( 'load-index.php', 'stats_enqueue_dashboard_head' );
78
		add_action( 'wp_dashboard_setup', 'stats_register_widget_control_callback' ); // Hacky but works.
79
		add_action( 'jetpack_dashboard_widget', 'stats_jetpack_dashboard_widget' );
80
	}
81
}
82
83
/**
84
 * Enqueue Stats Dashboard
85
 *
86
 * @access public
87
 * @return void
88
 */
89
function stats_enqueue_dashboard_head() {
90
	add_action( 'admin_head', 'stats_dashboard_head' );
91
}
92
93
/**
94
 * Checks if filter is set and dnt is enabled.
95
 *
96
 * @return bool
97
 */
98
function jetpack_is_dnt_enabled() {
99
	/**
100
	 * Filter the option which decides honor DNT or not.
101
	 *
102
	 * @module stats
103
	 * @since 6.1.0
104
	 *
105
	 * @param bool false If config honors DNT and client doesn't want to tracked, false if not.
106
	 */
107
	if ( false === apply_filters( 'jetpack_honor_dnt_header_for_stats', false ) ) {
108
		return false;
109
	}
110
111
	foreach ( $_SERVER as $name => $value ) {
112
		if ( 'http_dnt' == strtolower( $name ) && 1 == $value ) {
113
			return true;
114
		}
115
	}
116
117
	return false;
118
}
119
120
/**
121
 * Prevent sparkline img requests being redirected to upgrade.php.
122
 * See wp-admin/admin.php where it checks $wp_db_version.
123
 *
124
 * @access public
125
 * @param mixed $version Version.
126
 * @return string $version.
127
 */
128
function stats_ignore_db_version( $version ) {
129
	if (
130
		is_admin() &&
131
		isset( $_GET['page'] ) && 'stats' === $_GET['page'] &&
132
		isset( $_GET['chart'] ) && strpos($_GET['chart'], 'admin-bar-hours') === 0
133
	) {
134
		global $wp_db_version;
135
		return $wp_db_version;
136
	}
137
	return $version;
138
}
139
140
/**
141
 * Maps view_stats cap to read cap as needed.
142
 *
143
 * @access public
144
 * @param mixed $caps Caps.
145
 * @param mixed $cap Cap.
146
 * @param mixed $user_id User ID.
147
 * @return array Possibly mapped capabilities for meta capability.
148
 */
149
function stats_map_meta_caps( $caps, $cap, $user_id ) {
150
	// Map view_stats to exists.
151
	if ( 'view_stats' === $cap ) {
152
		$user        = new WP_User( $user_id );
153
		$user_role   = array_shift( $user->roles );
154
		$stats_roles = stats_get_option( 'roles' );
155
156
		// Is the users role in the available stats roles?
157
		if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles ) ) {
158
			$caps = array( 'read' );
159
		}
160
	}
161
162
	return $caps;
163
}
164
165
/**
166
 * Stats Template Redirect.
167
 *
168
 * @access public
169
 * @return void
170
 */
171
function stats_template_redirect() {
172
	global $current_user, $stats_footer;
173
174
	if ( is_feed() || is_robots() || is_trackback() || is_preview() || jetpack_is_dnt_enabled() ) {
175
		return;
176
	}
177
178
	// Should we be counting this user's views?
179
	if ( ! empty( $current_user->ID ) ) {
180
		$count_roles = stats_get_option( 'count_roles' );
181
		if ( ! is_array( $count_roles ) || ! array_intersect( $current_user->roles, $count_roles ) ) {
182
			return;
183
		}
184
	}
185
186
	add_action( 'wp_footer', 'stats_footer', 101 );
187
	add_action( 'wp_head', 'stats_add_shutdown_action' );
188
189
	$script = 'https://stats.wp.com/e-' . gmdate( 'YW' ) . '.js';
190
	$data = stats_build_view_data();
191
	$data_stats_array = stats_array( $data );
192
193
	$stats_footer = <<<END
194
<script type='text/javascript' src='{$script}' async='async' defer='defer'></script>
195
<script type='text/javascript'>
196
	_stq = window._stq || [];
197
	_stq.push([ 'view', {{$data_stats_array}} ]);
198
	_stq.push([ 'clickTrackerInit', '{$data['blog']}', '{$data['post']}' ]);
199
</script>
200
201
END;
202
}
203
204
205
/**
206
 * Stats Build View Data.
207
 *
208
 * @access public
209
 * @return array.
0 ignored issues
show
Documentation introduced by
The doc-type array. could not be parsed: Unknown type name "array." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
210
 */
211
function stats_build_view_data() {
212
	global $wp_the_query;
213
214
	$blog = Jetpack_Options::get_option( 'id' );
215
	$tz = get_option( 'gmt_offset' );
216
	$v = 'ext';
217
	$blog_url = parse_url( site_url() );
218
	$srv = $blog_url['host'];
219
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
220
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
221
		// Store and reset the queried_object and queried_object_id
222
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
223
		// Repro:
224
		// 1. Set home_url = https://ExamPle.com/
225
		// 2. Set show_on_front = page
226
		// 3. Set page_on_front = something
227
		// 4. Visit https://example.com/ !
228
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
229
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
230
		$post = $wp_the_query->get_queried_object_id();
231
		$wp_the_query->queried_object = $queried_object;
232
		$wp_the_query->queried_object_id = $queried_object_id;
233
	} else {
234
		$post = '0';
235
	}
236
237
	return compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
238
}
239
240
/**
241
 * Stats Add Shutdown Action.
242
 *
243
 * @access public
244
 * @return void
245
 */
246
function stats_add_shutdown_action() {
247
	// Just in case wp_footer isn't in your theme.
248
	add_action( 'shutdown',  'stats_footer', 101 );
249
}
250
251
/**
252
 * Stats Footer.
253
 *
254
 * @access public
255
 * @return void
256
 */
257
function stats_footer() {
258
	global $stats_footer;
259
	print $stats_footer;
260
	$stats_footer = '';
261
}
262
263
/**
264
 * Stats Get Options.
265
 *
266
 * @access public
267
 * @return array.
0 ignored issues
show
Documentation introduced by
The doc-type array. could not be parsed: Unknown type name "array." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
268
 */
269
function stats_get_options() {
270
	$options = get_option( 'stats_options' );
271
272
	if ( ! isset( $options['version'] ) || $options['version'] < STATS_VERSION ) {
273
		$options = stats_upgrade_options( $options );
274
	}
275
276
	return $options;
277
}
278
279
/**
280
 * Get Stats Options.
281
 *
282
 * @access public
283
 * @param mixed $option Option.
284
 * @return mixed|null.
0 ignored issues
show
Documentation introduced by
The doc-type mixed|null. could not be parsed: Unknown type name "null." at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
285
 */
286
function stats_get_option( $option ) {
287
	$options = stats_get_options();
288
289
	if ( 'blog_id' === $option ) {
290
		return Jetpack_Options::get_option( 'id' );
291
	}
292
293
	if ( isset( $options[ $option ] ) ) {
294
		return $options[ $option ];
295
	}
296
297
	return null;
298
}
299
300
/**
301
 * Stats Set Options.
302
 *
303
 * @access public
304
 * @param mixed $option Option.
305
 * @param mixed $value Value.
306
 * @return bool.
0 ignored issues
show
Documentation introduced by
The doc-type bool. could not be parsed: Unknown type name "bool." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
307
 */
308
function stats_set_option( $option, $value ) {
309
	$options = stats_get_options();
310
311
	$options[ $option ] = $value;
312
313
	return stats_set_options( $options );
314
}
315
316
/**
317
 * Stats Set Options.
318
 *
319
 * @access public
320
 * @param mixed $options Options.
321
 * @return bool
322
 */
323
function stats_set_options( $options ) {
324
	return update_option( 'stats_options', $options );
325
}
326
327
/**
328
 * Stats Upgrade Options.
329
 *
330
 * @access public
331
 * @param mixed $options Options.
332
 * @return array|bool
333
 */
334
function stats_upgrade_options( $options ) {
335
	$defaults = array(
336
		'admin_bar'    => true,
337
		'roles'        => array( 'administrator' ),
338
		'count_roles'  => array(),
339
		'blog_id'      => Jetpack_Options::get_option( 'id' ),
340
		'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...
341
		'hide_smile'   => true,
342
	);
343
344
	if ( isset( $options['reg_users'] ) ) {
345
		if ( ! function_exists( 'get_editable_roles' ) ) {
346
			require_once ABSPATH . 'wp-admin/includes/user.php';
347
		}
348
		if ( $options['reg_users'] ) {
349
			$options['count_roles'] = array_keys( get_editable_roles() );
350
		}
351
		unset( $options['reg_users'] );
352
	}
353
354
	if ( is_array( $options ) && ! empty( $options ) ) {
355
		$new_options = array_merge( $defaults, $options );
356
	} else { $new_options = $defaults;
357
	}
358
359
	$new_options['version'] = STATS_VERSION;
360
361
	if ( ! stats_set_options( $new_options ) ) {
362
		return false;
363
	}
364
365
	stats_update_blog();
366
367
	return $new_options;
368
}
369
370
/**
371
 * Stats Array.
372
 *
373
 * @access public
374
 * @param mixed $kvs KVS.
375
 * @return array
376
 */
377
function stats_array( $kvs ) {
378
	/**
379
	 * Filter the options added to the JavaScript Stats tracking code.
380
	 *
381
	 * @module stats
382
	 *
383
	 * @since 1.1.0
384
	 *
385
	 * @param array $kvs Array of options about the site and page you're on.
386
	 */
387
	$kvs = apply_filters( 'stats_array', $kvs );
388
	$kvs = array_map( 'addslashes', $kvs );
389
	foreach ( $kvs as $k => $v ) {
390
		$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...
391
	}
392
	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...
393
}
394
395
/**
396
 * Admin Pages.
397
 *
398
 * @access public
399
 * @return void
400
 */
401
function stats_admin_menu() {
402
	global $pagenow;
403
404
	// If we're at an old Stats URL, redirect to the new one.
405
	// Don't even bother with caps, menu_page_url(), etc.  Just do it.
406
	if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'stats' === $_GET['page'] ) {
407
		$redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] );
408
		$relative_pos = strpos( $redirect_url, '/wp-admin/' );
409
		if ( false !== $relative_pos ) {
410
			wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) );
411
			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...
412
		}
413
	}
414
415
	$hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' );
416
	add_action( "load-$hook", 'stats_reports_load' );
417
}
418
419
/**
420
 * Stats Admin Path.
421
 *
422
 * @access public
423
 * @return string
424
 */
425
function stats_admin_path() {
426
	return Jetpack::module_configuration_url( __FILE__ );
427
}
428
429
/**
430
 * Stats Reports Load.
431
 *
432
 * @access public
433
 * @return void
434
 */
435
function stats_reports_load() {
436
	wp_enqueue_script( 'jquery' );
437
	wp_enqueue_script( 'postbox' );
438
	wp_enqueue_script( 'underscore' );
439
440
	$rtl = is_rtl() ? '.rtl' : '';
441
	wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin.dops-style$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
442
	wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
443
444
	add_action( 'admin_print_styles', 'stats_reports_css' );
445
446
	if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) {
447
		$parsed = parse_url( admin_url() );
448
		// Remember user doesn't want JS.
449
		setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days.
450
	}
451
452
	if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) {
453
		// Detect if JS is on.  If so, remove cookie so next page load is via JS.
454
		add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' );
455
	} else if ( ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) {
456
		// Normal page load.  Load page content via JS.
457
		add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' );
458
	}
459
}
460
461
/**
462
 * Stats Reports CSS.
463
 *
464
 * @access public
465
 * @return void
466
 */
467
function stats_reports_css() {
468
?>
469
<style type="text/css">
470
#wpcontent {
471
	padding-left: 0 !important;
472
}
473
474
#jp-plugin-container {
475
	background-color: #f3f6f8;
476
}
477
478
#jp-stats-wrap {
479
	max-width: 720px;
480
	margin: 0 auto;
481
	overflow: hidden;
482
}
483
484
#stats-loading-wrap p {
485
	text-align: center;
486
	font-size: 2em;
487
	margin: 7.5em 15px 0 0;
488
	height: 64px;
489
	line-height: 64px;
490
}
491
</style>
492
<?php
493
}
494
495
496
/**
497
 * Detect if JS is on.  If so, remove cookie so next page load is via JS.
498
 *
499
 * @access public
500
 * @return void
501
 */
502
function stats_js_remove_stnojs_cookie() {
503
	$parsed = parse_url( admin_url() );
504
?>
505
<script type="text/javascript">
506
/* <![CDATA[ */
507
document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>';
508
/* ]]> */
509
</script>
510
<?php
511
}
512
513
/**
514
 * Normal page load.  Load page content via JS.
515
 *
516
 * @access public
517
 * @return void
518
 */
519
function stats_js_load_page_via_ajax() {
520
?>
521
<script type="text/javascript">
522
/* <![CDATA[ */
523
if ( -1 == document.location.href.indexOf( 'noheader' ) ) {
524
	jQuery( function( $ ) {
525
		$.get( document.location.href + '&noheader', function( responseText ) {
526
			$( '#stats-loading-wrap' ).replaceWith( responseText );
527
		} );
528
	} );
529
}
530
/* ]]> */
531
</script>
532
<?php
533
}
534
535
/**
536
 * Stats Report Page.
537
 *
538
 * @access public
539
 * @param bool $main_chart_only (default: false) Main Chart Only.
540
 */
541
function stats_reports_page( $main_chart_only = false ) {
542
543
	if ( isset( $_GET['dashboard'] ) ) {
544
		return stats_dashboard_widget_content();
545
	}
546
547
	$blog_id = stats_get_option( 'blog_id' );
548
	$domain = Jetpack::build_raw_urls( get_home_url() );
549
550
	$jetpack_admin_url = admin_url() . 'admin.php?page=jetpack';
551
552
	if ( ! $main_chart_only && ! isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) {
553
		$nojs_url = add_query_arg( 'nojs', '1' );
554
		$http = is_ssl() ? 'https' : 'http';
555
		// Loading message. No JS fallback message.
556
?>
557
<div id="jp-plugin-container">
558
	<div class="jp-masthead">
559
		<div class="jp-masthead__inside-container">
560
			<div class="jp-masthead__logo-container">
561
				<a class="jp-masthead__logo-link" href="<?php echo esc_url( $jetpack_admin_url ); ?>">
562
					<svg class="jetpack-logo__masthead" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" height="32" viewBox="0 0 118 32"><path fill="#00BE28" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M15,19H7l8-16V19z M17,29V13h8L17,29z"></path><path d="M41.3,26.6c-0.5-0.7-0.9-1.4-1.3-2.1c2.3-1.4,3-2.5,3-4.6V8h-3V6h6v13.4C46,22.8,45,24.8,41.3,26.6z"></path><path d="M65,18.4c0,1.1,0.8,1.3,1.4,1.3c0.5,0,2-0.2,2.6-0.4v2.1c-0.9,0.3-2.5,0.5-3.7,0.5c-1.5,0-3.2-0.5-3.2-3.1V12H60v-2h2.1V7.1 H65V10h4v2h-4V18.4z"></path><path d="M71,10h3v1.3c1.1-0.8,1.9-1.3,3.3-1.3c2.5,0,4.5,1.8,4.5,5.6s-2.2,6.3-5.8,6.3c-0.9,0-1.3-0.1-2-0.3V28h-3V10z M76.5,12.3 c-0.8,0-1.6,0.4-2.5,1.2v5.9c0.6,0.1,0.9,0.2,1.8,0.2c2,0,3.2-1.3,3.2-3.9C79,13.4,78.1,12.3,76.5,12.3z"></path><path d="M93,22h-3v-1.5c-0.9,0.7-1.9,1.5-3.5,1.5c-1.5,0-3.1-1.1-3.1-3.2c0-2.9,2.5-3.4,4.2-3.7l2.4-0.3v-0.3c0-1.5-0.5-2.3-2-2.3 c-0.7,0-2.3,0.5-3.7,1.1L84,11c1.2-0.4,3-1,4.4-1c2.7,0,4.6,1.4,4.6,4.7L93,22z M90,16.4l-2.2,0.4c-0.7,0.1-1.4,0.5-1.4,1.6 c0,0.9,0.5,1.4,1.3,1.4s1.5-0.5,2.3-1V16.4z"></path><path d="M104.5,21.3c-1.1,0.4-2.2,0.6-3.5,0.6c-4.2,0-5.9-2.4-5.9-5.9c0-3.7,2.3-6,6.1-6c1.4,0,2.3,0.2,3.2,0.5V13 c-0.8-0.3-2-0.6-3.2-0.6c-1.7,0-3.2,0.9-3.2,3.6c0,2.9,1.5,3.8,3.3,3.8c0.9,0,1.9-0.2,3.2-0.7V21.3z"></path><path d="M110,15.2c0.2-0.3,0.2-0.8,3.8-5.2h3.7l-4.6,5.7l5,6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z"></path><path d="M58.5,21.3c-1.5,0.5-2.7,0.6-4.2,0.6c-3.6,0-5.8-1.8-5.8-6c0-3.1,1.9-5.9,5.5-5.9s4.9,2.5,4.9,4.9c0,0.8,0,1.5-0.1,2h-7.3 c0.1,2.5,1.5,2.8,3.6,2.8c1.1,0,2.2-0.3,3.4-0.7C58.5,19,58.5,21.3,58.5,21.3z M56,15c0-1.4-0.5-2.9-2-2.9c-1.4,0-2.3,1.3-2.4,2.9 C51.6,15,56,15,56,15z"></path></svg>
563
				</a>
564
			</div>
565
			<div class="jp-masthead__nav"><span class="dops-button-group"><a href="<?php echo esc_url( $jetpack_admin_url ); ?>" type="button" class="dops-button is-compact"><?php esc_html_e( 'Dashboard', 'jetpack' ); ?></a><a href="<?php echo esc_url( $jetpack_admin_url . '#/settings' ); ?>" type="button" class="dops-button is-compact"><?php esc_html_e( 'Settings', 'jetpack' ); ?></a></span></div>
566
		</div>
567
	</div>
568
	<div id="jp-stats-wrap">
569
<div class="wrap">
570
	<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>
571
</div>
572
<div id="stats-loading-wrap" class="wrap">
573
<p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading&hellip;', 'jetpack' ); ?>" src="<?php
574
		echo esc_url(
575
			/**
576
			 * Sets external resource URL.
577
			 *
578
			 * @module stats
579
			 *
580
			 * @since 1.4.0
581
			 *
582
			 * @param string $args URL of external resource.
583
			 */
584
			apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" )
585
		); ?>" /></p>
586
<p style="font-size: 11pt; margin: 0;"><a href="https://wordpress.com/stats/<?php echo esc_attr( $domain ); ?>" target="_blank"><?php esc_html_e( 'View stats on WordPress.com right now', 'jetpack' ); ?></a></p>
587
<p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with JavaScript enabled.', 'jetpack' ); ?><br />
588
<a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without JavaScript', 'jetpack' ); ?></a>.</p>
589
</div>
590
</div>
591
<div class="jp-footer">
592
	<ul class="jp-footer__links">
593
		<li class="jp-footer__link-item">
594
			<a href="https://jetpack.com" target="_blank" rel="noopener noreferrer" class="jp-footer__link" title="<?php esc_html_e( 'Jetpack version', 'jetpack' ); ?>">Jetpack <?php echo JETPACK__VERSION; ?></a>
595
		</li>
596
		<li class="jp-footer__link-item">
597
			<a href="https://wordpress.com/tos/" target="_blank" rel="noopener noreferrer" title="<?php esc_html__( 'WordPress.com Terms of Service', 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Terms', 'Navigation item', 'jetpack' ); ?></a>
598
		</li>
599
		<li class="jp-footer__link-item">
600
			<a href="<?php echo esc_url( $jetpack_admin_url . '#/privacy' ); ?>" rel="noopener noreferrer" title="<?php esc_html_e( 'Automattic\'s Privacy Policy', 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Privacy', 'Navigation item', 'jetpack' ); ?></a>
601
		</li>
602
		<li class="jp-footer__link-item">
603
			<a href="<?php echo esc_url( admin_url() . 'admin.php?page=jetpack-debugger' ); ?>" title="<?php esc_html_e( 'Test your site\'s compatibility with Jetpack.', 'jetpack' ); ?>" class="jp-footer__link"><?php echo esc_html_x( 'Debug', 'Navigation item', 'jetpack' ); ?></a>
604
		</li>
605
	</ul>
606
</div>
607
</div>
608
<?php
609
		return;
610
	}
611
612
	$day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false;
613
	$q = array(
614
		'noheader' => 'true',
615
		'proxy' => '',
616
		'page' => 'stats',
617
		'day' => $day,
618
		'blog' => $blog_id,
619
		'charset' => get_option( 'blog_charset' ),
620
		'color' => get_user_option( 'admin_color' ),
621
		'ssl' => is_ssl(),
622
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
623
	);
624
	if ( get_locale() !== 'en_US' ) {
625
		$q['jp_lang'] = get_locale();
626
	}
627
	// Only show the main chart, without extra header data, or metaboxes.
628
	$q['main_chart_only'] = $main_chart_only;
629
	$args = array(
630
		'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),
631
		'numdays' => 'int',
632
		'day' => 'date',
633
		'unit' => array( 1, 7, 31, 'human' ),
634
		'humanize' => array( 'true' ),
635
		'num' => 'int',
636
		'summarize' => null,
637
		'post' => 'int',
638
		'width' => 'int',
639
		'height' => 'int',
640
		'data' => 'data',
641
		'blog_subscribers' => 'int',
642
		'comment_subscribers' => null,
643
		'type' => array( 'wpcom', 'email', 'pending' ),
644
		'pagenum' => 'int',
645
	);
646
	foreach ( $args as $var => $vals ) {
647
		if ( ! isset( $_REQUEST[$var] ) )
648
			continue;
649
		if ( is_array( $vals ) ) {
650
			if ( in_array( $_REQUEST[$var], $vals ) )
651
				$q[$var] = $_REQUEST[$var];
652
		} elseif ( 'int' === $vals ) {
653
			$q[$var] = intval( $_REQUEST[$var] );
654
		} elseif ( 'date' === $vals ) {
655
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) )
656
				$q[$var] = $_REQUEST[$var];
657
		} elseif ( null === $vals ) {
658
			$q[$var] = '';
659
		} elseif ( 'data' === $vals ) {
660
			if ( 'index.php' === substr( $_REQUEST[$var], 0, 9 ) )
661
				$q[$var] = $_REQUEST[$var];
662
		}
663
	}
664
665
	if ( isset( $_GET['chart'] ) ) {
666
		if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) {
667
			$chart = sanitize_title( $_GET['chart'] );
668
			$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php";
669
		}
670
	} else {
671
		$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
672
	}
673
674
	$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...
675
	$method = 'GET';
676
	$timeout = 90;
677
	$user_id = JETPACK_MASTER_USER; // means send the wp.com user_id
678
679
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
680
	$get_code = wp_remote_retrieve_response_code( $get );
681
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
682
		stats_print_wp_remote_error( $get, $url );
683
	} else {
684
		if ( ! empty( $get['headers']['content-type'] ) ) {
685
			$type = $get['headers']['content-type'];
686
			if ( substr( $type, 0, 5 ) === 'image' ) {
687
				$img = $get['body'];
688
				header( 'Content-Type: ' . $type );
689
				header( 'Content-Length: ' . strlen( $img ) );
690
				echo $img;
691
				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...
692
			}
693
		}
694
		$body = stats_convert_post_titles( $get['body'] );
695
		$body = stats_convert_chart_urls( $body );
696
		$body = stats_convert_image_urls( $body );
697
		$body = stats_convert_admin_urls( $body );
698
		echo $body;
699
	}
700
701
	if ( isset( $_GET['page'] ) && 'stats' === $_GET['page'] && ! isset( $_GET['chart'] ) ) {
702
		JetpackTracking::record_user_event( 'wpa_page_view', array( 'path' => 'old_stats' ) );
703
	}
704
705
	if ( isset( $_GET['noheader'] ) ) {
706
		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...
707
	}
708
}
709
710
/**
711
 * Stats Convert Admin Urls.
712
 *
713
 * @access public
714
 * @param mixed $html HTML.
715
 * @return string
716
 */
717
function stats_convert_admin_urls( $html ) {
718
	return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html );
719
}
720
721
/**
722
 * Stats Convert Image URLs.
723
 *
724
 * @access public
725
 * @param mixed $html HTML.
726
 * @return string
727
 */
728
function stats_convert_image_urls( $html ) {
729
	$url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER );
730
	$html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html );
731
	return $html;
732
}
733
734
/**
735
 * Callback for preg_replace_callback used in stats_convert_chart_urls()
736
 *
737
 * @since 5.6.0
738
 *
739
 * @param  array  $matches The matches resulting from the preg_replace_callback call.
740
 * @return string          The admin url for the chart.
741
 */
742
function jetpack_stats_convert_chart_urls_callback( $matches ) {
743
	// If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string.
744
	return 'admin.php?page=stats&noheader&chart=' . $matches[1] . str_replace( '?', '&', $matches[2] );
745
}
746
747
/**
748
 * Stats Convert Chart URLs.
749
 *
750
 * @access public
751
 * @param mixed $html HTML.
752
 * @return string
753
 */
754
function stats_convert_chart_urls( $html ) {
755
	$html = preg_replace_callback(
756
		'|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',
757
		'jetpack_stats_convert_chart_urls_callback',
758
		$html
759
	);
760
	return $html;
761
}
762
763
/**
764
 * Stats Convert Post Title HTML
765
 *
766
 * @access public
767
 * @param mixed $html HTML.
768
 * @return string
769
 */
770
function stats_convert_post_titles( $html ) {
771
	global $stats_posts;
772
	$pattern = "<span class='post-(\d+)-link'>.*?</span>";
773
	if ( ! preg_match_all( "!$pattern!", $html, $matches ) ) {
774
		return $html;
775
	}
776
	$posts = get_posts(
777
		array(
778
			'include' => implode( ',', $matches[1] ),
779
			'post_type' => 'any',
780
			'post_status' => 'any',
781
			'numberposts' => -1,
782
			'suppress_filters' => false,
783
		)
784
	);
785
	foreach ( $posts as $post ) {
786
		$stats_posts[ $post->ID ] = $post;
787
	}
788
	$html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html );
789
	return $html;
790
}
791
792
/**
793
 * Stats Convert Post Title Matches.
794
 *
795
 * @access public
796
 * @param mixed $matches Matches.
797
 * @return string
798
 */
799
function stats_convert_post_title( $matches ) {
800
	global $stats_posts;
801
	$post_id = $matches[1];
802
	if ( isset( $stats_posts[$post_id] ) )
803
		return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>';
804
	return $matches[0];
805
}
806
807
/**
808
 * Stats Configuration Load.
809
 *
810
 * @access public
811
 * @return void
812
 */
813
function stats_configuration_load() {
814
	if ( isset( $_POST['action'] ) && 'save_options' === $_POST['action'] && $_POST['_wpnonce'] === wp_create_nonce( 'stats' ) ) {
815
		$options = stats_get_options();
816
		$options['admin_bar']  = isset( $_POST['admin_bar']  ) && $_POST['admin_bar'];
817
		$options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile'];
818
819
		$options['roles'] = array( 'administrator' );
820 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
821
			if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] ) {
822
				$options['roles'][] = $role;
823
			}
824
		}
825
826
		$options['count_roles'] = array();
827 View Code Duplication
		foreach ( get_editable_roles() as $role => $details ) {
828
			if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] ) {
829
				$options['count_roles'][] = $role;
830
			}
831
		}
832
833
		stats_set_options( $options );
834
		stats_update_blog();
835
		Jetpack::state( 'message', 'module_configured' );
836
		wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) );
837
		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...
838
	}
839
}
840
841
/**
842
 * Stats Configuration Head.
843
 *
844
 * @access public
845
 * @return void
846
 */
847
function stats_configuration_head() {
848
?>
849
	<style type="text/css">
850
		#statserror {
851
			border: 1px solid #766;
852
			background-color: #d22;
853
			padding: 1em 3em;
854
		}
855
		.stats-smiley {
856
			vertical-align: 1px;
857
		}
858
	</style>
859
	<?php
860
}
861
862
/**
863
 * Stats Configuration Screen.
864
 *
865
 * @access public
866
 * @return void
867
 */
868
function stats_configuration_screen() {
869
	$options = stats_get_options();
870
?>
871
	<div class="narrow">
872
		<p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p>
873
		<form method="post">
874
		<input type='hidden' name='action' value='save_options' />
875
		<?php wp_nonce_field( 'stats' ); ?>
876
		<table id="menu" class="form-table">
877
		<tr valign="top"><th scope="row"><label for="admin_bar"><?php esc_html_e( 'Admin bar' , 'jetpack' ); ?></label></th>
878
		<td><label><input type='checkbox'<?php checked( $options['admin_bar'] ); ?> name='admin_bar' id='admin_bar' /> <?php esc_html_e( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ); ?></label></td></tr>
879
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Registered users', 'jetpack' ); ?></th>
880
		<td>
881
			<?php esc_html_e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/>
882
			<?php
883
	$count_roles = stats_get_option( 'count_roles' );
884
	foreach ( get_editable_roles() as $role => $details ) {
885
?>
886
				<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/>
887
				<?php
888
	}
889
?>
890
		</td></tr>
891
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Smiley' , 'jetpack' ); ?></th>
892
		<td><label><input type='checkbox'<?php checked( isset( $options['hide_smile'] ) && $options['hide_smile'] ); ?> name='hide_smile' id='hide_smile' /> <?php esc_html_e( 'Hide the stats smiley face image.', 'jetpack' ); ?></label><br /> <span class="description"><?php echo wp_kses( __( 'The image helps collect stats and <strong>makes the world a better place</strong> but should still work when hidden', 'jetpack' ), array( 'strong' => array() ) ); ?> <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>
893
		<tr valign="top"><th scope="row"><?php esc_html_e( 'Report visibility' , 'jetpack' ); ?></th>
894
		<td>
895
			<?php esc_html_e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/>
896
			<?php
897
	$stats_roles = stats_get_option( 'roles' );
898
	foreach ( get_editable_roles() as $role => $details ) {
899
?>
900
				<label><input type='checkbox' <?php if ( 'administrator' === $role ) echo "disabled='disabled' "; ?>name='role_<?php echo $role; ?>'<?php checked( 'administrator' === $role || in_array( $role, $stats_roles ) ); ?> /> <?php echo translate_user_role( $details['name'] ); ?></label><br/>
901
				<?php
902
	}
903
?>
904
		</td></tr>
905
		</table>
906
		<p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p>
907
		</form>
908
	</div>
909
	<?php
910
}
911
912
/**
913
 * Stats Hide Smile.
914
 *
915
 * @access public
916
 * @return void
917
 */
918
function stats_hide_smile_css() {
919
	$options = stats_get_options();
920
	if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) {
921
?>
922
<style type='text/css'>img#wpstats{display:none}</style><?php
923
	}
924
}
925
926
/**
927
 * Stats Admin Bar Head.
928
 *
929
 * @access public
930
 * @return void
931
 */
932
function stats_admin_bar_head() {
933
	if ( ! stats_get_option( 'admin_bar' ) )
934
		return;
935
936
	if ( ! current_user_can( 'view_stats' ) )
937
		return;
938
939
	if ( function_exists( 'is_admin_bar_showing' ) && ! is_admin_bar_showing() ) {
940
		return;
941
	}
942
943
	add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 );
944
?>
945
946
<style type='text/css'>
947
#wpadminbar .quicklinks li#wp-admin-bar-stats {
948
	height: 32px;
949
}
950
#wpadminbar .quicklinks li#wp-admin-bar-stats a {
951
	height: 32px;
952
	padding: 0;
953
}
954
#wpadminbar .quicklinks li#wp-admin-bar-stats a div {
955
	height: 32px;
956
	width: 95px;
957
	overflow: hidden;
958
	margin: 0 10px;
959
}
960
#wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
961
	width: auto;
962
	margin: 0 8px 0 10px;
963
}
964
#wpadminbar .quicklinks li#wp-admin-bar-stats a img {
965
	height: 24px;
966
	padding: 4px 0;
967
	max-width: none;
968
	border: none;
969
}
970
</style>
971
<?php
972
}
973
974
/**
975
 * Stats AdminBar.
976
 *
977
 * @access public
978
 * @param mixed $wp_admin_bar WPAdminBar.
979
 * @return void
980
 */
981
function stats_admin_bar_menu( &$wp_admin_bar ) {
982
	$url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side.
983
984
	$img_src = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale' ), $url ) );
985
	$img_src_2x = esc_attr( add_query_arg( array( 'noheader' => '', 'proxy' => '', 'chart' => 'admin-bar-hours-scale-2x' ), $url ) );
986
987
	$alt = esc_attr( __( 'Stats', 'jetpack' ) );
988
989
	$title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) );
990
991
	$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 );
992
993
	$wp_admin_bar->add_menu( $menu );
994
}
995
996
/**
997
 * Stats Update Blog.
998
 *
999
 * @access public
1000
 * @return void
1001
 */
1002
function stats_update_blog() {
1003
	Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() );
1004
}
1005
1006
/**
1007
 * Stats Get Blog.
1008
 *
1009
 * @access public
1010
 * @return string
1011
 */
1012
function stats_get_blog() {
1013
	$home = parse_url( trailingslashit( get_option( 'home' ) ) );
1014
	$blog = array(
1015
		'host'                => $home['host'],
1016
		'path'                => $home['path'],
1017
		'blogname'            => get_option( 'blogname' ),
1018
		'blogdescription'     => get_option( 'blogdescription' ),
1019
		'siteurl'             => get_option( 'siteurl' ),
1020
		'gmt_offset'          => get_option( 'gmt_offset' ),
1021
		'timezone_string'     => get_option( 'timezone_string' ),
1022
		'stats_version'       => STATS_VERSION,
1023
		'stats_api'           => 'jetpack',
1024
		'page_on_front'       => get_option( 'page_on_front' ),
1025
		'permalink_structure' => get_option( 'permalink_structure' ),
1026
		'category_base'       => get_option( 'category_base' ),
1027
		'tag_base'            => get_option( 'tag_base' ),
1028
	);
1029
	$blog = array_merge( stats_get_options(), $blog );
1030
	unset( $blog['roles'], $blog['blog_id'] );
1031
	return stats_esc_html_deep( $blog );
1032
}
1033
1034
/**
1035
 * Modified from stripslashes_deep()
1036
 *
1037
 * @access public
1038
 * @param mixed $value Value.
1039
 * @return string
1040
 */
1041
function stats_esc_html_deep( $value ) {
1042
	if ( is_array( $value ) ) {
1043
		$value = array_map( 'stats_esc_html_deep', $value );
1044
	} elseif ( is_object( $value ) ) {
1045
		$vars = get_object_vars( $value );
1046
		foreach ( $vars as $key => $data ) {
1047
			$value->{$key} = stats_esc_html_deep( $data );
1048
		}
1049
	} elseif ( is_string( $value ) ) {
1050
		$value = esc_html( $value );
1051
	}
1052
1053
	return $value;
1054
}
1055
1056
/**
1057
 * Stats xmlrpc_methods function.
1058
 *
1059
 * @access public
1060
 * @param mixed $methods Methods.
1061
 * @return array
1062
 */
1063
function stats_xmlrpc_methods( $methods ) {
1064
	$my_methods = array(
1065
		'jetpack.getBlog' => 'stats_get_blog',
1066
	);
1067
1068
	return array_merge( $methods, $my_methods );
1069
}
1070
1071
/**
1072
 * Register Stats Dashboard Widget.
1073
 *
1074
 * @access public
1075
 * @return void
1076
 */
1077
function stats_register_dashboard_widget() {
1078
	if ( ! current_user_can( 'view_stats' ) )
1079
		return;
1080
1081
	// With wp_dashboard_empty: we load in the content after the page load via JS.
1082
	wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' );
1083
1084
	add_action( 'admin_head', 'stats_dashboard_head' );
1085
}
1086
1087
/**
1088
 * Stats Dashboard Widget Options.
1089
 *
1090
 * @access public
1091
 * @return array
1092
 */
1093
function stats_dashboard_widget_options() {
1094
	$defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 );
1095
	if ( ( ! $options = get_option( 'stats_dashboard_widget' ) ) || ! is_array( $options ) ) {
1096
		$options = array();
1097
	}
1098
1099
	// Ignore obsolete option values.
1100
	$intervals = array( 1, 7, 31, 90, 365 );
1101
	foreach ( array( 'top', 'search' ) as $key ) {
1102
		if ( isset( $options[ $key ] ) && ! in_array( $options[ $key ], $intervals ) ) {
1103
			unset( $options[ $key ] );
1104
		}
1105
	}
1106
1107
		return array_merge( $defaults, $options );
1108
}
1109
1110
/**
1111
 * Stats Dashboard Widget Control.
1112
 *
1113
 * @access public
1114
 * @return void
1115
 */
1116
function stats_dashboard_widget_control() {
1117
	$periods   = array(
1118
		'1' => __( 'day', 'jetpack' ),
1119
		'7' => __( 'week', 'jetpack' ),
1120
		'31' => __( 'month', 'jetpack' ),
1121
	);
1122
	$intervals = array(
1123
		'1' => __( 'the past day', 'jetpack' ),
1124
		'7' => __( 'the past week', 'jetpack' ),
1125
		'31' => __( 'the past month', 'jetpack' ),
1126
		'90' => __( 'the past quarter', 'jetpack' ),
1127
		'365' => __( 'the past year', 'jetpack' ),
1128
	);
1129
	$defaults = array(
1130
		'top' => 1,
1131
		'search' => 7,
1132
	);
1133
1134
	$options = stats_dashboard_widget_options();
1135
1136
	if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' === $_POST['widget_id'] ) {
1137
		if ( isset( $periods[ $_POST['chart'] ] ) ) {
1138
			$options['chart'] = $_POST['chart'];
1139
		}
1140
		foreach ( array( 'top', 'search' ) as $key ) {
1141
			if ( isset( $intervals[ $_POST[ $key ] ] ) ) {
1142
				$options[ $key ] = $_POST[ $key ];
1143
			} else { $options[ $key ] = $defaults[ $key ];
1144
			}
1145
		}
1146
		update_option( 'stats_dashboard_widget', $options );
1147
	}
1148
?>
1149
	<p>
1150
	<label for="chart"><?php esc_html_e( 'Chart stats by' , 'jetpack' ); ?></label>
1151
	<select id="chart" name="chart">
1152
	<?php
1153
	foreach ( $periods as $val => $label ) {
1154
?>
1155
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option>
1156
		<?php
1157
	}
1158
?>
1159
	</select>.
1160
	</p>
1161
1162
	<p>
1163
	<label for="top"><?php esc_html_e( 'Show top posts over', 'jetpack' ); ?></label>
1164
	<select id="top" name="top">
1165
	<?php
1166 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1167
?>
1168
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option>
1169
		<?php
1170
	}
1171
?>
1172
	</select>.
1173
	</p>
1174
1175
	<p>
1176
	<label for="search"><?php esc_html_e( 'Show top search terms over', 'jetpack' ); ?></label>
1177
	<select id="search" name="search">
1178
	<?php
1179 View Code Duplication
	foreach ( $intervals as $val => $label ) {
1180
?>
1181
		<option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option>
1182
		<?php
1183
	}
1184
?>
1185
	</select>.
1186
	</p>
1187
	<?php
1188
}
1189
1190
/**
1191
 * Jetpack Stats Dashboard Widget.
1192
 *
1193
 * @access public
1194
 * @return void
1195
 */
1196
function stats_jetpack_dashboard_widget() {
1197
?>
1198
	<form id="stats_dashboard_widget_control" action="<?php echo esc_url( admin_url() ); ?>" method="post">
1199
		<?php stats_dashboard_widget_control(); ?>
1200
		<?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?>
1201
		<input type="hidden" name="widget_id" value="dashboard_stats" />
1202
		<?php submit_button( __( 'Submit', 'jetpack' ) ); ?>
1203
	</form>
1204
	<span id="js-toggle-stats_dashboard_widget_control">
1205
		<?php esc_html_e( 'Configure', 'jetpack' ); ?>
1206
	</span>
1207
	<div id="dashboard_stats">
1208
		<div class="inside">
1209
			<div style="height: 250px;"></div>
1210
		</div>
1211
	</div>
1212
	<script>
1213
		jQuery(document).ready(function($){
1214
			var $toggle = $('#js-toggle-stats_dashboard_widget_control');
1215
1216
			$toggle.parent().prev().append( $toggle );
1217
			$toggle.show().click(function(e){
1218
				e.preventDefault();
1219
				e.stopImmediatePropagation();
1220
				$(this).parent().toggleClass('controlVisible');
1221
				$('#stats_dashboard_widget_control').slideToggle();
1222
			});
1223
		});
1224
	</script>
1225
	<style>
1226
		#js-toggle-stats_dashboard_widget_control {
1227
			display: none;
1228
			float: right;
1229
			margin-top: 0.2em;
1230
			font-weight: 400;
1231
			color: #444;
1232
			font-size: .8em;
1233
			text-decoration: underline;
1234
			cursor: pointer;
1235
		}
1236
		#stats_dashboard_widget_control {
1237
			display: none;
1238
			padding: 0 10px;
1239
			overflow: hidden;
1240
		}
1241
		#stats_dashboard_widget_control .button-primary {
1242
			float: right;
1243
		}
1244
		#dashboard_stats {
1245
			box-sizing: border-box;
1246
			width: 100%;
1247
			padding: 0 10px;
1248
		}
1249
	</style>
1250
	<?php
1251
}
1252
1253
/**
1254
 * Register Stats Widget Control Callback.
1255
 *
1256
 * @access public
1257
 * @return void
1258
 */
1259
function stats_register_widget_control_callback() {
1260
	$GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control';
1261
}
1262
1263
/**
1264
 * JavaScript and CSS for dashboard widget.
1265
 *
1266
 * @access public
1267
 * @return void
1268
 */
1269
function stats_dashboard_head() { ?>
1270
<script type="text/javascript">
1271
/* <![CDATA[ */
1272
jQuery( function($) {
1273
	var dashStats = jQuery( '#dashboard_stats div.inside' );
1274
1275
	if ( dashStats.find( '.dashboard-widget-control-form' ).length ) {
1276
		return;
1277
	}
1278
1279
	if ( ! dashStats.length ) {
1280
		dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' );
1281
		var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() );
1282
		var args = 'width=' + dashStats.width() + '&height=' + h.toString();
1283
	} else {
1284
		if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) {
1285
			var args = 'width=' + ( dashStats.prev().width() * 2 ).toString();
1286
		} else {
1287
			var args = 'width=' + ( dashStats.width() * 2 ).toString();
1288
		}
1289
	}
1290
1291
	dashStats
1292
		.not( '.dashboard-widget-control' )
1293
		.load( 'admin.php?page=stats&noheader&dashboard&' + args );
1294
1295
	jQuery( window ).one( 'resize', function() {
1296
		jQuery( '#stat-chart' ).css( 'width', 'auto' );
1297
	} );
1298
} );
1299
/* ]]> */
1300
</script>
1301
<style type="text/css">
1302
/* <![CDATA[ */
1303
#stat-chart {
1304
	background: none !important;
1305
}
1306
#dashboard_stats .inside {
1307
	margin: 10px 0 0 0 !important;
1308
}
1309
#dashboard_stats #stats-graph {
1310
	margin: 0;
1311
}
1312
#stats-info {
1313
	border-top: 1px solid #dfdfdf;
1314
	margin: 7px -10px 0 -10px;
1315
	padding: 10px;
1316
	background: #fcfcfc;
1317
	-moz-box-shadow:inset 0 1px 0 #fff;
1318
	-webkit-box-shadow:inset 0 1px 0 #fff;
1319
	box-shadow:inset 0 1px 0 #fff;
1320
	overflow: hidden;
1321
	border-radius: 0 0 2px 2px;
1322
	-webkit-border-radius: 0 0 2px 2px;
1323
	-moz-border-radius: 0 0 2px 2px;
1324
	-khtml-border-radius: 0 0 2px 2px;
1325
}
1326
#stats-info #top-posts, #stats-info #top-search {
1327
	float: left;
1328
	width: 50%;
1329
}
1330
#stats-info #top-posts {
1331
	padding-right: 3%;
1332
}
1333
#top-posts .stats-section-inner p {
1334
	white-space: nowrap;
1335
	overflow: hidden;
1336
}
1337
#top-posts .stats-section-inner p a {
1338
	overflow: hidden;
1339
	text-overflow: ellipsis;
1340
}
1341
#stats-info div#active {
1342
	border-top: 1px solid #dfdfdf;
1343
	margin: 0 -10px;
1344
	padding: 10px 10px 0 10px;
1345
	-moz-box-shadow:inset 0 1px 0 #fff;
1346
	-webkit-box-shadow:inset 0 1px 0 #fff;
1347
	box-shadow:inset 0 1px 0 #fff;
1348
	overflow: hidden;
1349
}
1350
#top-search p {
1351
	color: #999;
1352
}
1353
#stats-info h3 {
1354
	font-size: 1em;
1355
	margin: 0 0 .5em 0 !important;
1356
}
1357
#stats-info p {
1358
	margin: 0 0 .25em;
1359
	color: #999;
1360
}
1361
#stats-info p.widget-loading {
1362
	margin: 1em 0 0;
1363
	color: #333;
1364
}
1365
#stats-info p a {
1366
	display: block;
1367
}
1368
#stats-info p a.button {
1369
	display: inline;
1370
}
1371
/* ]]> */
1372
</style>
1373
<?php
1374
}
1375
1376
/**
1377
 * Stats Dashboard Widget Content.
1378
 *
1379
 * @access public
1380
 * @return void
1381
 */
1382
function stats_dashboard_widget_content() {
1383
	if ( ! isset( $_GET['width'] ) || ( ! $width = (int) ( $_GET['width'] / 2 ) ) || $width < 250 ) {
1384
		$width = 370;
1385
	}
1386
	if ( ! isset( $_GET['height'] ) || ( ! $height = (int) $_GET['height'] - 36 ) || $height < 230 ) {
1387
		$height = 180;
1388
	}
1389
1390
	$_width  = $width  - 5;
1391
	$_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // Hack!
1392
1393
	$options = stats_dashboard_widget_options();
1394
	$blog_id = Jetpack_Options::get_option( 'id' );
1395
1396
	$q = array(
1397
		'noheader' => 'true',
1398
		'proxy' => '',
1399
		'blog' => $blog_id,
1400
		'page' => 'stats',
1401
		'chart' => '',
1402
		'unit' => $options['chart'],
1403
		'color' => get_user_option( 'admin_color' ),
1404
		'width' => $_width,
1405
		'height' => $_height,
1406
		'ssl' => is_ssl(),
1407
		'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),
1408
	);
1409
1410
	$url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php";
1411
1412
	$url = add_query_arg( $q, $url );
1413
	$method = 'GET';
1414
	$timeout = 90;
1415
	$user_id = JETPACK_MASTER_USER;
1416
1417
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1418
	$get_code = wp_remote_retrieve_response_code( $get );
1419
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1420
		stats_print_wp_remote_error( $get, $url );
1421
	} else {
1422
		$body = stats_convert_post_titles( $get['body'] );
1423
		$body = stats_convert_chart_urls( $body );
1424
		$body = stats_convert_image_urls( $body );
1425
		echo $body;
1426
	}
1427
1428
	$post_ids = array();
1429
1430
	$csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) );
1431
	$csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" );
1432
	/* Translators: Stats dashboard widget postviews list: "$post_title $views Views". */
1433
	$printf = __( '%1$s %2$s Views' , 'jetpack' );
1434
1435
	foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) {
1436
		if ( 0 === $post['post_id'] ) {
1437
			unset( $top_posts[$i] );
1438
			continue;
1439
		}
1440
		$post_ids[] = $post['post_id'];
1441
	}
1442
1443
	// Cache.
1444
	get_posts( array( 'include' => join( ',', array_unique( $post_ids ) ) ) );
1445
1446
	$searches = array();
1447
	foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) {
1448
		if ( 'encrypted_search_terms' === $search_term['searchterm'] ) {
1449
			continue;
1450
		}
1451
		$searches[] = esc_html( $search_term['searchterm'] );
1452
	}
1453
1454
?>
1455
<a class="button" href="admin.php?page=stats"><?php  esc_html_e( 'View All', 'jetpack' ); ?></a>
1456
<div id="stats-info">
1457
	<div id="top-posts" class='stats-section'>
1458
		<div class="stats-section-inner">
1459
		<h3 class="heading"><?php  esc_html_e( 'Top Posts' , 'jetpack' ); ?></h3>
1460
		<?php
1461
	if ( empty( $top_posts ) ) {
1462
?>
1463
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1464
			<?php
1465
	} else {
1466
		foreach ( $top_posts as $post ) {
1467
			if ( ! get_post( $post['post_id'] ) ) {
1468
				continue;
1469
			}
1470
?>
1471
				<p><?php printf(
1472
				$printf,
1473
				'<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',
1474
				number_format_i18n( $post['views'] )
1475
			); ?></p>
1476
				<?php
1477
		}
1478
	}
1479
?>
1480
		</div>
1481
	</div>
1482
	<div id="top-search" class='stats-section'>
1483
		<div class="stats-section-inner">
1484
		<h3 class="heading"><?php  esc_html_e( 'Top Searches' , 'jetpack' ); ?></h3>
1485
		<?php
1486
	if ( empty( $searches ) ) {
1487
?>
1488
			<p class="nothing"><?php  esc_html_e( 'Sorry, nothing to report.', 'jetpack' ); ?></p>
1489
			<?php
1490
	} else {
1491
?>
1492
			<p><?php echo join( ',&nbsp; ', $searches );?></p>
1493
			<?php
1494
	}
1495
?>
1496
		</div>
1497
	</div>
1498
</div>
1499
<div class="clear"></div>
1500
<?php
1501
	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...
1502
}
1503
1504
/**
1505
 * Stats Print WP Remote Error.
1506
 *
1507
 * @access public
1508
 * @param mixed $get Get.
1509
 * @param mixed $url URL.
1510
 * @return void
1511
 */
1512
function stats_print_wp_remote_error( $get, $url ) {
1513
	$state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 );
1514
	$previous_error = Jetpack::state( $state_name );
1515
	$error = md5( serialize( compact( 'get', 'url' ) ) );
1516
	Jetpack::state( $state_name, $error );
1517
	if ( $error !== $previous_error ) {
1518
?>
1519
	<div class="wrap">
1520
	<p><?php esc_html_e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p>
1521
	</div>
1522
<?php
1523
		return;
1524
	}
1525
?>
1526
	<div class="wrap">
1527
	<p><?php printf( __( 'We were unable to get your stats just now. Please reload this page to try again. If this error persists, please <a href="%1$s" target="_blank">contact support</a>. In your report please include the information below.', 'jetpack' ), 'https://support.wordpress.com/contact/?jetpack=needs-service' ); ?></p>
1528
	<pre>
1529
	User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>"
1530
	Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>"
1531
	API URL: "<?php echo esc_url( $url ); ?>"
1532
<?php
1533
if ( is_wp_error( $get ) ) {
1534
	foreach ( $get->get_error_codes() as $code ) {
1535
		foreach ( $get->get_error_messages( $code ) as $message ) {
1536
?>
1537
<?php print $code . ': "' . $message . '"' ?>
1538
1539
<?php
1540
		}
1541
	}
1542
} else {
1543
	$get_code = wp_remote_retrieve_response_code( $get );
1544
	$content_length = strlen( wp_remote_retrieve_body( $get ) );
1545
?>
1546
Response code: "<?php print $get_code ?>"
1547
Content length: "<?php print $content_length ?>"
1548
1549
<?php
1550
}
1551
	?></pre>
1552
	</div>
1553
	<?php
1554
}
1555
1556
/**
1557
 * Get stats from WordPress.com
1558
 *
1559
 * @param string $table The stats which you want to retrieve: postviews, or searchterms.
1560
 * @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...
1561
 *      An associative array of arguments.
1562
 *
1563
 *      @type bool    $end        The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01)
1564
 *                                and default timezone is UTC date. Default value is Now.
1565
 *      @type string  $days       The length of the desired time frame. Default is 30. Maximum 90 days.
1566
 *      @type int     $limit      The maximum number of records to return. Default is 10. Maximum 100.
1567
 *      @type int     $post_id    The ID of the post to retrieve stats data for
1568
 *      @type string  $summarize  If present, summarizes all matching records. Default Null.
1569
 *
1570
 * }
1571
 *
1572
 * @return array {
1573
 *      An array of post view data, each post as an array
1574
 *
1575
 *      array {
1576
 *          The post view data for a single post
1577
 *
1578
 *          @type string  $post_id         The ID of the post
1579
 *          @type string  $post_title      The title of the post
1580
 *          @type string  $post_permalink  The permalink for the post
1581
 *          @type string  $views           The number of views for the post within the $num_days specified
1582
 *      }
1583
 * }
1584
 */
1585
function stats_get_csv( $table, $args = null ) {
1586
	$defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' );
1587
1588
	$args = wp_parse_args( $args, $defaults );
1589
	$args['table'] = $table;
1590
	$args['blog_id'] = Jetpack_Options::get_option( 'id' );
1591
1592
	$stats_csv_url = add_query_arg( $args, 'https://stats.wordpress.com/csv.php' );
1593
1594
	$key = md5( $stats_csv_url );
1595
1596
	// Get cache.
1597
	$stats_cache = get_option( 'stats_cache' );
1598
	if ( ! $stats_cache || ! is_array( $stats_cache ) ) {
1599
		$stats_cache = array();
1600
	}
1601
1602
	// Return or expire this key.
1603
	if ( isset( $stats_cache[ $key ] ) ) {
1604
		$time = key( $stats_cache[ $key ] );
1605
		if ( time() - $time < 300 ) {
1606
			return $stats_cache[ $key ][ $time ];
1607
		}
1608
		unset( $stats_cache[ $key ] );
1609
	}
1610
1611
	$stats_rows = array();
1612
	do {
1613
		if ( ! $stats = stats_get_remote_csv( $stats_csv_url ) ) {
1614
			break;
1615
		}
1616
1617
		$labels = array_shift( $stats );
1618
1619
		if ( 0 === stripos( $labels[0], 'error' ) ) {
1620
			break;
1621
		}
1622
1623
		$stats_rows = array();
1624
		for ( $s = 0; isset( $stats[ $s ] ); $s++ ) {
1625
			$row = array();
1626
			foreach ( $labels as $col => $label ) {
1627
				$row[ $label ] = $stats[ $s ][ $col ];
1628
			}
1629
			$stats_rows[] = $row;
1630
		}
1631
	} while ( 0 );
1632
1633
	// Expire old keys.
1634 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1635
		if ( ! is_array( $cache ) || 300 < time() - key( $cache ) ) {
1636
			unset( $stats_cache[ $k ] );
1637
		}
1638
	}
1639
1640
		// Set cache.
1641
		$stats_cache[ $key ] = array( time() => $stats_rows );
1642
	update_option( 'stats_cache', $stats_cache );
1643
1644
	return $stats_rows;
1645
}
1646
1647
/**
1648
 * Stats get remote CSV.
1649
 *
1650
 * @access public
1651
 * @param mixed $url URL.
1652
 * @return array
1653
 */
1654
function stats_get_remote_csv( $url ) {
1655
	$method = 'GET';
1656
	$timeout = 90;
1657
	$user_id = JETPACK_MASTER_USER;
1658
1659
	$get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) );
1660
	$get_code = wp_remote_retrieve_response_code( $get );
1661
	if ( is_wp_error( $get ) || ( 2 !== intval( $get_code / 100 ) && 304 !== $get_code ) || empty( $get['body'] ) ) {
1662
		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...
1663
	} else {
1664
		return stats_str_getcsv( $get['body'] );
1665
	}
1666
}
1667
1668
/**
1669
 * Rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it.
1670
 *
1671
 * @access public
1672
 * @param mixed $csv CSV.
1673
 * @return array.
0 ignored issues
show
Documentation introduced by
The doc-type array. could not be parsed: Unknown type name "array." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1674
 */
1675
function stats_str_getcsv( $csv ) {
1676
	if ( function_exists( 'str_getcsv' ) ) {
1677
		$lines = str_getcsv( $csv, "\n" );
1678
		return array_map( 'str_getcsv', $lines );
1679
	}
1680
	if ( ! $temp = tmpfile() ) { // The tmpfile() automatically unlinks.
1681
		return false;
1682
	}
1683
1684
	$data = array();
1685
1686
	fwrite( $temp, $csv, strlen( $csv ) );
1687
	fseek( $temp, 0 );
1688
	while ( false !== $row = fgetcsv( $temp, 2000 ) ) {
1689
		$data[] = $row;
1690
	}
1691
	fclose( $temp );
1692
1693
	return $data;
1694
}
1695
1696
/**
1697
 * Abstract out building the rest api stats path.
1698
 *
1699
 * @param  string $resource Resource.
1700
 * @return string
1701
 */
1702
function jetpack_stats_api_path( $resource = '' ) {
1703
	$resource = ltrim( $resource, '/' );
1704
	return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource );
1705
}
1706
1707
/**
1708
 * Fetches stats data from the REST API.  Caches locally for 5 minutes.
1709
 *
1710
 * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
1711
 * @access public
1712
 * @param array  $args (default: array())  The args that are passed to the endpoint.
1713
 * @param string $resource (default: '') Optional sub-endpoint following /stats/.
1714
 * @return array|WP_Error.
0 ignored issues
show
Documentation introduced by
The doc-type array|WP_Error. could not be parsed: Unknown type name "WP_Error." at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1715
 */
1716
function stats_get_from_restapi( $args = array(), $resource = '' ) {
1717
	$endpoint    = jetpack_stats_api_path( $resource );
1718
	$api_version = '1.1';
1719
	$args        = wp_parse_args( $args, array() );
1720
	$cache_key   = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) );
1721
1722
	// Get cache.
1723
	$stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() );
1724
	if ( ! is_array( $stats_cache ) ) {
1725
		$stats_cache = array();
1726
	}
1727
1728
	// Return or expire this key.
1729
	if ( isset( $stats_cache[ $cache_key ] ) ) {
1730
		$time = key( $stats_cache[ $cache_key ] );
1731
		if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) {
1732
			$cached_stats = $stats_cache[ $cache_key ][ $time ];
1733
			$cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats );
1734
			return $cached_stats;
1735
		}
1736
		unset( $stats_cache[ $cache_key ] );
1737
	}
1738
1739
	// Do the dirty work.
1740
	$response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args );
1741
	if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1742
		// If bad, just return it, don't cache.
1743
		return $response;
1744
	}
1745
1746
	$data = json_decode( wp_remote_retrieve_body( $response ) );
1747
1748
	// Expire old keys.
1749 View Code Duplication
	foreach ( $stats_cache as $k => $cache ) {
1750
		if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) {
1751
			unset( $stats_cache[ $k ] );
1752
		}
1753
	}
1754
1755
	// Set cache.
1756
	$stats_cache[ $cache_key ] = array(
1757
		time() => $data,
1758
	);
1759
	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...
1760
1761
	return $data;
1762
}
1763
1764
/**
1765
 * Load CSS needed for Stats column width in WP-Admin area.
1766
 *
1767
 * @since 4.7.0
1768
 */
1769
function jetpack_stats_load_admin_css() {
1770
	?>
1771
	<style type="text/css">
1772
		.fixed .column-stats {
1773
			width: 5em;
1774
		}
1775
	</style>
1776
	<?php
1777
}
1778
1779
/**
1780
 * Set header for column that allows to go to WordPress.com to see an entry's stats.
1781
 *
1782
 * @param array $columns An array of column names.
1783
 *
1784
 * @since 4.7.0
1785
 *
1786
 * @return mixed
1787
 */
1788
function jetpack_stats_post_table( $columns ) { // Adds a stats link on the edit posts page
1789
	if ( ! current_user_can( 'view_stats' ) || ! Jetpack::is_user_connected() ) {
1790
		return $columns;
1791
	}
1792
	// Array-Fu to add before comments
1793
	$pos = array_search( 'comments', array_keys( $columns ) );
1794
	if ( ! is_int( $pos ) ) {
1795
		return $columns;
1796
	}
1797
	$chunks             = array_chunk( $columns, $pos, true );
1798
	$chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack' );
1799
1800
	return call_user_func_array( 'array_merge', $chunks );
1801
}
1802
1803
/**
1804
 * Set content for cell with link to an entry's stats in WordPress.com.
1805
 *
1806
 * @param string $column  The name of the column to display.
1807
 * @param int    $post_id The current post ID.
1808
 *
1809
 * @since 4.7.0
1810
 *
1811
 * @return mixed
1812
 */
1813
function jetpack_stats_post_table_cell( $column, $post_id ) {
1814
	if ( 'stats' == $column ) {
1815
		if ( 'publish' != get_post_status( $post_id ) ) {
1816
			printf(
1817
				'<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
1818
				esc_html__( 'No stats', 'jetpack' )
1819
			);
1820
		} else {
1821
			printf(
1822
				'<a href="%s" title="%s" class="dashicons dashicons-chart-bar" target="_blank"></a>',
1823
				esc_url( "https://wordpress.com/stats/post/$post_id/" . Jetpack::build_raw_urls( get_home_url() ) ),
1824
				esc_html__( 'View stats for this post in WordPress.com', 'jetpack' )
1825
			);
1826
		}
1827
	}
1828
}
1829