Skip to content

Commit

Permalink
Refactor JS snippet to use WP AJAX (#109)
Browse files Browse the repository at this point in the history
* Refactored JS tracking to use WP AJAX

The custom GET request intercepts normal page generation and might
trigger other plugins' actions before Statify is loaded. It also
provided an open door for lightweight malicious requests targeting the
statistics.
Using WP AJAX including Nonce verification reduces both problems.

* Move sanitization of target and referrer out of if-else block

* Reset AJAX request from jQuery back to vanilla JS with XmlHttpRequest

* Remove superflous sanitization in pre-sanitized var in target filter

* rework target and referrer sanitization again

* Use minifed version of the snippet, set version to 1.7.0 (consistent with the PHPDoc)

Co-authored-by: Patrick Robrecht <[email protected]>
  • Loading branch information
stklcode and patrickrobrecht authored Apr 13, 2020
1 parent 2ab4a90 commit 71c4680
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 62 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
},
"globals": {
"Chartist": "readonly",
"statify_ajax": "readonly",
"statify_translations": "readonly"
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. This projec
* Introduced separate settinge page and reduced widget backview to widget settings only
* Add options to track logged in users, feeds and search requests
* Add option to show total visits
* Refactored JavaScript tracking to use WP AJAX
* Updated Chartist JS library for dashboard widget
* Introduced new option to separate display from storage range

Expand Down
93 changes: 60 additions & 33 deletions inc/class-statify-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,47 @@ class Statify_Frontend extends Statify {
* Track the page view
*
* @since 0.1.0
* @version 1.4.2
* @since 1.7.0 $is_snippet parameter added.
* @version 1.7.0
*
* @return bool
* @param boolean $is_snippet Is tracking triggered via JS (default: false).
*
* @return boolean
*/
public static function track_visit() {
public static function track_visit( $is_snippet = false ) {

/* Init vars */
// Check of JS snippet is configured.
$use_snippet = self::$_options['snippet'];
$is_snippet = $use_snippet && get_query_var( 'statify_target' );

/* Set target & referrer */
if ( $is_snippet ) {
$target = urldecode( get_query_var( 'statify_target' ) );
$referrer = urldecode( get_query_var( 'statify_referrer' ) );
// Set target & referrer.
$target = null;
$referrer = null;
if ( $use_snippet && $is_snippet ) {
if ( isset( $_REQUEST['statify_target'] ) ) {
$target = filter_var( wp_unslash( $_REQUEST['statify_target'] ), FILTER_SANITIZE_URL );
}
if ( isset( $_REQUEST['statify_referrer'] ) ) {
$referrer = filter_var( wp_unslash( $_REQUEST['statify_referrer'] ), FILTER_SANITIZE_URL );
}
} elseif ( ! $use_snippet ) {
$target = filter_var(
( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/' ),
FILTER_SANITIZE_URL
);
if ( is_null( $target ) || false === $target ) {
$target = '/';
} else {
$target = wp_unslash( $target );
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
$target = filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL );
}

$referrer = filter_var(
( isset( $_SERVER['HTTP_REFERER'] ) ? wp_unslash( $_SERVER['HTTP_REFERER'] ) : '' ),
FILTER_SANITIZE_URL
);
if ( is_null( $referrer ) || false === $referrer ) {
$referrer = '';
if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
$referrer = filter_var( wp_unslash( $_SERVER['HTTP_REFERER'] ), FILTER_SANITIZE_URL );
}
} else {
return false;
}

// Fallbacks for uninitialized or omitted target and referrer values.
if ( is_null( $target ) || false === $target ) {
$target = '/';
}
if ( is_null( $referrer ) || false === $referrer ) {
$referrer = '';
}

/* Invalid target? */
if ( empty( $target ) || ! wp_validate_redirect( $target, false ) ) {
return self::_jump_out( $is_snippet );
Expand Down Expand Up @@ -116,6 +121,20 @@ public static function track_visit() {
return self::_jump_out( $is_snippet );
}

/**
* Track the page view via AJAX.
*
* @return void
*/
public static function track_visit_ajax() {
// Check AJAX referrer.
check_ajax_referer( 'statify_track' );
// Only do something if snippet use is actually configured.
if ( self::$_options['snippet'] ) {
self::track_visit( true );
}
}

/**
* Find the position of the first occurrence of a substring in a string about a array.
*
Expand Down Expand Up @@ -353,23 +372,31 @@ public static function query_vars( $vars ) {
*/
public static function wp_footer() {

/* Skip by option */
// Skip by option.
if ( ! self::$_options['snippet'] ) {
return;
}

/* Skip by internal rules (#84) */
// Skip by internal rules (#84).
if ( self::_is_internal() ) {
return;
}

/* Load template */
load_template(
wp_normalize_path(
sprintf(
'%s/views/js-snippet.php',
STATIFY_DIR
)
wp_enqueue_script(
'statify-js',
plugins_url( 'js/snippet.min.js', STATIFY_FILE ),
array(),
STATIFY_VERSION,
true
);

// Add endpoint to script.
wp_localize_script(
'statify-js',
'statify_ajax',
array(
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'statify_track' ),
)
);
}
Expand Down
8 changes: 5 additions & 3 deletions inc/class-statify.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public static function instance() {
* @version 2017-01-10
*/
public function __construct() {
// Skip me!
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
// Nothing to do on autosave.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}

Expand All @@ -71,7 +71,9 @@ public function __construct() {
)
);

if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { // XMLRPC.
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
add_action( 'wp_ajax_nopriv_statify_track', array( 'Statify_Frontend', 'track_visit_ajax' ) );
} elseif ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { // XMLRPC.
add_filter( 'xmlrpc_methods', array( 'Statify_XMLRPC', 'xmlrpc_methods' ) );
} elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) { // Cron.
add_action( 'statify_cleanup', array( 'Statify_Cron', 'cleanup_data' ) );
Expand Down
11 changes: 6 additions & 5 deletions js/snippet.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
var statifyReq;
try {
statifyReq = new XMLHttpRequest();
statifyReq.open(
'GET',
document.getElementById( 'statify-js-snippet' ).getAttribute( 'data-home-url' ) +
'?statify_referrer=' + encodeURIComponent( document.referrer ) +
statifyReq.open( 'POST', statify_ajax.url, true );
statifyReq.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' );
statifyReq.send(
'_ajax_nonce=' + statify_ajax.nonce +
'&action=statify_track' +
'&statify_referrer=' + encodeURIComponent( document.referrer ) +
'&statify_target=' + encodeURIComponent( location.pathname + location.search )
);
statifyReq.send( null );
} catch ( e ) {
}
}() );
1 change: 1 addition & 0 deletions statify.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
define( 'STATIFY_FILE', __FILE__ );
define( 'STATIFY_DIR', dirname( __FILE__ ) );
define( 'STATIFY_BASE', plugin_basename( __FILE__ ) );
define( 'STATIFY_VERSION', '1.7.0' );


/* Hooks */
Expand Down
21 changes: 0 additions & 21 deletions views/js-snippet.php

This file was deleted.

0 comments on commit 71c4680

Please sign in to comment.