Completed
Push — update/consent-widget-core-pri... ( 19f894...9f194e )
by
unknown
97:48 queued 87:37
created

stats.php ➔ stats_render_amp_footer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Module Name: Site Stats
4
 * Module Description: Collect valuable traffic stats and insights.
5
 * Sort Order: 1
6
 * Recommendation Order: 2
7
 * First Introduced: 1.1
8
 * Requires Connection: Yes
9
 * Auto Activate: Yes
10
 * Module Tags: Site Stats, Recommended
11
 * Feature: Engagement
12
 * Additional Search Queries: statistics, tracking, analytics, views, traffic, stats
13
 *
14
 * @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 Honors DNT for clients who don't want to be tracked. Defaults to false. Set to true to enable.
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, $rendered_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
	$rendered_stats_footer = false;
190
}
191
192
193
/**
194
 * Stats Build View Data.
195
 *
196
 * @access public
197
 * @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...
198
 */
199
function stats_build_view_data() {
200
	global $wp_the_query;
201
202
	$blog = Jetpack_Options::get_option( 'id' );
203
	$tz = get_option( 'gmt_offset' );
204
	$v = 'ext';
205
	$blog_url = parse_url( site_url() );
206
	$srv = $blog_url['host'];
207
	$j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
208
	if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) {
209
		// Store and reset the queried_object and queried_object_id
210
		// Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase.
211
		// Repro:
212
		// 1. Set home_url = https://ExamPle.com/
213
		// 2. Set show_on_front = page
214
		// 3. Set page_on_front = something
215
		// 4. Visit https://example.com/ !
216
		$queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null;
217
		$queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null;
218
		$post = $wp_the_query->get_queried_object_id();
219
		$wp_the_query->queried_object = $queried_object;
220
		$wp_the_query->queried_object_id = $queried_object_id;
221
	} else {
222
		$post = '0';
223
	}
224
225
	return compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
226
}
227
228
/**
229
 * Stats Add Shutdown Action.
230
 *
231
 * @access public
232
 * @return void
233
 */
234
function stats_add_shutdown_action() {
235
	// Just in case wp_footer isn't in your theme.
236
	add_action( 'shutdown',  'stats_footer', 101 );
237
}
238
239
/**
240
 * Stats Footer.
241
 *
242
 * @access public
243
 * @return void
244
 */
245
function stats_footer() {
246
	global $rendered_stats_footer;
247
248
	if ( ! $rendered_stats_footer ) {
249
		$data = stats_build_view_data();
250
		if ( Jetpack_AMP_Support::is_amp_request() ) {
251
			stats_render_amp_footer( $data );
252
		} else {
253
			stats_render_footer( $data );
254
		}
255
		$rendered_stats_footer = true;
256
	}
257
}
258
259
function stats_render_footer( $data ) {
260
	$script = 'https://stats.wp.com/e-' . gmdate( 'YW' ) . '.js';
261
	$data_stats_array = stats_array( $data );
262
263
	$stats_footer = <<<END
0 ignored issues
show
Unused Code introduced by
$stats_footer is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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