diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c323621 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +/assets export-ignore +/test export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +composer.json export-ignore +composer.lock export-ignore +CONTRIBUTING.md export-ignore +package.json export-ignore +phpcs.xml export-ignore +phpunit.xml export-ignore +RoboFile.php export-ignore diff --git a/.gitignore b/.gitignore index 29ba421..283f98c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock /dist/ .idea tests-clover.xml -tests-junit.xml \ No newline at end of file +tests-junit.xml +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index a1f594f..79000c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ language: php -dist: trusty php: - - '5.5' - '5.6' - - '7.0' - - '7.1' - '7.2' + - '7.3' + - '7.4' before_script: - composer install script: - composer test-all +notifications: + slack: + secure: "ScXTSMO65veI1jA6TBHGDUtvDqEMkqJykaNf7vLLbb7YIxPIHHNBiX/wcjOHVFfQXZCV3qxQrflB7Lbm9qVUsAv861jTO9x/ZkECl5QhRoc0DIznejwZoypx0HJ9tBZFYT6qNUkViXRKZ/ILAiBLU9Yw52WACtQB9hu3FNFZwmKsjipvV8Sne1qEyTkLYLaMphsbC5mtXYdKMHvdt39jsYsk91UWGeYbXQ37LkMbsaG/8YHXF724d5JO7BRGoThw6p5knKAO5fk29V7GfNqg2h+hnGyNIUOcmxujgMDMFLyFCGMZpPoBa+3jyWWgq4PgpQt0F5VZtJFGoXCGcoMQm5IbVfqkSKJ4jYhqiSIrqSebLmzoPHepWX3yn8tpfOiBWjC6K9w9esp6vcZf26rnAJcjcGkA01rMrHRwR+UEMCLvj7q0DR0qzi/AFeED6gtpODzUf93Rp42Tz1iGvWIbgeCtkCWjfPO6XLuNiqGVPEVaT5BDKqlqbijdKxxp7yh1fdt8s0fInWdIsgoWTbU9DC1W4ZiqtQW7oYO+QtFZMaD6kZWpSqJUwB3kW5JL3odAUEm8bLbRWBvK5ZjGdaGqSbOs6f9gAKcf86iQQhwzCJSOgFlLlKFv9smicjPC+BGOxgx32pgseHNPWn6tmEo/ihmmr/NbbqoOusUKX9gQbA4=" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b70affd..8e24a86 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,17 +33,19 @@ If the changes introduce new functionality or affect major parts of existing cod For adding new functionality a new test case the corresponding PHPUnit test would be nice (no hard criterion though). +The `master` branch should also be target for most pull requests. +However it it features new functionality you might want to target the `develop` branch instead (see next section for details on branches). + ### Branches The `master` branch represents the current state of development. Please ensure your initial code is up to date with it at the time you start development. -The `master` should also be target for most pull requests. In addition, this project features a `develop` branch, which holds bleeding edge developments, not necessarily considered stable or even compatible. Do not expect this code to run smoothly, but you might have a look into the history to see if some work on an issue has already been started there. -For fixes and features, there might be additional branches, likely prefixed by `ft-` (feature) or `hf-` (hotfix) followed by an issue number (if applicable) and/or a title. -Feel free to adapt these naming scheme to your forks. +For fixes and features, there might be additional branches, likely prefixed by `hotfix/` or `feature/` followed by an issue number (if applicable) and/or a title. +Feel free to adapt this naming scheme to your forks. ### Merge Requirements diff --git a/Gulpfile.js b/Gulpfile.js deleted file mode 100644 index 2fe2690..0000000 --- a/Gulpfile.js +++ /dev/null @@ -1,65 +0,0 @@ -var gulp = require('gulp'); -var clean = require('gulp-clean'); -var copy = require('gulp-copy'); -var zip = require('gulp-zip'); -var composer = require('gulp-composer'); -var phpunit = require('gulp-phpunit'); -var exec = require('child_process').exec; -var phpcs = require('gulp-phpcs'); -var config = require('./package.json'); - -// Clean the target directory. -gulp.task('clean', function () { - console.log('Cleaning up target directory ...'); - return gulp.src('dist', {read: false}) - .pipe(clean()); -}); - -// Prepare composer. -gulp.task('compose', function () { - console.log('Preparing Composer ...'); - return composer('install'); -}); - -// Execute unit tests. -gulp.task('test', ['compose'], function () { - console.log('Running PHPUnit tests ...'); - return gulp.src('phpunit.xml') - .pipe(phpunit('./vendor/bin/phpunit', {debug: false})); -}); - -// Execute PHP Code Sniffer. -gulp.task('test-cs', function (cb) { - return exec('./vendor/bin/phpcs --config-set installed_paths vendor/wimg/php-compatibility,vendor/wp-coding-standards/wpcs', function (err, stdout, stderr) { - console.log(stdout); - console.log(stderr); - if (null === err) { - console.log('Running PHP Code Sniffer tests ...'); - // exec('./vendor/bin/phpcs --standard=phpcs.xml', function(err, stdout, stderr) { - // console.log(stdout); - // console.log(stderr); - // }); - gulp.src(['statify-blacklist.php', 'inc/**/*.php']) - .pipe(phpcs({bin: './vendor/bin/phpcs', standard: 'phpcs.xml'})) - .pipe(phpcs.reporter('log')); - } - cb(err); - }); -}); - -// Bundle files as required for plugin distribution.. -gulp.task('bundle', ['clean'], function () { - console.log('Collecting files for package dist/' + config.name + config.version + ' ...'); - return gulp.src(['**/*.php', '!RoboFile.php', '!test/**', '!vendor/**', 'README.md', 'LICENSE.md'], {base: './'}) - .pipe(copy('./dist/' + config.name + '.' + config.version + '/' + config.name)); -}); - -// Create a ZIP package of the relevant files for plugin distribution. -gulp.task('package', ['bundle'], function () { - console.log('Building package dist/' + config.name + config.version + '.zip ...'); - return gulp.src('./dist/' + config.name + '.' + config.version + '/**') - .pipe(zip(config.name + '.' + config.version + '.zip')) - .pipe(gulp.dest('./dist')); -}); - -gulp.task('default', ['clean', 'compose', 'test', 'test-cs', 'bundle', 'package']); diff --git a/LICENSE.md b/LICENSE.md index 5544f2d..28fbeca 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -357,5 +357,5 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the [GNU Lesser General Public -License](http://www.gnu.org/licenses/lgpl.html) instead of this +License](https://www.gnu.org/licenses/lgpl.html) instead of this License. diff --git a/README.md b/README.md index 50b4654..443086e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ +[![Build Status](https://travis-ci.org/stklcode/statify-blacklist.svg?branch=master)](https://travis-ci.org/stklcode/statify-blacklist) +[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=de.stklcode.web.wordpress.plugins%3Astatify-blacklist&metric=alert_status)](https://sonarcloud.io/dashboard?id=de.stklcode.web.wordpress.plugins%3Astatify-blacklist) +[![Packagist Version](https://img.shields.io/packagist/v/stklcode/statify-blacklist.svg)](https://packagist.org/packages/stklcode/statify-blacklist) +[![License](https://img.shields.io/badge/license-GPL%20v2-blue.svg)](https://github.com/stklcode/statify-blacklist/blob/master/LICENSE.md) + # Statify Blacklist # * Contributors: Stefan Kalscheuer -* Requires at least: 4.4 +* Requires at least: 4.7 * Tested up to: 5.4 * Requires PHP: 5.5 -* Stable tag: 1.4.4 +* Stable tag: 1.5.0 * License: GPLv2 or later -* License URI: http://www.gnu.org/licenses/gpl-2.0.html +* License URI: https://www.gnu.org/licenses/gpl-2.0.html ## Description ## A blacklist extension for the famous [Statify](https://wordpress.org/plugins/statify/) Wordpress plugin. -This plugin adds customizable blacklist to Statify to allow blocking of referer spam or internal interactions. +This plugin adds a customizable blacklist to Statify to allow blocking of referer spam or internal interactions. ### Features ## @@ -27,7 +32,7 @@ Add a list of IP addresses or subnets (e.g. _192.0.2.123_, _198.51.100.0/24_, _2 Filters can be applied to data stored in database after modifying filter rules or for one-time clean-up. #### Compatibility #### -This plugin requires Statify to be installed. The extension has been tested with Statify up to version 1.5.1 +This plugin requires Statify to be installed. The extension has been tested with Statify up to version 1.7 The plugin is capable of handling multisite installations. ### Support & Contributions ### @@ -37,51 +42,62 @@ The plugin is capable of handling multisite installations. ### Credits ### * Author: Stefan Kalscheuer -* Special Thanks to [pluginkollektiv](https://github.com/pluginkollektiv) for maintaining _Statify_ +* Special Thanks to [pluginkollektiv](https://pluginkollektiv.org/) for maintaining _Statify_ ## Installation ## -* If you don’t know how to install a plugin for WordPress, [here’s how](http://codex.wordpress.org/Managing_Plugins#Installing_Plugins). -* Make sure _Statify_ plugin is installed and active +* If you don’t know how to install a plugin for WordPress, [here’s how](https://wordpress.org/support/article/managing-plugins/#installing-plugins). +* Make sure _Statify_ plugin is installed and active * Goto _Settings_ -> _Statify Blacklist_ to configure the plugin ### Requirements ### * PHP 5.5 or above -* WordPress 4.4 or above -* Statify plugin installed and activated (1.5.0 or above) +* WordPress 4.7 or above +* _Statify_ plugin installed and activated (1.5 or above) ## Frequently Asked Questions ## ### What is blocked by default? ### -Nothing. By default all blacklists are empty and disabled. They can and have to be filled by the blog administrator. +Nothing. By default, all blacklists are empty and disabled. They can and have to be filled by the blog administrator. A default blacklist is not provided, as the plugin itself is totally neutral. If you want to filter out referer spam, -visitors from search engines, just "false" referers from 301 redirects or you own IP address used for testing only depends on you. +visitors from search engines, just "false" referrers from 301 redirects or you own IP address used for testing only depends on you. ### Does the filter effect user experience? ### -No. It only prevent's _Statify_ from tracking, nothing more or less. +No. It only prevents _Statify_ from tracking, nothing more or less. ### Does live filtering impact performance? ### -Yes, but probalby not noticeable. Checking a single referer string against a (usually small) list should be negligible compared to the total loading procedure. +Yes, but probably not noticeable. Checking a single referer string against a (usually small) list should be negligible compared to the total loading procedure. If this still is an issue for you, consider deactivating the filter and only run the one-time-cleanup or activate the cron job. ### Is any personal data collected? ### No. The privacy policy of _Statify_ is untouched. Data is only processed, not stored or exposed to anyone. ### Are regular expression filters possible? ### -Yes, it is. Just select if you want to filter using regular expressions case sensitive or insensitive. - -Note, that regular expression matching is significantly slower than the plain domain filter. Hence it is only recommended for asynchronous cron or manual execution and not for live filtering. +Yes, it is. Just select regular expressions (case-sensitive or insensitive) as matching method instead of exact or keyword match. ### Why is IP filtering only available as live filter? ### -As you might know, Statify does not store any personal information, including IP addresses in the database. +As you might know, _Statify_ does not store any personal information, including IP addresses in the database. Because of this, an IP blacklist can only be applied while processing the request and not afterwards. +### Can whole IP subnet be blocked? ### +Yes. The plugin features subnet blacklists using CIDR notation. +For example _198.51.100.0/24_ blacklists all sources from _198.51.100.1_ to _198.51.100.254_. +Same for IPv6 prefixes like _2001:db8:a0b:12f0::/64_. + ## Screenshots ## 1. Statify Blacklist settings page ## Changelog ## +### 1.5.0 / 13.05.2020 ### +* Minimum required WordPress version is 4.7 +* Removed `load_plugin_textdomain()` and `Domain Path` header +* Added automatic compatibility check for WP and PHP version (#17) +* Added keyword filter mode for referer blacklist (#15) +* Layout adjustments on settings page +* Regular expression filters are validated before saving (#13) + ### 1.4.4 / 19.05.2018 ### * Fix live filter chain when regular expressions are active (#12) @@ -115,7 +131,7 @@ Because of this, an IP blacklist can only be applied while processing the reques ### 1.2.0 / 29.08.2016 ### * Switched from `in_array()` to faster `isset()` for referer checking -* Optional cron execiton implemented +* Optional cron execution implemented ### 1.1.2 / 17.08.2016 ### * Prepared for localization diff --git a/RoboFile.php b/RoboFile.php index e991634..5c76074 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -8,7 +8,7 @@ * @author Stefan Kalscheuer * * @package Statify_Blacklist - * @version 1.4.4 + * @version 1.5.0 */ use Robo\Exception\TaskException; @@ -19,10 +19,10 @@ */ class RoboFile extends Tasks { const PROJECT_NAME = 'statify-blacklist'; - const SVN_URL = 'https://plugins.svn.wordpress.org/statify-blacklist'; + const SVN_URL = 'https://plugins.svn.wordpress.org/statify-blacklist'; - const OPT_TARGET = 'target'; - const OPT_SKIPTEST = 'skipTests'; + const OPT_TARGET = 'target'; + const OPT_SKIPTEST = 'skipTests'; const OPT_SKIPSTYLE = 'skipStyle'; /** @@ -134,13 +134,21 @@ public function build( */ private function bundle() { $this->say( 'Bundling resources...' ); - $this->taskCopyDir( [ - 'inc' => $this->target_dir . '/' . $this->final_name . '/inc', - 'views' => $this->target_dir . '/' . $this->final_name . '/views', - ] )->run(); + $this->taskCopyDir( + [ + 'inc' => $this->target_dir . '/' . $this->final_name . '/inc', + 'views' => $this->target_dir . '/' . $this->final_name . '/views', + ] + )->run(); $this->_copy( 'statify-blacklist.php', $this->target_dir . '/' . $this->final_name . '/statify-blacklist.php' ); - $this->_copy( 'README.md', $this->target_dir . '/' . $this->final_name . '/README.md' ); $this->_copy( 'LICENSE.md', $this->target_dir . '/' . $this->final_name . '/LICENSE.md' ); + $this->_copy( 'README.md', $this->target_dir . '/' . $this->final_name . '/README.md' ); + + // Remove content before title (e.g. badges) from README file. + $this->taskReplaceInFile( $this->target_dir . '/' . $this->final_name . '/README.md' ) + ->regex( '/^[^\\#]*/' ) + ->to( '' ) + ->run(); } /** diff --git a/assets/screenshot-1.png b/assets/screenshot-1.png index 6147f4c..b03eb2c 100644 Binary files a/assets/screenshot-1.png and b/assets/screenshot-1.png differ diff --git a/composer.json b/composer.json index 0bf7586..bd136ef 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "stklcode/statify-blacklist", - "version": "1.4.4", + "version": "1.5.0", "description": "A blacklist extension for the famous Statify WordPress plugin", "keywords": [ "wordpress", @@ -19,17 +19,17 @@ "type": "wordpress-plugin", "require": { "php": ">=5.5", - "composer/installers": "~1.0" + "composer/installers": "~1.7" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4", - "consolidation/robo": "^1.0.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.5", + "consolidation/robo": "^1.4", "phpunit/phpunit": "*", "phpunit/php-code-coverage": "*", "slowprog/composer-copy-file": "~0.2", - "squizlabs/php_codesniffer": "^3.1", - "wimg/php-compatibility": "^8.0", - "wp-coding-standards/wpcs": "~0.14" + "squizlabs/php_codesniffer": "^3.5", + "phpcompatibility/php-compatibility": "^9.3", + "wp-coding-standards/wpcs": "^2.1" }, "scripts": { "build": [ diff --git a/inc/class-statifyblacklist-admin.php b/inc/class-statifyblacklist-admin.php index e807ef9..caed7fc 100644 --- a/inc/class-statifyblacklist-admin.php +++ b/inc/class-statifyblacklist-admin.php @@ -9,8 +9,10 @@ * @since 1.0.0 */ -// Quit. -defined( 'ABSPATH' ) || exit; +// Quit if accessed directly. +if ( ! defined( 'ABSPATH' ) ) { + exit; +} /** * Statify Blacklist admin configuration. @@ -18,37 +20,101 @@ * @since 1.0.0 */ class StatifyBlacklist_Admin extends StatifyBlacklist { + + /** + * Initialize admin-only components of the plugin. + * + * @since 1.5.0 + * + * @return void + */ + public static function init() { + // Add actions. + add_action( 'wpmu_new_blog', array( 'StatifyBlacklist_System', 'install_site' ) ); + add_action( 'delete_blog', array( 'StatifyBlacklist_System', 'uninstall_site' ) ); + add_filter( 'plugin_row_meta', array( 'StatifyBlacklist_Admin', 'plugin_meta_link' ), 10, 2 ); + + if ( self::$multisite ) { + add_action( 'network_admin_menu', array( 'StatifyBlacklist_Admin', 'add_menu_page' ) ); + add_filter( + 'network_admin_plugin_action_links', + array( + 'StatifyBlacklist_Admin', + 'plugin_actions_links', + ), + 10, + 2 + ); + } else { + add_action( 'admin_menu', array( 'StatifyBlacklist_Admin', 'add_menu_page' ) ); + add_filter( 'plugin_action_links', array( 'StatifyBlacklist_Admin', 'plugin_actions_links' ), 10, 2 ); + } + } + /** * Update options. * + * @since 1.1.1 + * * @param array $options Optional. New options to save. * * @return array|bool array of sanitized array on errors, FALSE if there were none. - * @since 1.1.1 */ public static function update_options( $options = null ) { if ( isset( $options ) && current_user_can( 'manage_options' ) ) { - // Sanitize URLs and remove empty inputs. - $given_referer = $options['referer']['blacklist']; - if ( 0 === $options['referer']['regexp'] ) { - $sanitized_referer = self::sanitizeURLs( $given_referer ); + + // Sanitize referer list. + $given_referer = $options['referer']['blacklist']; + $invalid_referer = array(); + if ( self::MODE_NORMAL === $options['referer']['regexp'] ) { + // Sanitize URLs and remove empty inputs. + $sanitized_referer = self::sanitize_urls( $given_referer ); + } elseif ( self::MODE_REGEX === $options['referer']['regexp'] || self::MODE_REGEX_CI === $options['referer']['regexp'] ) { + $sanitized_referer = $given_referer; + // Check regular expressions. + $invalid_referer = self::sanitize_regex( $given_referer ); } else { $sanitized_referer = $given_referer; } - // Sanitize IPs and Subnets and remove empty inputs. + // Sanitize target list. + $given_target = $options['target']['blacklist']; + $invalid_target = array(); + if ( self::MODE_REGEX === $options['target']['regexp'] || self::MODE_REGEX_CI === $options['target']['regexp'] ) { + $sanitized_target = $given_target; + // Check regular expressions. + $invalid_target = self::sanitize_regex( $given_target ); + } else { + $sanitized_target = $given_target; + } + + // Sanitize IPs and subnets and remove empty inputs. $given_ip = $options['ip']['blacklist']; - $sanitized_ip = self::sanitizeIPs( $given_ip ); + $sanitized_ip = self::sanitize_ips( $given_ip ); // Abort on errors. - if ( ! empty( array_diff( array_keys( $given_referer ), array_keys( $sanitized_referer ) ) ) ) { - return array( - 'referer' => $sanitized_referer, - ); - } elseif ( ! empty( array_diff( $given_ip, $sanitized_ip ) ) ) { - return array( - 'ip' => array_diff( $given_ip, $sanitized_ip ), - ); + $errors = array( + 'referer' => array( + 'sanitized' => $sanitized_referer, + 'diff' => array_diff( $given_referer, $sanitized_referer ), + 'invalid' => $invalid_referer, + ), + 'target' => array( + 'sanitized' => $sanitized_target, + 'diff' => array_diff( $given_target, $sanitized_target ), + 'invalid' => $invalid_target, + ), + 'ip' => array( + 'sanitized' => $sanitized_ip, + 'diff' => array_diff( $given_ip, $sanitized_ip ), + ), + ); + if ( ! empty( $errors['referer']['diff'] ) + || ! empty( $errors['referer']['invalid'] ) + || ! empty( $errors['target']['diff'] ) + || ! empty( $errors['target']['invalid'] ) + || ! empty( $errors['ip']['diff'] ) ) { + return $errors; } // Update database on success. @@ -74,14 +140,24 @@ public static function add_menu_page() { $title = __( 'Statify Blacklist', 'statify-blacklist' ); if ( self::$multisite ) { add_submenu_page( - 'settings.php', $title, $title, 'manage_network_plugins', 'statify-blacklist-settings', array( + 'settings.php', + $title, + $title, + 'manage_network_plugins', + 'statify-blacklist-settings', + array( 'StatifyBlacklist_Admin', 'settings_page', ) ); } else { add_submenu_page( - 'options-general.php', $title, $title, 'manage_options', 'statify-blacklist', array( + 'options-general.php', + $title, + $title, + 'manage_options', + 'statify-blacklist', + array( 'StatifyBlacklist_Admin', 'settings_page', ) @@ -154,20 +230,20 @@ public static function cleanup_database() { } if ( defined( 'DOING_CRON' ) && DOING_CRON ) { - $clean_ref = ( 1 === self::$_options['referer']['cron'] ); - $clean_trg = ( 1 === self::$_options['target']['cron'] ); + $clean_ref = ( 1 === self::$options['referer']['cron'] ); + $clean_trg = ( 1 === self::$options['target']['cron'] ); } else { $clean_ref = true; $clean_trg = true; } if ( $clean_ref ) { - if ( isset( self::$_options['referer']['regexp'] ) && self::$_options['referer']['regexp'] > 0 ) { + if ( isset( self::$options['referer']['regexp'] ) && self::$options['referer']['regexp'] > 0 ) { // Merge given regular expressions into one. - $referer_regexp = implode( '|', array_keys( self::$_options['referer']['blacklist'] ) ); + $referer_regexp = implode( '|', array_keys( self::$options['referer']['blacklist'] ) ); } else { // Sanitize URLs. - $referer = self::sanitizeURLs( self::$_options['referer']['blacklist'] ); + $referer = self::sanitize_urls( self::$options['referer']['blacklist'] ); // Build filter regexp. $referer_regexp = str_replace( '.', '\.', implode( '|', array_flip( $referer ) ) ); @@ -175,12 +251,12 @@ public static function cleanup_database() { } if ( $clean_trg ) { - if ( isset( self::$_options['target']['regexp'] ) && self::$_options['target']['regexp'] > 0 ) { + if ( isset( self::$options['target']['regexp'] ) && self::$options['target']['regexp'] > 0 ) { // Merge given regular expressions into one. - $target_regexp = implode( '|', array_keys( self::$_options['target']['blacklist'] ) ); + $target_regexp = implode( '|', array_keys( self::$options['target']['blacklist'] ) ); } else { // Build filter regexp. - $target_regexp = str_replace( '.', '\.', implode( '|', array_flip( self::$_options['target']['blacklist'] ) ) ); + $target_regexp = str_replace( '.', '\.', implode( '|', array_flip( self::$options['target']['blacklist'] ) ) ); } } @@ -188,13 +264,14 @@ public static function cleanup_database() { global $wpdb; // Execute filter on database. - // @codingStandardsIgnoreStart These statements prouce warnings, rework in future release (TODO). + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- These statements produce warnings, rework in future release (TODO). if ( ! empty( $referer_regexp ) ) { $wpdb->query( $wpdb->prepare( "DELETE FROM `$wpdb->statify` WHERE " - . ( ( 1 === self::$_options['referer']['regexp'] ) ? ' BINARY ' : '' ) - . 'referrer REGEXP %s', $referer_regexp + . ( ( 1 === self::$options['referer']['regexp'] ) ? ' BINARY ' : '' ) + . 'referrer REGEXP %s', + $referer_regexp ) ); } @@ -202,12 +279,13 @@ public static function cleanup_database() { $wpdb->query( $wpdb->prepare( "DELETE FROM `$wpdb->statify` WHERE " - . ( ( 1 === self::$_options['target']['regexp'] ) ? ' BINARY ' : '' ) - . 'target REGEXP %s', $target_regexp + . ( ( 1 === self::$options['target']['regexp'] ) ? ' BINARY ' : '' ) + . 'target REGEXP %s', + $target_regexp ) ); } - // @codingStandardsIgnoreEnd + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared // Optimize DB. $wpdb->query( "OPTIMIZE TABLE `$wpdb->statify`" ); @@ -227,7 +305,7 @@ public static function cleanup_database() { * * @return array sanitized array. */ - private static function sanitizeURLs( $urls ) { + private static function sanitize_urls( $urls ) { return array_flip( array_filter( array_map( @@ -249,15 +327,39 @@ function ( $r ) { * * @return array sanitized array. */ - private static function sanitizeIPs( $ips ) { + private static function sanitize_ips( $ips ) { return array_filter( - $ips, function ( $ip ) { + $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 + '/^((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 - ); + 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 + ); + } + ); + } + + /** + * Validate regular expressions, i.e. remove duplicates and empty values and validate others. + * + * @since 1.5.0 #13 + * + * @param array $expressions Given pre-sanitized array of regular expressions. + * + * @return array Array of invalid expressions. + */ + private static function sanitize_regex( $expressions ) { + return array_filter( + array_flip( $expressions ), + function ( $re ) { + // Check of preg_match() fails (warnings suppressed). + + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + return false === @preg_match( StatifyBlacklist::regex( $re, false ), null ); } ); } diff --git a/inc/class-statifyblacklist-system.php b/inc/class-statifyblacklist-system.php index a9160b1..842310b 100644 --- a/inc/class-statifyblacklist-system.php +++ b/inc/class-statifyblacklist-system.php @@ -1,6 +1,6 @@ array( - 'active' => self::$_options['active_referer'], - 'cron' => self::$_options['cron_referer'], - 'regexp' => self::$_options['referer_regexp'], - 'blacklist' => self::$_options['referer'], + 'active' => self::$options['active_referer'], + 'cron' => self::$options['cron_referer'], + 'regexp' => self::$options['referer_regexp'], + 'blacklist' => self::$options['referer'], ), 'target' => array( 'active' => 0, @@ -170,9 +179,9 @@ public static function upgrade() { } // Version older than current major release. - if ( self::VERSION_MAIN > self::$_options['version'] ) { + if ( self::VERSION_MAIN > self::$options['version'] ) { // Merge default options with current config, assuming only additive changes. - $options = array_merge_recursive( self::default_options(), self::$_options ); + $options = array_merge_recursive( self::default_options(), self::$options ); $options['version'] = self::VERSION_MAIN; if ( self::$multisite ) { update_site_option( 'statify-blacklist', $options ); diff --git a/inc/class-statifyblacklist.php b/inc/class-statifyblacklist.php index 3ebea16..abc7780 100644 --- a/inc/class-statifyblacklist.php +++ b/inc/class-statifyblacklist.php @@ -8,8 +8,10 @@ * @since 1.0.0 */ -// Quit. -defined( 'ABSPATH' ) || exit; +// Quit if accessed directly. +if ( ! defined( 'ABSPATH' ) ) { + exit; +} /** * Statify Blacklist. @@ -27,45 +29,56 @@ class StatifyBlacklist { const VERSION_MAIN = 1.4; /** - * Plugin options. + * Operation mode "normal". * - * @since 1.0.0 - * @var array $_options + * @var integer MODE_NORMAL */ - public static $_options; + const MODE_NORMAL = 0; /** - * Multisite Status. + * Operation mode "regular expression". * - * @since 1.0.0 - * @var bool $multisite + * @var integer MODE_REGEX */ - public static $multisite; + const MODE_REGEX = 1; + + /** + * Operation mode "regular expression case insensitive". + * + * @var integer MODE_REGEX_CI + */ + const MODE_REGEX_CI = 2; + + /** + * Operation mode "keyword". + * + * @since 1.5.0 + * @var integer MODE_KEYWORD + */ + const MODE_KEYWORD = 3; /** - * Class self initialize. + * Plugin options. * * @since 1.0.0 - * @deprecated 1.4.2 Replaced by init(). + * @var array $options */ - public static function instance() { - self::init(); - } + public static $options; /** - * Class constructor. + * Multisite Status. * * @since 1.0.0 - * @deprecated 1.4.2 Replaced by init(). + * @var bool $multisite */ - public function __construct() { - self::init(); - } + public static $multisite; /** * Plugin initialization. * * @since 1.4.2 + * + * @return void */ public static function init() { // Skip on autosave or AJAX. @@ -80,41 +93,19 @@ public static function init() { self::update_options(); // Add Filter to statify hook if enabled. - if ( 0 !== self::$_options['referer']['active'] || 0 !== self::$_options['target']['active'] || 0 !== self::$_options['ip']['active'] ) { + if ( 0 !== self::$options['referer']['active'] || 0 !== self::$options['target']['active'] || 0 !== self::$options['ip']['active'] ) { add_filter( 'statify__skip_tracking', array( 'StatifyBlacklist', 'apply_blacklist_filter' ) ); } // Admin only filters. if ( is_admin() ) { - // Load Textdomain (only needed for backend. - load_plugin_textdomain( 'statifyblacklist', false, STATIFYBLACKLIST_DIR . '/lang/' ); - - // Add actions. - add_action( 'wpmu_new_blog', array( 'StatifyBlacklist_System', 'install_site' ) ); - add_action( 'delete_blog', array( 'StatifyBlacklist_System', 'uninstall_site' ) ); - add_filter( 'plugin_row_meta', array( 'StatifyBlacklist_Admin', 'plugin_meta_link' ), 10, 2 ); - - if ( self::$multisite ) { - add_action( 'network_admin_menu', array( 'StatifyBlacklist_Admin', 'add_menu_page' ) ); - add_filter( - 'network_admin_plugin_action_links', array( - 'StatifyBlacklist_Admin', - 'plugin_actions_links', - ), - 10, - 2 - ); - } else { - add_action( 'admin_menu', array( 'StatifyBlacklist_Admin', 'add_menu_page' ) ); - add_filter( 'plugin_action_links', array( 'StatifyBlacklist_Admin', 'plugin_actions_links' ), 10, 2 ); - } + StatifyBlacklist_Admin::init(); } // CronJob to clean up database. - if ( defined( 'DOING_CRON' ) && DOING_CRON ) { - if ( 1 === self::$_options['referer']['cron'] || 1 === self::$_options['target']['cron'] ) { - add_action( 'statify_cleanup', array( 'StatifyBlacklist_Admin', 'cleanup_database' ) ); - } + if ( defined( 'DOING_CRON' ) && DOING_CRON && + ( 1 === self::$options['referer']['cron'] || 1 === self::$options['target']['cron'] ) ) { + add_action( 'statify_cleanup', array( 'StatifyBlacklist_Admin', 'cleanup_database' ) ); } } @@ -125,6 +116,8 @@ public static function init() { * @since 1.2.1 update_options($options = null) Parameter with default value introduced. * * @param array $options Optional. New options to save. + * + * @return void */ public static function update_options( $options = null ) { if ( self::$multisite ) { @@ -132,7 +125,7 @@ public static function update_options( $options = null ) { } else { $o = get_option( 'statify-blacklist' ); } - self::$_options = wp_parse_args( $o, self::default_options() ); + self::$options = wp_parse_args( $o, self::default_options() ); } /** @@ -173,52 +166,72 @@ protected static function default_options() { */ public static function apply_blacklist_filter() { // Referer blacklist. - if ( isset( self::$_options['referer']['active'] ) && 0 !== self::$_options['referer']['active'] ) { - // Regular Expression filtering since 1.3.0. - if ( isset( self::$_options['referer']['regexp'] ) && self::$_options['referer']['regexp'] > 0 ) { - // Get full referer string. - $referer = wp_get_raw_referer(); - if ( ! $referer ) { - $referer = ''; - } - // Merge given regular expressions into one. - $regexp = '/' . implode( '|', array_keys( self::$_options['referer']['blacklist'] ) ) . '/'; - if ( 2 === self::$_options['referer']['regexp'] ) { - $regexp .= 'i'; - } + if ( isset( self::$options['referer']['active'] ) && 0 !== self::$options['referer']['active'] ) { + // Determine filter mode. + $mode = isset( self::$options['referer']['regexp'] ) ? intval( self::$options['referer']['regexp'] ) : 0; + + // Get full referer string. + $referer = wp_get_raw_referer(); + if ( ! $referer ) { + $referer = ''; + } - // Check blacklist (no return to continue filtering #12). - if ( 1 === preg_match( $regexp, $referer ) ) { - return true; - } - } else { - // Extract relevant domain parts. - $referer = wp_parse_url( wp_get_raw_referer() ); - $referer = strtolower( ( isset( $referer['host'] ) ? $referer['host'] : '' ) ); + switch ( $mode ) { - // Get blacklist. - $blacklist = self::$_options['referer']['blacklist']; + // Regular Expression filtering since 1.3.0. + case self::MODE_REGEX: + case self::MODE_REGEX_CI: + // Merge given regular expressions into one. + $regexp = self::regex( + array_keys( self::$options['referer']['blacklist'] ), + self::MODE_REGEX_CI === self::$options['referer']['regexp'] + ); - // Check blacklist. - if ( isset( $blacklist[ $referer ] ) ) { - return true; - } + // Check blacklist (no return to continue filtering #12). + if ( 1 === preg_match( $regexp, $referer ) ) { + return true; + } + break; + + // Keyword filter since 1.5.0 (#15). + case self::MODE_KEYWORD: + // Get blacklist. + $blacklist = self::$options['referer']['blacklist']; + + foreach ( array_keys( $blacklist ) as $keyword ) { + if ( false !== strpos( strtolower( $referer ), strtolower( $keyword ) ) ) { + return true; + } + } + break; + + // Standard domain filter. + default: + // Extract relevant domain parts. + $referer = wp_parse_url( $referer ); + $referer = strtolower( ( isset( $referer['host'] ) ? $referer['host'] : '' ) ); + + // Get blacklist. + $blacklist = self::$options['referer']['blacklist']; + + // Check blacklist. + if ( isset( $blacklist[ $referer ] ) ) { + return true; + } } } // Target blacklist (since 1.4.0). - if ( isset( self::$_options['target']['active'] ) && 0 !== self::$_options['target']['active'] ) { + if ( isset( self::$options['target']['active'] ) && 0 !== self::$options['target']['active'] ) { // Regular Expression filtering since 1.3.0. - if ( isset( self::$_options['target']['regexp'] ) && 0 < self::$_options['target']['regexp'] ) { + if ( isset( self::$options['target']['regexp'] ) && 0 < self::$options['target']['regexp'] ) { // Get full referer string. - // @codingStandardsIgnoreStart The globals are checked. - $target = ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/' ); - // @codingStandardsIgnoreEnd + $target = ( isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL ) : '/' ); // Merge given regular expressions into one. - $regexp = '/' . implode( '|', array_keys( self::$_options['target']['blacklist'] ) ) . '/'; - if ( 2 === self::$_options['target']['regexp'] ) { - $regexp .= 'i'; - } + $regexp = self::regex( + array_keys( self::$options['target']['blacklist'] ), + self::MODE_REGEX_CI === self::$options['target']['regexp'] + ); // Check blacklist (no return to continue filtering #12). if ( 1 === preg_match( $regexp, $target ) ) { @@ -226,11 +239,9 @@ public static function apply_blacklist_filter() { } } else { // Extract target page. - // @codingStandardsIgnoreStart The globals are checked. - $target = ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/' ); - // @codingStandardsIgnoreEnd + $target = ( isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL ) : '/' ); // Get blacklist. - $blacklist = self::$_options['target']['blacklist']; + $blacklist = self::$options['target']['blacklist']; // Check blacklist. if ( isset( $blacklist[ $target ] ) ) { return true; @@ -239,10 +250,10 @@ public static function apply_blacklist_filter() { } // IP blacklist (since 1.4.0). - if ( isset( self::$_options['ip']['active'] ) && 0 !== self::$_options['ip']['active'] ) { + if ( isset( self::$options['ip']['active'] ) && 0 !== self::$options['ip']['active'] ) { $ip = self::get_ip(); if ( false !== ( $ip ) ) { - foreach ( self::$_options['ip']['blacklist'] as $net ) { + foreach ( self::$options['ip']['blacklist'] as $net ) { if ( self::cidr_match( $ip, $net ) ) { return true; } @@ -254,6 +265,37 @@ public static function apply_blacklist_filter() { return null; } + /** + * Preprocess regular expression provided by the user, i.e. add delimiters and optional ci flag. + * + * @param string|array $expression Original expression string or array of expressions. + * @param string|array $case_insensitive Make expression match case-insensitive. + * + * @return string Preprocessed expression ready for preg_match(). + */ + protected static function regex( $expression, $case_insensitive ) { + $res = '/'; + if ( is_string( $expression ) ) { + $res .= str_replace( '/', '\/', $expression ); + } elseif ( is_array( $expression ) ) { + $res .= implode( + '|', + array_map( + function ( $e ) { + return str_replace( '/', '\/', $e ); + }, + $expression + ) + ); + } + $res .= '/'; + if ( $case_insensitive ) { + $res .= 'i'; + } + + return $res; + } + /** * Helper method to determine the client's IP address. * @@ -277,15 +319,14 @@ private static function get_ip() { 'REMOTE_ADDR', ) as $k ) { - // @codingStandardsIgnoreStart The globals are checked. if ( isset( $_SERVER[ $k ] ) ) { + // phpcs:ignore foreach ( explode( ',', $_SERVER[ $k ] ) as $ip ) { if ( false !== filter_var( $ip, FILTER_VALIDATE_IP ) ) { return $ip; } } } - // @codingStandardsIgnoreEnd } return false; @@ -361,6 +402,6 @@ private static function cidr_match( $ip, $net ) { } return ( 0 === substr_compare( sprintf( '%032b', ip2long( $ip ) ), sprintf( '%032b', ip2long( $base ) ), 0, $mask ) ); - } // End if(). + } } } diff --git a/package.json b/package.json index 9176cf4..b49920e 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,7 @@ { "name": "statify-blacklist", - "version": "1.4.4", + "version": "1.5.0", "description": "A blacklist extension for the famous Statify WordPress plugin", "author": "Stefan Kalscheuer", - "license": "GPL-2.0+", - "devDependencies": { - "gulp": "^3.9.1", - "gulp-clean": "^0.3.2", - "gulp-copy": "^1.0.1", - "gulp-zip": "^4.0.0", - "gulp-composer": "^0.4.4", - "gulp-phpunit": "^0.24.1", - "gulp-phpcs": "^2.1.0", - "child_process": "^1.0.2" - } + "license": "GPL-2.0+" } diff --git a/phpcs.xml b/phpcs.xml index a40b6c5..3e2fb67 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -2,23 +2,20 @@ Derived from WordPress Coding Standard - + - inc statify-blacklist.php + inc + views - + - - - - - - + + diff --git a/phpunit.xml b/phpunit.xml index 4ea43f9..f6cdc73 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,6 +12,6 @@ - + diff --git a/statify-blacklist.php b/statify-blacklist.php index 6d84741..6217898 100644 --- a/statify-blacklist.php +++ b/statify-blacklist.php @@ -10,11 +10,10 @@ * Plugin Name: Statify Blacklist * Plugin URI: https://wordpress.org/plugins/statify-blacklist/ * Description: Extension for the Statify plugin to add a customizable blacklists. - * Version: 1.4.4 + * Version: 1.5.0 * Author: Stefan Kalscheuer (@stklcode) * Author URI: https://www.stklcode.de * Text Domain: statify-blacklist - * Domain Path: /lang * License: GPLv2 or later * * Statify Blacklist is free software: you can redistribute it and/or modify @@ -28,34 +27,42 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Statify Blacklist. If not, see http://www.gnu.org/licenses/gpl-2.0.html. + * along with Statify Blacklist. If not, see https://www.gnu.org/licenses/gpl-2.0.html. */ -// Quit. -defined( 'ABSPATH' ) || exit; +// Quit if accessed directly. +if ( ! defined( 'ABSPATH' ) ) { + exit; +} // Constants. define( 'STATIFYBLACKLIST_FILE', __FILE__ ); define( 'STATIFYBLACKLIST_DIR', dirname( __FILE__ ) ); define( 'STATIFYBLACKLIST_BASE', plugin_basename( __FILE__ ) ); -// System Hooks. -add_action( 'plugins_loaded', array( 'StatifyBlacklist', 'init' ) ); +// Check for compatibility. +if ( statify_blacklist_compatibility_check() ) { + // System Hooks. + add_action( 'plugins_loaded', array( 'StatifyBlacklist', 'init' ) ); -register_activation_hook( STATIFYBLACKLIST_FILE, array( 'StatifyBlacklist_System', 'install' ) ); + register_activation_hook( STATIFYBLACKLIST_FILE, array( 'StatifyBlacklist_System', 'install' ) ); -register_uninstall_hook( STATIFYBLACKLIST_FILE, array( 'StatifyBlacklist_System', 'uninstall' ) ); + register_uninstall_hook( STATIFYBLACKLIST_FILE, array( 'StatifyBlacklist_System', 'uninstall' ) ); -// Upgrade hook. -register_activation_hook( STATIFYBLACKLIST_FILE, array( 'StatifyBlacklist_System', 'upgrade' ) ); + // Upgrade hook. + register_activation_hook( STATIFYBLACKLIST_FILE, array( 'StatifyBlacklist_System', 'upgrade' ) ); -// Autoload. -spl_autoload_register( 'statify_blacklist_autoload' ); + // Autoload. + spl_autoload_register( 'statify_blacklist_autoload' ); +} else { + // Disable plugin, if active. + add_action( 'admin_init', 'statify_blacklist_disable' ); +} /** * Autoloader for StatifyBlacklist classes. * - * @param string $class Name of the class to load. + * @param string $class Name of the class to load. * * @since 1.0.0 */ @@ -74,3 +81,59 @@ function statify_blacklist_autoload( $class ) { ); } } + +/** + * Check for compatibility with PHP and WP version. + * + * @since 1.5.0 + * + * @return boolean Whether minimum WP and PHP versions are met. + */ +function statify_blacklist_compatibility_check() { + return version_compare( $GLOBALS['wp_version'], '4.7', '>=' ) && + version_compare( phpversion(), '5.5', '>=' ); +} + +/** + * Disable plugin if active and incompatible. + * + * @since 1.5.0 + * + * @return void + */ +function statify_blacklist_disable() { + if ( is_plugin_active( STATIFYBLACKLIST_BASE ) ) { + deactivate_plugins( STATIFYBLACKLIST_BASE ); + add_action( 'admin_notices', 'statify_blacklist_disabled_notice' ); + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['activate'] ) ) { + unset( $_GET['activate'] ); + } + // phpcs:enable + } +} + +/** + * Admin notification for unmet requirements. + * + * @since 1.5.0 + * + * @return void + */ +function statify_blacklist_disabled_notice() { + echo '

'; + printf( + /* translators: minimum version numbers for WordPress and PHP inserted at placeholders */ + esc_html__( 'Statify Blacklist requires at least WordPress %1$s and PHP %2$s.', 'statify-blacklist' ), + '4.7', + '5.5' + ); + echo '
'; + printf( + /* translators: current version numbers for WordPress and PHP inserted at placeholders */ + esc_html__( 'Your site is running WordPress %1$s on PHP %2$s, thus the plugin has been disabled.', 'statify-blacklist' ), + esc_html( $GLOBALS['wp_version'] ), + esc_html( phpversion() ) + ); + echo '

'; +} diff --git a/test/statifyblacklist-test.php b/test/statifyblacklist-test.php index 2e71a65..a7a18e4 100644 --- a/test/statifyblacklist-test.php +++ b/test/statifyblacklist-test.php @@ -20,17 +20,17 @@ /** * The StatifyBlacklist base class. */ -require_once( 'inc/class-statifyblacklist.php' ); +require_once __DIR__ . '/../inc/class-statifyblacklist.php'; /** * The StatifyBlacklist system class. */ -require_once( 'inc/class-statifyblacklist-system.php' ); +require_once __DIR__ . '/../inc/class-statifyblacklist-system.php'; /** * The StatifyBlacklist admin class. */ -require_once( 'inc/class-statifyblacklist-admin.php' ); +require_once __DIR__ . '/../inc/class-statifyblacklist-admin.php'; /** * Class StatifyBlacklistTest. @@ -43,10 +43,12 @@ class StatifyBlacklist_Test extends PHPUnit\Framework\TestCase { /** * Test simple referer filter. + * + * @return void */ public function test_referer_filter() { // Prepare Options: 2 blacklisted domains, disabled. - StatifyBlacklist::$_options = array( + StatifyBlacklist::$options = array( 'referer' => array( 'active' => 0, 'cron' => 0, @@ -86,7 +88,7 @@ public function test_referer_filter() { $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); // Activate filter and run tests again. - StatifyBlacklist::$_options['referer']['active'] = 1; + StatifyBlacklist::$options['referer']['active'] = 1; unset( $_SERVER['HTTP_REFERER'] ); $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); @@ -103,10 +105,12 @@ public function test_referer_filter() { /** * Test referer filter using regular expressions. + * + * @return void */ - public function testRefererRegexFilter() { + public function test_referer_regex_filter() { // Prepare Options: 2 regular expressions. - StatifyBlacklist::$_options = array( + StatifyBlacklist::$options = array( 'referer' => array( 'active' => 1, 'cron' => 0, @@ -147,19 +151,74 @@ public function testRefererRegexFilter() { // Matching both. $_SERVER['HTTP_REFERER'] = 'http://example.net/test/me'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); - // Mathinc with wrong case. + // Matching 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; + StatifyBlacklist::$options['referer']['regexp'] = 2; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + } + + /** + * Test referer filter using keywords. + * + * @return void + */ + public function test_referer_keyword_filter() { + // Prepare Options: 2 regular expressions. + StatifyBlacklist::$options = array( + 'referer' => array( + 'active' => 1, + 'cron' => 0, + 'regexp' => StatifyBlacklist::MODE_KEYWORD, + 'blacklist' => array( + 'example' => 0, + 'test' => 1, + ), + ), + 'target' => array( + 'active' => 0, + 'cron' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, + 'blacklist' => array(), + ), + 'ip' => array( + 'active' => 0, + 'blacklist' => array(), + ), + 'version' => StatifyBlacklist::VERSION_MAIN, + ); + + // 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() ); + // Matching with wrong case. + $_SERVER['HTTP_REFERER'] = 'http://eXaMpLe.NeT/tEsT/mE'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); } /** * Test the upgrade methodology for configuration options. + * + * @return void */ - public function testUpgrade() { + public function test_upgrade() { // Create configuration of version 1.3. $options13 = array( 'active_referer' => 1, @@ -179,138 +238,141 @@ public function testUpgrade() { StatifyBlacklist_System::upgrade(); // Retrieve updated options. - $optionsUpdated = get_option( 'statify-blacklist' ); + $options_updated = get_option( 'statify-blacklist' ); // Verify size against default options (no junk left). - $this->assertEquals( 4, count( $optionsUpdated ) ); - $this->assertEquals( 4, count( $optionsUpdated['referer'] ) ); - $this->assertEquals( 4, count( $optionsUpdated['target'] ) ); - $this->assertEquals( 2, count( $optionsUpdated['ip'] ) ); + $this->assertEquals( 4, count( $options_updated ) ); + $this->assertEquals( 4, count( $options_updated['referer'] ) ); + $this->assertEquals( 4, count( $options_updated['target'] ) ); + $this->assertEquals( 2, count( $options_updated['ip'] ) ); // Verify that original attributes are unchanged. - $this->assertEquals( $options13['active_referer'], $optionsUpdated['referer']['active'] ); - $this->assertEquals( $options13['cron_referer'], $optionsUpdated['referer']['cron'] ); - $this->assertEquals( $options13['referer'], $optionsUpdated['referer']['blacklist'] ); - $this->assertEquals( $options13['referer_regexp'], $optionsUpdated['referer']['regexp'] ); + $this->assertEquals( $options13['active_referer'], $options_updated['referer']['active'] ); + $this->assertEquals( $options13['cron_referer'], $options_updated['referer']['cron'] ); + $this->assertEquals( $options13['referer'], $options_updated['referer']['blacklist'] ); + $this->assertEquals( $options13['referer_regexp'], $options_updated['referer']['regexp'] ); // Verify that new attributes are present in config and filled with default values (disabled, empty). - $this->assertEquals( 0, $optionsUpdated['target']['active'] ); - $this->assertEquals( 0, $optionsUpdated['target']['cron'] ); - $this->assertEquals( 0, $optionsUpdated['target']['regexp'] ); - $this->assertEquals( array(), $optionsUpdated['target']['blacklist'] ); - $this->assertEquals( 0, $optionsUpdated['ip']['active'] ); - $this->assertEquals( array(), $optionsUpdated['ip']['blacklist'] ); + $this->assertEquals( 0, $options_updated['target']['active'] ); + $this->assertEquals( 0, $options_updated['target']['cron'] ); + $this->assertEquals( 0, $options_updated['target']['regexp'] ); + $this->assertEquals( array(), $options_updated['target']['blacklist'] ); + $this->assertEquals( 0, $options_updated['ip']['active'] ); + $this->assertEquals( array(), $options_updated['ip']['blacklist'] ); // Verify that version number has changed to current release. - $this->assertEquals( StatifyBlacklist::VERSION_MAIN, $optionsUpdated['version'] ); + $this->assertEquals( StatifyBlacklist::VERSION_MAIN, $options_updated['version'] ); } /** - * Test CIDR address matching for IP filter (#7) + * Test CIDR address matching for IP filter (#7). + * + * @return void */ - public function testCidrMatch() { + public function test_cidr_match() { // IPv4 tests. - $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '127.0.0.1', '127.0.0.1' ) ) ); - $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '127.0.0.1', '127.0.0.1/32' ) ) ); + $this->assertTrue( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '127.0.0.1', '127.0.0.1' ) ) ); + $this->assertTrue( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '127.0.0.1', '127.0.0.1/32' ) ) ); $this->assertFalse( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '127.0.0.1', - '127.0.0.1/33', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '127.0.0.1', '127.0.0.1/33' ) ) ); $this->assertFalse( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '127.0.0.1', - '127.0.0.1/-1', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '127.0.0.1', '127.0.0.1/-1' ) ) ); $this->assertTrue( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '192.0.2.123', - '192.0.2.0/24', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '192.0.2.123', '192.0.2.0/24' ) ) ); $this->assertFalse( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '192.0.3.123', - '192.0.2.0/24', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '192.0.3.123', '192.0.2.0/24' ) ) ); $this->assertTrue( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '192.0.2.123', - '192.0.2.120/29', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '192.0.2.123', '192.0.2.120/29' ) ) ); $this->assertFalse( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '192.0.2.128', - '192.0.2.120/29', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '192.0.2.128', '192.0.2.120/29' ) ) ); - $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '10.11.12.13', '10.0.0.0/8' ) ) ); + $this->assertTrue( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '10.11.12.13', '10.0.0.0/8' ) ) ); $this->assertFalse( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '10.11.12.345', - '10.0.0.0/8', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '10.11.12.345', '10.0.0.0/8' ) ) ); // IPv6 tests. - $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1' ) ) ); - $this->assertTrue( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1/128' ) ) ); - $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1/129' ) ) ); - $this->assertFalse( invokeStatic( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1/-1' ) ) ); + $this->assertTrue( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1' ) ) ); + $this->assertTrue( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1/128' ) ) ); + $this->assertFalse( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1/129' ) ) ); + $this->assertFalse( invoke_static( StatifyBlacklist::class, 'cidr_match', array( '::1', '::1/-1' ) ) ); $this->assertTrue( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '2001:db8:a0b:12f0:1:2:3:4', - '2001:db8:a0b:12f0::1/64 ', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '2001:db8:a0b:12f0:1:2:3:4', '2001:db8:a0b:12f0::1/64 ' ) ) ); $this->assertTrue( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '2001:db8:a0b:12f0::123:456', - '2001:db8:a0b:12f0::1/96 ', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '2001:db8:a0b:12f0::123:456', '2001:db8:a0b:12f0::1/96 ' ) ) ); $this->assertFalse( - invokeStatic( - StatifyBlacklist::class, 'cidr_match', array( - '2001:db8:a0b:12f0::1:132:465', - '2001:db8:a0b:12f0::1/96 ', - ) + invoke_static( + StatifyBlacklist::class, + 'cidr_match', + array( '2001:db8:a0b:12f0::1:132:465', '2001:db8:a0b:12f0::1/96 ' ) ) ); } /** - * Test sanitization of IP addresses + * Test sanitization of IP addresses. + * + * @return void */ - public function testSanitizeIPs() { + public function test_sanitize_ips() { // IPv4 tests. $valid = array( '192.0.2.123', '192.0.2.123/32', '192.0.2.0/24', '192.0.2.128/25' ); $invalid = array( '12.34.56.789', '192.0.2.123/33', '192.0.2.123/-1' ); - $result = invokeStatic( StatifyBlacklist_Admin::class, 'sanitizeIPs', array( array_merge( $valid, $invalid ) ) ); + $result = invoke_static( StatifyBlacklist_Admin::class, 'sanitize_ips', array( array_merge( $valid, $invalid ) ) ); $this->assertNotFalse( $result ); - $this->assertInternalType( 'array', $result ); + + /* + * Unfortunately this is necessary as long as we run PHP 5 tests, because "assertInternalType" is deprecated + * as of PHPUnit 8, but "assertIsArray" has been introduces in PHPUnit 7.5 which requires PHP >= 7.1. + */ + if ( method_exists( $this, 'assertIsArray' ) ) { + $this->assertIsArray( $result ); + } else { + $this->assertInternalType( 'array', $result ); + } $this->assertEquals( $valid, $result ); // IPv6 tests. @@ -327,28 +389,34 @@ public function testSanitizeIPs() { '2001:db8:a0b:12f0::/129', '1:2:3:4:5:6:7:8:9', ); - $result = invokeStatic( StatifyBlacklist_Admin::class, 'sanitizeIPs', array( array_merge( $valid, $invalid ) ) ); + $result = invoke_static( StatifyBlacklist_Admin::class, 'sanitize_ips', array( array_merge( $valid, $invalid ) ) ); $this->assertNotFalse( $result ); - $this->assertInternalType( 'array', $result ); + if ( method_exists( $this, 'assertIsArray' ) ) { + $this->assertIsArray( $result ); + } else { + $this->assertInternalType( 'array', $result ); + } $this->assertEquals( $valid, $result ); } /** * Test IP filter (#7). + * + * @return void */ - public function testIPFilter() { + public function test_ip_filter() { // Prepare Options: 2 blacklisted IPs, disabled. - StatifyBlacklist::$_options = array( + StatifyBlacklist::$options = array( 'referer' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array(), ), 'target' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array(), ), 'ip' => array( @@ -368,7 +436,7 @@ public function testIPFilter() { $_SERVER['REMOTE_ADDR'] = '192.0.2.123'; $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); // Activate filter. - StatifyBlacklist::$_options['ip']['active'] = 1; + StatifyBlacklist::$options['ip']['active'] = 1; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); // Try matching v6 address. $_SERVER['REMOTE_ADDR'] = '2001:db8:a0b:12f0::1'; @@ -379,11 +447,11 @@ public function testIPFilter() { $_SERVER['REMOTE_ADDR'] = '2001:db8:a0b:12f0::2'; $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); // Subnet matching. - StatifyBlacklist::$_options['ip']['blacklist'] = array( + StatifyBlacklist::$options['ip']['blacklist'] = array( '192.0.2.0/25', '2001:db8:a0b:12f0::/96', ); - $_SERVER['REMOTE_ADDR'] = '192.0.2.123'; + $_SERVER['REMOTE_ADDR'] = '192.0.2.123'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); @@ -405,20 +473,22 @@ public function testIPFilter() { /** * Test simple target filter. + * + * @return void */ - public function testTargetFilter() { + public function test_target_filter() { // Prepare Options: 2 blacklisted domains, disabled. - StatifyBlacklist::$_options = array( + StatifyBlacklist::$options = array( 'referer' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array(), ), 'target' => array( 'active' => 0, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array( '/excluded/page/' => 0, '/?page_id=3' => 1, @@ -451,7 +521,7 @@ public function testTargetFilter() { $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); // Activate filter and run tests again. - StatifyBlacklist::$_options['target']['active'] = 1; + StatifyBlacklist::$options['target']['active'] = 1; unset( $_SERVER['REQUEST_URI'] ); $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); @@ -483,11 +553,11 @@ public function testTargetFilter() { */ public function test_combined_filters() { // Prepare Options: simple referer + simple target + ip. - StatifyBlacklist::$_options = array( + StatifyBlacklist::$options = array( 'referer' => array( 'active' => 1, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array( 'example.com' => 0, ), @@ -495,15 +565,15 @@ public function test_combined_filters() { 'target' => array( 'active' => 1, 'cron' => 0, - 'regexp' => 0, + 'regexp' => StatifyBlacklist::MODE_NORMAL, 'blacklist' => array( - '/excluded/page/' => 0 + '/excluded/page/' => 0, ), ), 'ip' => array( 'active' => 1, 'blacklist' => array( - '192.0.2.123' + '192.0.2.123', ), ), 'version' => StatifyBlacklist::VERSION_MAIN, @@ -514,8 +584,8 @@ public function test_combined_filters() { // No match. $_SERVER['HTTP_REFERER'] = 'https://example.net'; - $_SERVER['REQUEST_URI'] = '/normal/page/'; - $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; + $_SERVER['REQUEST_URI'] = '/normal/page/'; + $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; unset( $_SERVER['HTTP_X_FORWARDED_FOR'] ); unset( $_SERVER['HTTP_X_REAL_IP'] ); @@ -525,7 +595,7 @@ public function test_combined_filters() { // Matching target. $_SERVER['HTTP_REFERER'] = 'https://example.net'; - $_SERVER['REQUEST_URI'] = '/excluded/page/'; + $_SERVER['REQUEST_URI'] = '/excluded/page/'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); // Matching IP. @@ -535,16 +605,21 @@ public function test_combined_filters() { $_SERVER['REMOTE_ADDR'] = '192.0.2.234'; // Same for RegExp filters. - StatifyBlacklist::$_options['referer']['regexp'] = 1; - StatifyBlacklist::$_options['referer']['blacklist'] = array( 'example\.com' => 0 ); - StatifyBlacklist::$_options['target']['regexp'] = 1; - StatifyBlacklist::$_options['target']['blacklist'] = array( '\/excluded\/.*' => 0 ); + StatifyBlacklist::$options['referer']['regexp'] = StatifyBlacklist::MODE_REGEX; + StatifyBlacklist::$options['referer']['blacklist'] = array( 'example\.com' => 0 ); + StatifyBlacklist::$options['target']['regexp'] = StatifyBlacklist::MODE_REGEX; + StatifyBlacklist::$options['target']['blacklist'] = array( '/excluded/.*' => 0 ); $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); $_SERVER['HTTP_REFERER'] = 'https://example.com'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); + // Check case-insensitive match. + $_SERVER['HTTP_REFERER'] = 'https://eXaMpLe.com'; + $this->assertNull( StatifyBlacklist::apply_blacklist_filter() ); + StatifyBlacklist::$options['referer']['regexp'] = StatifyBlacklist::MODE_REGEX_CI; + $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); $_SERVER['HTTP_REFERER'] = 'https://example.net'; - $_SERVER['REQUEST_URI'] = '/excluded/page/'; + $_SERVER['REQUEST_URI'] = '/excluded/page/'; $this->assertTrue( StatifyBlacklist::apply_blacklist_filter() ); $_SERVER['REQUEST_URI'] = '/normal/page/'; $_SERVER['REMOTE_ADDR'] = '192.0.2.123'; @@ -556,9 +631,9 @@ public function test_combined_filters() { /** @ignore */ -function invokeStatic( $class, $methodName, $parameters = array() ) { +function invoke_static( $class, $method_name, $parameters = array() ) { $reflection = new \ReflectionClass( $class ); - $method = $reflection->getMethod( $methodName ); + $method = $reflection->getMethod( $method_name ); $method->setAccessible( true ); return $method->invokeArgs( null, $parameters ); diff --git a/views/settings-page.php b/views/settings-page.php index 2213995..4b188e4 100755 --- a/views/settings-page.php +++ b/views/settings-page.php @@ -9,6 +9,8 @@ * @since 1.0.0 */ +// phpcs:disable WordPress.WhiteSpace.PrecisionAlignment.Found + // Quit. defined( 'ABSPATH' ) || exit; @@ -19,7 +21,7 @@ // Check user capabilities. if ( ! current_user_can( 'manage_options' ) ) { - die( __( 'Are you sure you want to do this?' ) ); + die( esc_html__( 'Are you sure you want to do this?' ) ); } if ( ! empty( $_POST['cleanUp'] ) ) { @@ -27,24 +29,63 @@ StatifyBlacklist_Admin::cleanup_database(); } else { // Extract referer array. - if ( empty( trim( $_POST['statifyblacklist']['referer']['blacklist'] ) ) ) { + if ( isset( $_POST['statifyblacklist']['referer']['blacklist'] ) ) { + $referer_str = sanitize_textarea_field( wp_unslash( $_POST['statifyblacklist']['referer']['blacklist'] ) ); + } + if ( empty( trim( $referer_str ) ) ) { $referer = array(); } else { - $referer = explode( "\r\n", $_POST['statifyblacklist']['referer']['blacklist'] ); + $referer = array_filter( + array_map( + function ( $a ) { + return trim( $a ); + }, + explode( "\r\n", $referer_str ) + ), + function ( $a ) { + return ! empty( $a ); + } + ); } // Extract target array. - if ( empty( trim( $_POST['statifyblacklist']['target']['blacklist'] ) ) ) { + if ( isset( $_POST['statifyblacklist']['target']['blacklist'] ) ) { + $target_str = sanitize_textarea_field( wp_unslash( $_POST['statifyblacklist']['target']['blacklist'] ) ); + } + if ( empty( trim( $target_str ) ) ) { $target = array(); } else { - $target = explode( "\r\n", str_replace( '\\\\', '\\', $_POST['statifyblacklist']['target']['blacklist'] ) ); + $target = array_filter( + array_map( + function ( $a ) { + return trim( $a ); + }, + explode( "\r\n", str_replace( '\\\\', '\\', $target_str ) ) + ), + function ( $a ) { + return ! empty( $a ); + } + ); } // Extract IP array. - if ( empty( trim( $_POST['statifyblacklist']['ip']['blacklist'] ) ) ) { + if ( isset( $_POST['statifyblacklist']['ip']['blacklist'] ) ) { + $ip_str = sanitize_textarea_field( wp_unslash( $_POST['statifyblacklist']['ip']['blacklist'] ) ); + } + if ( empty( trim( $ip_str ) ) ) { $ip = array(); } else { - $ip = explode( "\r\n", $_POST['statifyblacklist']['ip']['blacklist'] ); + $ip = array_filter( + array_map( + function ( $a ) { + return trim( $a ); + }, + explode( "\r\n", $ip_str ) + ), + function ( $a ) { + return ! empty( $a ); + } + ); } // Update options (data will be sanitized). @@ -79,21 +120,33 @@ // Generate messages. if ( false !== $statifyblacklist_update_result ) { - if ( array_key_exists( 'referer', $statifyblacklist_update_result ) ) { - $statifyblacklist_post_warning = __( 'Some URLs are invalid and have been sanitized.', 'statify-blacklist' ); - } elseif ( array_key_exists( 'ip', $statifyblacklist_update_result ) ) { + $statifyblacklist_post_warning = array(); + if ( ! empty( $statifyblacklist_update_result['referer']['diff'] ) ) { + $statifyblacklist_post_warning[] = __( 'Some URLs are invalid and have been sanitized.', 'statify-blacklist' ); + } + if ( ! empty( $statifyblacklist_update_result['referer']['invalid'] ) ) { + $statifyblacklist_post_warning[] = __( 'Some regular expressions are invalid:', 'statify-blacklist' ) . '
' . implode( '
', $statifyblacklist_update_result['referer']['invalid'] ); + } + if ( ! empty( $statifyblacklist_update_result['ip']['diff'] ) ) { // translators: List of invalid IP addresses (comma separated). - $statifyblacklist_post_warning = sprintf( __( 'Some IPs are invalid : %s', 'statify-blacklist' ), implode( ', ', $statifyblacklist_update_result['ip'] ) ); + $statifyblacklist_post_warning[] = sprintf( __( 'Some IPs are invalid: %s', 'statify-blacklist' ), implode( ', ', $statifyblacklist_update_result['ip']['diff'] ) ); } } else { $statifyblacklist_post_success = __( 'Settings updated successfully.', 'statify-blacklist' ); } - } // End if(). -} // End if(). + } +} + +/* + * Disable some code style rules that are impractical for textarea content: + * + * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentBeforeOpen + * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentAfterEnd + */ ?>
-

+

'; @@ -101,11 +154,12 @@ print '

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

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

'; + foreach ( $statifyblacklist_post_warning as $w ) { + print '

' . + wp_kses( $w, array( 'br' => array() ) ) . + '

'; + } + print '

' . esc_html__( 'Settings have not been saved yet.', 'statify-blacklist' ) . '

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

' . @@ -114,179 +168,223 @@ } ?>

-
-

-
    -
  • + + +

    + + + + + + + + + + + + + + + + + + + + +
    - -
  • +
  • + > +

    + +

    +
    - -
  • - -
  • -
  • - -
  • - - +
    + > +

    +
    + + + + +

    + - +
    + - +
    + - +

    +
    + + + +

    + +

    +
    -
    -

    -
      -
    • +

      + + + + + + + + + + + + + + + + + + + + +
      - -
    • +
    • + > +

      + +

      +
      - -
    • +
    • + > +

      + +

      +
      - -
    • +
    • + + +

      + - +
      + - +

      +
      - - - + + + +

      + ( /, /test/page/, /?page_id=123) +

      +
      -
      -

      -
        -
      • +

        + + + + + + + + + + + + +
        - -
      • - - ( - ) - -
      • -
      • - -
      • - - +
        + > +

        + +
        + +

        +
        + : + + - +

        + + 127.0.0.1, 192.168.123.0/24, 2001:db8:a0b:12f0::1/64 +

        +

        - -


        - -
        - - + +
        + +
        +

        + + +