diff --git a/README.md b/README.md index d07f084..da835d0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ This plugin adds customizable blacklist to Statify to allow blocking of referer #### Referer Blacklist #### Add a list of domains (for simplicity only second-level, e.g. _example.com_ which blocks _everything.example.com_). +#### Target Blacklist #### +Add a list of target pages (e.g. _/test/page/_, _/?page_id=123_) that will be excluded from tracking. + #### IP Blacklist #### Add a list of IP addresses or subnets (e.g. _192.0.2.123_, _198.51.100.0/24_, _2001:db8:a0b:12f0::/64_). @@ -74,6 +77,7 @@ Because of this, an IP blacklist can only be applied while processing the reques ### 1.4.0 / work in progress ### * IP blacklist implemented (#7) +* Target page blacklist implemented (#8) ### 1.3.1 / 09.12.2016 ### * Continue filtering if no filter applies (#6) diff --git a/assets/screenshot-1.png b/assets/screenshot-1.png index 2b38ecf..6147f4c 100644 Binary files a/assets/screenshot-1.png and b/assets/screenshot-1.png differ diff --git a/inc/statifyblacklist.class.php b/inc/statifyblacklist.class.php index 197d846..05da829 100644 --- a/inc/statifyblacklist.class.php +++ b/inc/statifyblacklist.class.php @@ -85,7 +85,7 @@ public function __construct() { /* CronJob to clean up database */ if ( defined( 'DOING_CRON' ) && DOING_CRON ) { - if ( self::$_options['cron_referer'] == 1 ) { + if ( self::$_options['cron_referer'] == 1 || self::$_options['cron_target'] == 1 ) { add_action( 'statify_cleanup', array( 'StatifyBlacklist_Admin', 'cleanup_database' ) ); } } @@ -119,6 +119,10 @@ protected static function defaultOptions() { 'cron_referer' => 0, 'referer' => array(), 'referer_regexp' => 0, + 'active_target' => 0, + 'cron_target' => 0, + 'target' => array(), + 'target_regexp' => 0, 'active_ip' => 0, 'ip' => array(), 'version' => self::VERSION_MAIN @@ -130,8 +134,8 @@ protected static function defaultOptions() { * * @return TRUE if referer matches blacklist. * - * @since 1.0.0 - * @changed 1.4.0 + * @since 1.0.0 + * @since 1.4.0 Target and IP blacklist */ public static function apply_blacklist_filter() { /* Referer blacklist */ @@ -163,6 +167,33 @@ public static function apply_blacklist_filter() { } } + /* Target blacklist (since 1.4.0) */ + if ( isset( self::$_options['active_target'] ) && self::$_options['active_target'] != 0 ) { + /* Regular Expression filtering since 1.3.0 */ + if ( isset( self::$_options['target_regexp'] ) && self::$_options['target_regexp'] > 0 ) { + /* Get full referer string */ + $target = ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/' ); + /* Merge given regular expressions into one */ + $regexp = '/' . implode( "|", array_keys( self::$_options['target'] ) ) . '/'; + if ( self::$_options['target_regexp'] == 2 ) { + $regexp .= 'i'; + } + + /* Check blacklist (return NULL to continue filtering) */ + + return ( preg_match( $regexp, $target ) === 1 ) ? true : null; + } else { + /* Extract target page */ + $target = ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/' ); + /* Get blacklist */ + $blacklist = self::$_options['target']; + /* Check blacklist */ + if ( isset( $blacklist[ $target ] ) ) { + return true; + } + } + } + /* IP blacklist (since 1.4.0) */ if ( isset ( self::$_options['active_ip'] ) && self::$_options['active_ip'] != 0 ) { if ( ( $ip = self::getIP() ) !== false ) { diff --git a/inc/statifyblacklist_admin.class.php b/inc/statifyblacklist_admin.class.php index bd9236c..541478d 100644 --- a/inc/statifyblacklist_admin.class.php +++ b/inc/statifyblacklist_admin.class.php @@ -131,26 +131,57 @@ public static function cleanup_database() { die( __( 'Are you sure you want to do this?' ) ); } - global $wpdb; - - if ( isset( self::$_options['referer_regexp'] ) && self::$_options['referer_regexp'] > 0 ) { - /* Merge given regular expressions into one */ - $refererRegexp = implode( "|", array_keys( self::$_options['referer'] ) ); + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { + $cleanRef = ( self::$_options['cron_referer'] == 1 ); + $cleanTrg = ( self::$_options['cron_target'] == 1 ); } else { - /* Sanitize URLs */ - $referer = self::sanitizeURLs( self::$_options['referer'] ); + $cleanRef = true; + $cleanTrg = true; + } + + + if ( $cleanRef ) { + if ( isset( self::$_options['referer_regexp'] ) && self::$_options['referer_regexp'] > 0 ) { + /* Merge given regular expressions into one */ + $refererRegexp = implode( "|", array_keys( self::$_options['referer'] ) ); + } else { + /* Sanitize URLs */ + $referer = self::sanitizeURLs( self::$_options['referer'] ); - /* Build filter regexp */ - $refererRegexp = str_replace( '.', '\.', implode( '|', array_flip( $referer ) ) ); + /* Build filter regexp */ + $refererRegexp = str_replace( '.', '\.', implode( '|', array_flip( $referer ) ) ); + } } - if ( ! empty( $refererRegexp ) ) { + if ( $cleanTrg ) { + if ( isset( self::$_options['target_regexp'] ) && self::$_options['target_regexp'] > 0 ) { + /* Merge given regular expressions into one */ + $targetRegexp = implode( "|", array_keys( self::$_options['target'] ) ); + } else { + /* Build filter regexp */ + $targetRegexp = str_replace( '.', '\.', implode( '|', array_flip( self::$_options['target'] ) ) ); + } + } + + + if ( ! empty( $refererRegexp ) || ! empty( $targetRegexp ) ) { + global $wpdb; + /* Execute filter on database */ - $wpdb->query( - $wpdb->prepare( "DELETE FROM `$wpdb->statify` WHERE " - . ( ( self::$_options['referer_regexp'] == 1 ) ? " BINARY " : "" ) - . "referrer REGEXP %s", $refererRegexp ) - ); + if ( ! empty( $refererRegexp ) ) { + $wpdb->query( + $wpdb->prepare( "DELETE FROM `$wpdb->statify` WHERE " + . ( ( self::$_options['referer_regexp'] == 1 ) ? " BINARY " : "" ) + . "referrer REGEXP %s", $refererRegexp ) + ); + } + if ( ! empty( $targetRegexp ) ) { + $wpdb->query( + $wpdb->prepare( "DELETE FROM `$wpdb->statify` WHERE " + . ( ( self::$_options['target_regexp'] == 1 ) ? " BINARY " : "" ) + . "target REGEXP %s", $targetRegexp ) + ); + } /* Optimize DB */ $wpdb->query( "OPTIMIZE TABLE `$wpdb->statify`" ); @@ -195,10 +226,10 @@ function ( $r ) { */ private static function sanitizeIPs( $ips ) { return array_filter( $ips, function ( $ip ) { - return preg_match('/^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])'. - '(\/([0-9]|[1-2][0-9]|3[0-2]))?$/', $ip) || - preg_match('/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))'. - '(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/', $ip); + return preg_match( '/^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])' . + '(\/([0-9]|[1-2][0-9]|3[0-2]))?$/', $ip ) || + preg_match( '/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))' . + '(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/', $ip ); } ); } } diff --git a/test/StatifyBlacklistTest.php b/test/StatifyBlacklistTest.php index a8c1150..39e3a41 100644 --- a/test/StatifyBlacklistTest.php +++ b/test/StatifyBlacklistTest.php @@ -132,7 +132,7 @@ public function testUpgrade() { $optionsUpdated = get_option( 'statify-blacklist' ); /* Verify size against default options (no junk left) */ - $this->assertEquals( 7, sizeof( $optionsUpdated ) ); + $this->assertEquals( 11, sizeof( $optionsUpdated ) ); /* Verify that original attributes are unchanged */ $this->assertEquals( $options13['active_referer'], $optionsUpdated['active_referer'] ); @@ -294,6 +294,114 @@ public function testIPFilter() { $_SERVER['HTTP_X_REAL_IP'] = '2001:db8:a0b:12f0:0::1'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); } + + /** + * Test simple target filter. + */ + public function testTargetFilter() { + /* Prepare Options: 2 blacklisted domains, disabled */ + StatifyBlacklist::$_options = array( + 'active_referer' => 0, + 'cron_referer' => 0, + 'referer' => array( + 'example.com' => 0, + 'example.net' => 1 + ), + 'referer_regexp' => 0, + 'active_target' => 0, + 'cron_target' => 0, + 'target' => array( + '/excluded/page/' => 0, + '/?page_id=3' => 1 + ), + 'target_regexp' => 0, + 'active_ip' => 0, + 'ip' => array(), + 'version' => StatifyBlacklist::VERSION_MAIN + ); + + /* No multisite */ + StatifyBlacklist::$multisite = false; + + /* Empty target */ + unset( $_SERVER['REQUEST_URI'] ); + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + /* Non-blacklisted targets */ + $_SERVER['REQUEST_URI'] = ''; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/?page_id=1'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + /* Blacklisted referer */ + $_SERVER['REQUEST_URI'] = '/excluded/page/'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/?page_id=3'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + + /* Activate filter and run tests again */ + StatifyBlacklist::$_options['active_target'] = 1; + + unset( $_SERVER['REQUEST_URI'] ); + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + + $_SERVER['REQUEST_URI'] = ''; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/?page_id=1'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + + $_SERVER['REQUEST_URI'] = '/excluded/page/'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/?page_id=3'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + $_SERVER['REQUEST_URI'] = '/?page_id=3'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + } + + /** + * Test target filter using regular expressions. + */ + public function testTargetRegexFilter() { + /* Prepare Options: 2 regular expressions */ + StatifyBlacklist::$_options = array( + 'active_referer' => 1, + 'cron_referer' => 0, + 'referer' => array( + 'example.[a-z]+' => 0, + 'test' => 1 + ), + 'referer_regexp' => 1, + 'version' => 1.3 + ); + + /* No multisite */ + StatifyBlacklist::$multisite = false; + + /* No referer */ + unset( $_SERVER['HTTP_REFERER'] ); + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + /* Non-blacklisted referer */ + $_SERVER['HTTP_REFERER'] = 'http://not.evil'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + /* Blacklisted referer */ + $_SERVER['HTTP_REFERER'] = 'http://example.com'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + /* Blacklisted referer with path */ + $_SERVER['HTTP_REFERER'] = 'http://foobar.net/test/me'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + /* Matching both */ + $_SERVER['HTTP_REFERER'] = 'http://example.net/test/me'; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + /* Mathinc with wrong case */ + $_SERVER['HTTP_REFERER'] = 'http://eXaMpLe.NeT/tEsT/mE'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + + /* Set RegExp filter to case insensitive */ + StatifyBlacklist::$_options['referer_regexp'] = 2; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + } } /** @@ -346,3 +454,7 @@ function update_option( $option, $value, $autoload = null ) { global $mock_options; $mock_options[ $option ] = $value; } + +function wp_unslash ( $value ) { + return is_string( $value ) ? stripslashes( $value ) : $value; +} \ No newline at end of file diff --git a/views/settings_page.php b/views/settings_page.php index b1c1d67..8cdbe2a 100755 --- a/views/settings_page.php +++ b/views/settings_page.php @@ -24,6 +24,13 @@ $referer = explode( "\r\n", $_POST['statifyblacklist']['referer'] ); } + /* Extract target array */ + if ( empty( trim( $_POST['statifyblacklist']['target'] ) ) ) { + $target = array(); + } else { + $target = explode( "\r\n", str_replace( '\\\\', '\\', $_POST['statifyblacklist']['target'] ) ); + } + /* Extract IP array */ if ( empty( trim( $_POST['statifyblacklist']['ip'] ) ) ) { $ip = array(); @@ -38,9 +45,13 @@ 'cron_referer' => (int) @$_POST['statifyblacklist']['cron_referer'], 'referer' => array_flip( $referer ), 'referer_regexp' => (int) @$_POST['statifyblacklist']['referer_regexp'], - 'active_ip' => (int) @$_POST['statifyblacklist']['active_ip'], - 'ip' => $ip, - 'version' => StatifyBlacklist::VERSION_MAIN + 'active_target' => (int) @$_POST['statifyblacklist']['active_target'], + 'cron_target' => (int) @$_POST['statifyblacklist']['cron_target'], + 'target' => array_flip( $target ), + 'target_regexp' => (int) @$_POST['statifyblacklist']['target_regexp'], + 'active_ip' => (int) @$_POST['statifyblacklist']['active_ip'], + 'ip' => $ip, + 'version' => StatifyBlacklist::VERSION_MAIN ) ); @@ -52,81 +63,87 @@ $statifyBlacklistPostWarning = sprintf( __( 'Some IPs are invalid : %s', 'statify-blacklist' ), implode( ', ', $statifyBlacklistUpdateResult['ip'] ) ); } } else { - $statifyBlacklistPostSuccess = __('Settings updated successfully.', 'statify-blacklist'); + $statifyBlacklistPostSuccess = __( 'Settings updated successfully.', 'statify-blacklist' ); } } } ?>
-

+

'; - esc_html( 'Statify plugin is not active.'); + esc_html( 'Statify plugin is not active.' ); print '

'; } if ( isset( $statifyBlacklistPostWarning ) ) { print '

' . esc_html( $statifyBlacklistPostWarning ); print '
'; - esc_html_e('Settings have not been saved yet.', 'statify-blacklist'); + esc_html_e( 'Settings have not been saved yet.', 'statify-blacklist' ); print '

'; } if ( isset( $statifyBlacklistPostSuccess ) ) { - print '

'. - esc_html( $statifyBlacklistPostSuccess ). - '

'; + print '

' . + esc_html( $statifyBlacklistPostSuccess ) . + '

'; } ?> -
+

+
+

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+

    @@ -147,7 +227,10 @@
  • - () + + ( + ) +