diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4ed72b0..0f8436c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -32,7 +32,7 @@ jobs:
           args: >
             -Dsonar.organization=stklcode-github
             -Dsonar.projectKey=stklcode:statify-blacklist
-            -Dsonar.sources=inc,views,statify-blacklist.php
+            -Dsonar.sources=inc,statify-blacklist.php
             -Dsonar.tests=test
             -Dsonar.php.tests.reportPath=tests-junit.xml
             -Dsonar.php.coverage.reportPaths=tests-clover.xml
diff --git a/inc/class-statifyblacklist-admin.php b/inc/class-statifyblacklist-admin.php
index 7151940..023321b 100644
--- a/inc/class-statifyblacklist-admin.php
+++ b/inc/class-statifyblacklist-admin.php
@@ -16,17 +16,15 @@ if ( ! defined( 'ABSPATH' ) ) {
 
 /**
  * Statify Filter admin configuration.
- *
- * @since   1.0.0
  */
 class StatifyBlacklist_Admin extends StatifyBlacklist {
 
 	/**
 	 * Initialize admin-only components of the plugin.
 	 *
-	 * @since 1.5.0
-	 *
 	 * @return void
+	 *
+	 * @since 1.5.0
 	 */
 	public static function init() {
 		// Add actions.
@@ -46,91 +44,12 @@ class StatifyBlacklist_Admin extends StatifyBlacklist {
 				2
 			);
 		} else {
+			add_action( 'admin_init', array( 'StatifyBlacklist_Settings', 'register_settings' ) );
 			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.
-	 */
-	public static function update_options( $options = null ) {
-		if ( isset( $options ) && current_user_can( 'manage_options' ) ) {
-
-			// 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 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::sanitize_ips( $given_ip );
-
-			// Abort on errors.
-			$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.
-			if ( self::$multisite ) {
-				update_site_option( 'statify-blacklist', $options );
-			} else {
-				update_option( 'statify-blacklist', $options );
-			}
-		}
-
-		// Refresh options.
-		parent::update_options( $options );
-
-		return false;
-	}
-
 	/**
 	 * Add configuration page to admin menu.
 	 *
@@ -139,50 +58,33 @@ class StatifyBlacklist_Admin extends StatifyBlacklist {
 	public static function add_menu_page() {
 		$title = __( 'Statify Filter', 'statify-blacklist' );
 		if ( self::$multisite ) {
-			add_submenu_page(
-				'settings.php',
+			add_options_page(
 				$title,
 				$title,
 				'manage_network_plugins',
-				'statify-blacklist-settings',
-				array(
-					'StatifyBlacklist_Admin',
-					'settings_page',
-				)
+				'statify-blacklist',
+				array( 'StatifyBlacklist_Settings', 'create_settings_page' )
 			);
 		} else {
-			add_submenu_page(
-				'options-general.php',
+			add_options_page(
 				$title,
 				$title,
 				'manage_options',
 				'statify-blacklist',
-				array(
-					'StatifyBlacklist_Admin',
-					'settings_page',
-				)
+				array( 'StatifyBlacklist_Settings', 'create_settings_page' )
 			);
 		}
 	}
 
-	/**
-	 * Include the Statify-Blacklist settings page.
-	 *
-	 * @since 1.0.0
-	 */
-	public static function settings_page() {
-		include STATIFYBLACKLIST_DIR . '/views/settings-page.php';
-	}
-
 	/**
 	 * Add plugin meta links
 	 *
-	 * @since 1.0.0
-	 *
 	 * @param array  $links Registered links.
 	 * @param string $file  The filename.
 	 *
 	 * @return array  Merged links.
+	 *
+	 * @since 1.0.0
 	 */
 	public static function plugin_meta_link( $links, $file ) {
 		if ( STATIFYBLACKLIST_BASE === $file ) {
@@ -195,12 +97,12 @@ class StatifyBlacklist_Admin extends StatifyBlacklist {
 	/**
 	 * Add plugin action links.
 	 *
-	 * @since 1.0.0
-	 *
 	 * @param array  $links Registered links.
 	 * @param string $file  The filename.
 	 *
 	 * @return array  Merged links.
+	 *
+	 * @since 1.0.0
 	 */
 	public static function plugin_actions_links( $links, $file ) {
 		$base = self::$multisite ? network_admin_url( 'settings.php' ) : admin_url( 'options-general.php' );
@@ -298,11 +200,11 @@ class StatifyBlacklist_Admin extends StatifyBlacklist {
 	/**
 	 * Sanitize URLs and remove empty results.
 	 *
-	 * @since 1.1.1
-	 *
 	 * @param array $urls given array of URLs.
 	 *
 	 * @return array  sanitized array.
+	 *
+	 * @since 1.1.1
 	 */
 	private static function sanitize_urls( $urls ) {
 		return array_flip(
@@ -316,56 +218,4 @@ class StatifyBlacklist_Admin extends StatifyBlacklist {
 			)
 		);
 	}
-
-	/**
-	 * Sanitize IP addresses with optional CIDR notation and remove empty results.
-	 *
-	 * @since 1.4.0
-	 *
-	 * @param array $ips given array of URLs.
-	 *
-	 * @return array  sanitized array.
-	 */
-	private static function sanitize_ips( $ips ) {
-		return array_filter(
-			array_map( 'strtolower', $ips ),
-			function ( $ip ) {
-				return preg_match(
-					'/^((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])(\/([0-9]|[1-2][0-9]|3[0-2]))?$/',
-					$ip
-				) ||
-				preg_match(
-					'/^(([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,7}:|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}' .
-					'|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}' .
-					'|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}' .
-					'|[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|:((:[0-9a-f]{1,4}){1,7}|:)' .
-					'|fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]' .
-					'|1?[0-9])?[0-9])|([0-9a-f]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))' .
-					'(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/i',
-					$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-settings.php b/inc/class-statifyblacklist-settings.php
new file mode 100644
index 0000000..2d5ff26
--- /dev/null
+++ b/inc/class-statifyblacklist-settings.php
@@ -0,0 +1,727 @@
+<?php
+/**
+ * Statify Filter: StatifyBlacklist_Settings class
+ *
+ * This file contains the plugin's settings capabilities.
+ *
+ * @package Statify_Blacklist
+ * @since 1.7
+ */
+
+// Quit if accessed directly..
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Statify Filter settings handling.
+ */
+class StatifyBlacklist_Settings extends StatifyBlacklist {
+	/**
+	 * Registers all options using the WP Settings API.
+	 *
+	 * @return void
+	 */
+	public static function register_settings() {
+		register_setting(
+			'statify-blacklist',
+			'statify-blacklist',
+			array(
+				'sanitize_callback' =>
+					array( __CLASS__, 'sanitize_options' ),
+			)
+		);
+
+		// Referer filter.
+		add_settings_section(
+			'statifyblacklist-referer',
+			__( 'Referer filter', 'statify-blacklist' ),
+			null,
+			'statify-blacklist'
+		);
+		add_settings_field(
+			'statifyblacklist-referer-active',
+			__( 'Activate live filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_referer_active' ),
+			'statify-blacklist',
+			'statifyblacklist-referer'
+		);
+		add_settings_field(
+			'statifyblacklist-referer-cron',
+			__( 'CronJob execution', 'statify-blacklist' ),
+			array( __CLASS__, 'option_referer_cron' ),
+			'statify-blacklist',
+			'statifyblacklist-referer'
+		);
+		add_settings_field(
+			'statifyblacklist-referer-regexp',
+			__( 'Matching method', 'statify-blacklist' ),
+			array( __CLASS__, 'option_referer_regexp' ),
+			'statify-blacklist',
+			'statifyblacklist-referer',
+			array( 'label_for' => 'statifyblacklist-referer-regexp' )
+		);
+		add_settings_field(
+			'statifyblacklist-referer-blacklist',
+			__( 'Referer filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_referer_blacklist' ),
+			'statify-blacklist',
+			'statifyblacklist-referer',
+			array( 'label_for' => 'statifyblacklist-referer-blacklist' )
+		);
+
+		// Target filter.
+		add_settings_section(
+			'statifyblacklist-target',
+			__( 'Target filter', 'statify-blacklist' ),
+			null,
+			'statify-blacklist'
+		);
+		add_settings_field(
+			'statifyblacklist-target-active',
+			__( 'Activate live filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_target_active' ),
+			'statify-blacklist',
+			'statifyblacklist-target'
+		);
+		add_settings_field(
+			'statifyblacklist-target-cron',
+			__( 'CronJob execution', 'statify-blacklist' ),
+			array( __CLASS__, 'option_target_cron' ),
+			'statify-blacklist',
+			'statifyblacklist-target'
+		);
+		add_settings_field(
+			'statifyblacklist-target-regexp',
+			__( 'Matching method', 'statify-blacklist' ),
+			array( __CLASS__, 'option_target_regexp' ),
+			'statify-blacklist',
+			'statifyblacklist-target',
+			array( 'label_for' => 'statifyblacklist-target-regexp' )
+		);
+		add_settings_field(
+			'statifyblacklist-target-blacklist',
+			__( 'Target filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_target_blacklist' ),
+			'statify-blacklist',
+			'statifyblacklist-target',
+			array( 'label_for' => 'statifyblacklist-target-blacklist' )
+		);
+
+		// IP filter.
+		add_settings_section(
+			'statifyblacklist-ip',
+			__( 'IP filter', 'statify-blacklist' ),
+			null,
+			'statify-blacklist'
+		);
+		add_settings_field(
+			'statifyblacklist-ip-active',
+			__( 'Activate live filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_ip_active' ),
+			'statify-blacklist',
+			'statifyblacklist-ip'
+		);
+		add_settings_field(
+			'statifyblacklist-ip-blacklist',
+			__( 'IP filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_ip_blacklist' ),
+			'statify-blacklist',
+			'statifyblacklist-ip',
+			array( 'label_for' => 'statifyblacklist-ip-blacklist' )
+		);
+
+		// User agent filter.
+		add_settings_section(
+			'statifyblacklist-ua',
+			__( 'User agent filter', 'statify-blacklist' ),
+			null,
+			'statify-blacklist'
+		);
+		add_settings_field(
+			'statifyblacklist-ua-active',
+			__( 'Activate live filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_ua_active' ),
+			'statify-blacklist',
+			'statifyblacklist-ua'
+		);
+		add_settings_field(
+			'statifyblacklist-ua-regexp',
+			__( 'Matching method', 'statify-blacklist' ),
+			array( __CLASS__, 'option_ua_regexp' ),
+			'statify-blacklist',
+			'statifyblacklist-ua',
+			array( 'label_for' => 'statifyblacklist-ua-regexp' )
+		);
+		add_settings_field(
+			'statifyblacklist-ua-blacklist',
+			__( 'User agent filter', 'statify-blacklist' ),
+			array( __CLASS__, 'option_ua_blacklist' ),
+			'statify-blacklist',
+			'statifyblacklist-ua',
+			array( 'label_for' => 'statifyblacklist-ua-blacklist' )
+		);
+	}
+
+	/**
+	 * Creates the settings pages.
+	 *
+	 * @return void
+	 */
+	public static function create_settings_page() {
+		?>
+		<div class="wrap">
+			<h1><?php esc_html_e( 'Statify Filter', 'statify-blacklist' ); ?></h1>
+
+			<form id="statify-settings" method="post" action="options.php">
+				<?php
+				settings_fields( 'statify-blacklist' );
+				do_settings_sections( 'statify-blacklist' );
+				submit_button();
+				?>
+				<hr>
+				<input class="button-secondary" type="submit" name="cleanUp"
+					value="<?php esc_html_e( 'CleanUp Database', 'statify-blacklist' ); ?>"
+					onclick="return confirm('<?php echo esc_js( __( 'Do you really want to apply filters to database? This cannot be undone.', 'statify-blacklist' ) ); ?>');">
+				<p class="description">
+					<?php esc_html_e( 'Applies referer and target filter (even if disabled) to data stored in database.', 'statify-blacklist' ); ?>
+					<em><?php esc_html_e( 'This cannot be undone!', 'statify-blacklist' ); ?></em>
+				</p>
+			</form>
+		</div>
+
+		<?php
+	}
+
+	/*
+	 * Disable some code style rules that are impractical for textarea content:
+	 *
+	 * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentBeforeOpen
+	 * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentAfterEnd
+	 */
+
+	/**
+	 * Option for activating the live referer filter.
+	 *
+	 * @return void
+	 */
+	public static function option_referer_active() {
+		?>
+		<fieldset>
+			<legend class="screen-reader-text"><?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?></legend>
+			<label for="statifyblacklist-referer-active">
+				<input id="statifyblacklist-referer-active" name="statify-blacklist[referer][active]" type="checkbox" value="1" <?php checked( StatifyBlacklist::$options['referer']['active'], 1 ); ?>">
+				<?php esc_html_e( 'Activate', 'statify-blacklist' ); ?>
+			</label>
+			<p class="description">
+				<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
+			</p>
+		</fieldset>
+		<?php
+	}
+
+	/**
+	 * Option for activating cron the referer filter.
+	 *
+	 * @return void
+	 */
+	public static function option_referer_cron() {
+		?>
+		<fieldset>
+			<legend class="screen-reader-text"><?php esc_html_e( 'CronJob execution', 'statify-blacklist' ); ?></legend>
+			<label for="statifyblacklist-referer-cron">
+				<input id="statifyblacklist-referer-cron" name="statify-blacklist[referer][cron]" type="checkbox" value="1" <?php checked( StatifyBlacklist::$options['referer']['cron'], 1 ); ?>">
+				<?php esc_html_e( 'Activate', 'statify-blacklist' ); ?>
+			</label>
+			<p class="description">
+				<?php esc_html_e( 'Periodically clean up database in background', 'statify-blacklist' ); ?>
+			</p>
+		</fieldset>
+		<?php
+	}
+
+	/**
+	 * Option for referer matching method.
+	 *
+	 * @return void
+	 */
+	public static function option_referer_regexp() {
+		?>
+		<select id="statifyblacklist-referer-regexp" name="statify-blacklist[referer][regexp]">
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
+				<?php esc_html_e( 'Domain', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_KEYWORD ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_KEYWORD ); ?>>
+				<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
+				<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
+				<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
+			</option>
+		</select>
+		<p class="description">
+			<?php esc_html_e( 'Domain', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match given domain including subdomains', 'statify-blacklist' ); ?>
+			<br>
+			<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match every referer that contains one of the keywords', 'statify-blacklist' ); ?>
+			<br>
+			<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match referer by regular expression', 'statify-blacklist' ); ?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for the referer filter list.
+	 *
+	 * @return void
+	 */
+	public static function option_referer_blacklist() {
+		?>
+		<textarea id="statifyblacklist-referer-blacklist" name="statify-blacklist[referer][blacklist]" cols="40" rows="5"><?php
+		print esc_html( implode( "\r\n", array_keys( StatifyBlacklist::$options['referer']['blacklist'] ) ) );
+		?></textarea>
+		<p class="description">
+			<?php esc_html_e( 'Add one domain (without subdomains) each line, e.g. example.com', 'statify-blacklist' ); ?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for activating the live target filter.
+	 *
+	 * @return void
+	 */
+	public static function option_target_active() {
+		?>
+		<fieldset>
+			<legend class="screen-reader-text"><?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?></legend>
+			<label for="statifyblacklist-target-active">
+				<input id="statifyblacklist-target-active" name="statify-blacklist[target][active]" type="checkbox" value="1" <?php checked( StatifyBlacklist::$options['target']['active'], 1 ); ?>">
+				<?php esc_html_e( 'Activate', 'statify-blacklist' ); ?>
+			</label>
+			<p class="description">
+				<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
+			</p>
+		</fieldset>
+		<?php
+	}
+
+	/**
+	 * Option for activating cron the target filter.
+	 *
+	 * @return void
+	 */
+	public static function option_target_cron() {
+		?>
+		<fieldset>
+			<legend class="screen-reader-text"><?php esc_html_e( 'CronJob execution', 'statify-blacklist' ); ?></legend>
+			<label for="statifyblacklist-target-cron">
+				<input id="statifyblacklist-target-cron" name="statify-blacklist[target][cron]" type="checkbox" value="1" <?php checked( StatifyBlacklist::$options['target']['cron'], 1 ); ?>">
+				<?php esc_html_e( 'Activate', 'statify-blacklist' ); ?>
+			</label>
+			<p class="description">
+				<?php esc_html_e( 'Clean database periodically in background', 'statify-blacklist' ); ?>
+			</p>
+		</fieldset>
+		<?php
+	}
+
+	/**
+	 * Option for target matching method.
+	 *
+	 * @return void
+	 */
+	public static function option_target_regexp() {
+		?>
+		<select id="statifyblacklist-target-regexp" name="statify-blacklist[target][regexp]">
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['target']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
+				<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$options['target']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
+				<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['target']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
+				<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
+			</option>
+		</select>
+		<p class="description">
+			<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match only given targets', 'statify-blacklist' ); ?>
+			<br>
+			<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match target by regular expression', 'statify-blacklist' ); ?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for the target filter list.
+	 *
+	 * @return void
+	 */
+	public static function option_target_blacklist() {
+		?>
+		<textarea id="statifyblacklist-target-blacklist" name="statify-blacklist[target][blacklist]" cols="40" rows="5"><?php
+		print esc_html( implode( "\r\n", array_keys( StatifyBlacklist::$options['target']['blacklist'] ) ) );
+		?></textarea>
+		<p class="description">
+			<?php esc_html_e( 'Add one target URL each line, e.g.', 'statify-blacklist' ); ?> /, /test/page/, /?page_id=123
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for activating the live IP filter.
+	 *
+	 * @return void
+	 */
+	public static function option_ip_active() {
+		?>
+		<fieldset>
+			<legend class="screen-reader-text"><?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?></legend>
+			<label for="statifyblacklist-ip-active">
+				<input id="statifyblacklist-ip-active" name="statify-blacklist[ip][active]" type="checkbox" value="1" <?php checked( StatifyBlacklist::$options['ip']['active'], 1 ); ?>">
+				<?php esc_html_e( 'Activate', 'statify-blacklist' ); ?>
+			</label>
+			<p class="description">
+				<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
+				<br>
+				<?php esc_html_e( 'Cron execution is not possible for IP filter, because IP addresses are not stored.', 'statify-blacklist' ); ?>
+			</p>
+		</fieldset>
+		<?php
+	}
+
+	/**
+	 * Option for the IP filter list.
+	 *
+	 * @return void
+	 */
+	public static function option_ip_blacklist() {
+		?>
+		<textarea id="statifyblacklist-ip-blacklist" name="statify-blacklist[ip][blacklist]" cols="40" rows="5"><?php
+		print esc_html( implode( "\r\n", StatifyBlacklist::$options['ip']['blacklist'] ) );
+		?></textarea>
+		<p class="description">
+			<?php esc_html_e( 'Add one IP address or range per line, e.g.', 'statify-blacklist' ); ?>
+			127.0.0.1, 192.168.123.0/24, 2001:db8:a0b:12f0::1/64
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for activating the live user agent filter.
+	 *
+	 * @return void
+	 */
+	public static function option_ua_active() {
+		?>
+		<label for="statifyblacklist-ua-active">
+			<input id="statifyblacklist-ua-active" name="statify-blacklist[ua][active]" type="checkbox" value="1" <?php checked( StatifyBlacklist::$options['ua']['active'], 1 ); ?>">
+			<?php esc_html_e( 'Activate', 'statify-blacklist' ); ?>
+		</label>
+
+		<p class="description">
+			<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
+			<br>
+			<?php esc_html_e( 'Cron execution is not possible for user agent filter, because the user agent is stored.', 'statify-blacklist' ); ?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for user agent matching method.
+	 *
+	 * @return void
+	 */
+	public static function option_ua_regexp() {
+		?>
+		<select id="statifyblacklist-ua-regexp" name="statify-blacklist[ua][regexp]">
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
+				<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_KEYWORD ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_KEYWORD ); ?>>
+				<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
+				<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
+			</option>
+			<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
+				<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
+			</option>
+		</select>
+		<p class="description">
+			<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match only given user agents', 'statify-blacklist' ); ?>
+			<br>
+			<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match every referer that contains one of the keywords', 'statify-blacklist' ); ?>
+			<br>
+			<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match user agent by regular expression', 'statify-blacklist' ); ?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Option for the user agent filter list.
+	 *
+	 * @return void
+	 */
+	public static function option_ua_blacklist() {
+		?>
+		<textarea name="statify-blacklist[ua][blacklist]" id="statifyblacklist-ua-blacklist" cols="40" rows="5"><?php
+		print esc_html( implode( "\r\n", StatifyBlacklist::$options['ua']['blacklist'] ) );
+		?></textarea>
+		<p class="description">
+			<?php esc_html_e( 'Add one user agent string per line, e.g.', 'statify-blacklist' ); ?>
+			MyBot/1.23
+		</p>
+		<?php
+	}
+
+	/**
+	 * Validate and sanitize submitted options.
+	 *
+	 * @param array $options Original options.
+	 *
+	 * @return array Validated and sanitized options.
+	 */
+	public static function sanitize_options( $options ) {
+		// Extract filter lists from multi-line inputs.
+		$referer = self::parse_multiline_option( $options['referer']['blacklist'] );
+		$target  = self::parse_multiline_option( $options['target']['blacklist'] );
+		$ip      = self::parse_multiline_option( $options['ip']['blacklist'] );
+		$ua      = self::parse_multiline_option( $options['ua']['blacklist'] );
+
+		// Generate options.
+		$res = array(
+			'referer' => array(
+				'active'    => isset( $options['referer']['active'] ) ? (int) $options['referer']['active'] : 0,
+				'cron'      => isset( $options['referer']['cron'] ) ? (int) $options['referer']['cron'] : 0,
+				'regexp'    => isset( $options['referer']['regexp'] ) ? (int) $options['referer']['regexp'] : 0,
+				'blacklist' => array_flip( $referer ),
+			),
+			'target'  => array(
+				'active'    => isset( $options['target']['active'] ) ? (int) $options['target']['active'] : 0,
+				'cron'      => isset( $options['target']['cron'] ) ? (int) $options['target']['cron'] : 0,
+				'regexp'    => isset( $options['target']['regexp'] ) ? (int) $options['target']['regexp'] : 0,
+				'blacklist' => array_flip( $target ),
+			),
+			'ip'      => array(
+				'active'    => isset( $options['ip']['active'] ) ? (int) $options['ip']['active'] : 0,
+				'blacklist' => $ip,
+			),
+			'ua'      => array(
+				'active'    => isset( $options['ua']['active'] ) ? (int) $options['ua']['active'] : 0,
+				'regexp'    => isset( $options['ua']['regexp'] ) ? (int) $options['ua']['regexp'] : 0,
+				'blacklist' => array_flip( $ua ),
+			),
+			'version' => StatifyBlacklist::VERSION_MAIN,
+		);
+
+		// Apply sanitizations.
+		self::sanitize_referer_options( $res['referer'] );
+		self::sanitize_target_options( $res['target'] );
+		self::sanitize_ip_options( $res['ip'] );
+
+		return $res;
+	}
+
+	/**
+	 * Sanitize referer options.
+	 *
+	 * @param array $options Original referer options.
+	 *
+	 * @return void
+	 *
+	 * @since 1.7.0
+	 */
+	private static function sanitize_referer_options( &$options ) {
+		$referer_given   = $options['blacklist'];
+		$referer_invalid = array();
+		if ( StatifyBlacklist::MODE_NORMAL === $options['regexp'] ) {
+			// Sanitize URLs and remove empty inputs.
+			$referer_sanitized = self::sanitize_urls( $referer_given );
+		} elseif ( StatifyBlacklist::MODE_REGEX === $options['regexp'] || StatifyBlacklist::MODE_REGEX_CI === $options['regexp'] ) {
+			$referer_sanitized = $referer_given;
+			// Check regular expressions.
+			$referer_invalid = self::sanitize_regex( $referer_given );
+		} else {
+			$referer_sanitized = $referer_given;
+		}
+		$referer_diff         = array_diff_key( $referer_given, $referer_sanitized );
+		$options['blacklist'] = $referer_sanitized;
+
+		// Generate messages.
+		if ( ! empty( $referer_diff ) ) {
+			add_settings_error(
+				'statify-blacklist',
+				'referer-diff',
+				__( 'Some URLs are invalid and have been sanitized.', 'statify-blacklist' ),
+				'warning'
+			);
+		}
+		if ( ! empty( $referer_invalid ) ) {
+			add_settings_error(
+				'statify-blacklist',
+				'referer-invalid',
+				__( 'Some regular expressions for referrers are invalid:', 'statify-blacklist' ) . '<br>' . implode( '<br>', $referer_invalid )
+			);
+		}
+	}
+
+	/**
+	 * Sanitize target options.
+	 *
+	 * @param array $options Original target options.
+	 *
+	 * @return void
+	 *
+	 * @since 1.7.0
+	 */
+	private static function sanitize_target_options( &$options ) {
+		$target_given   = $options['blacklist'];
+		$target_invalid = array();
+		if ( StatifyBlacklist::MODE_REGEX === $options['regexp'] || StatifyBlacklist::MODE_REGEX_CI === $options['regexp'] ) {
+			$target_sanitized = $target_given;
+			// Check regular expressions.
+			$target_invalid = self::sanitize_regex( $target_given );
+		} else {
+			$target_sanitized = $target_given;
+		}
+		$options['blacklist'] = $target_sanitized;
+
+		// Generate messages.
+		if ( ! empty( $target_invalid ) ) {
+			add_settings_error(
+				'statify-blacklist',
+				'target-invalid',
+				__( 'Some regular expressions for targets are invalid:', 'statify-blacklist' ) . '<br>' . implode( '<br>', $target_invalid )
+			);
+		}
+	}
+
+	/**
+	 * Sanitize IPs and subnets and remove empty inputs.
+	 *
+	 * @param array $options Original IP options.
+	 *
+	 * @return void
+	 *
+	 * @since 1.7.0
+	 */
+	private static function sanitize_ip_options( &$options ) {
+		$given_ip             = $options['blacklist'];
+		$sanitized_ip         = self::sanitize_ips( $given_ip );
+		$ip_diff              = array_diff( $given_ip, $sanitized_ip );
+		$options['blacklist'] = $sanitized_ip;
+
+		// Generate messages.
+		if ( ! empty( $ip_diff ) ) {
+			add_settings_error(
+				'statify-blacklist',
+				'ip-diff',
+				// translators: List of invalid IP addresses (comma separated).
+				sprintf( __( 'Some IPs are invalid: %s', 'statify-blacklist' ), implode( ', ', $ip_diff ) ),
+				'warning'
+			);
+		}
+	}
+
+	/**
+	 * Sanitize URLs and remove empty results.
+	 *
+	 * @param array $urls given array of URLs.
+	 *
+	 * @return array  sanitized array.
+	 *
+	 * @since 1.1.1
+	 * @since 1.7.0 moved from StatifyBlacklist_Admin to StatifyBlacklist_Settings.
+	 */
+	private static function sanitize_urls( $urls ) {
+		return array_flip(
+			array_filter(
+				array_map(
+					function ( $r ) {
+						return preg_replace( '/[^\da-z\.-]/i', '', filter_var( $r, FILTER_SANITIZE_URL ) );
+					},
+					array_flip( $urls )
+				)
+			)
+		);
+	}
+
+	/**
+	 * Sanitize IP addresses with optional CIDR notation and remove empty results.
+	 *
+	 * @param array $ips given array of URLs.
+	 *
+	 * @return array  sanitized array.
+	 *
+	 * @since 1.4.0
+	 * @since 1.7.0 moved from StatifyBlacklist_Admin to StatifyBlacklist_Settings.
+	 */
+	private static function sanitize_ips( $ips ) {
+		return array_filter(
+			array_map( 'strtolower', $ips ),
+			function ( $ip ) {
+				return preg_match(
+					'/^((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])(\/([0-9]|[1-2][0-9]|3[0-2]))?$/',
+					$ip
+				) ||
+				preg_match(
+					'/^(([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,7}:|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}' .
+					'|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}' .
+					'|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}' .
+					'|[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|:((:[0-9a-f]{1,4}){1,7}|:)' .
+					'|fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]' .
+					'|1?[0-9])?[0-9])|([0-9a-f]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[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.
+	 *
+	 * @param array $expressions Given pre-sanitized array of regular expressions.
+	 *
+	 * @return array Array of invalid expressions.
+	 *
+	 * @since 1.5.0 #13
+	 * @since 1.7.0 moved from StatifyBlacklist_Admin to StatifyBlacklist_Settings.
+	 */
+	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 );
+			}
+		);
+	}
+
+	/**
+	 * Parse multi-line option string.
+	 *
+	 * @param string $raw Input string.
+	 *
+	 * @return array Parsed options.
+	 */
+	private static function parse_multiline_option( $raw ) {
+		if ( empty( trim( $raw ) ) ) {
+			return array();
+		} else {
+			return array_filter(
+				array_map(
+					function ( $a ) {
+						return trim( $a );
+					},
+					explode( "\r\n", str_replace( '\\\\', '\\', $raw ) )
+				),
+				function ( $a ) {
+					return ! empty( $a );
+				}
+			);
+		}
+	}
+}
diff --git a/phpcs.xml b/phpcs.xml
index a4ff01e..1e0c607 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -8,7 +8,6 @@
 	<!-- Files to sniff -->
 	<file>statify-blacklist.php</file>
 	<file>inc</file>
-	<file>views</file>
 
 	<!-- Compliance with WordPress Coding Standard -->
 	<config name="minimum_supported_wp_version" value="4.7"/>
diff --git a/statify-blacklist.php b/statify-blacklist.php
index 240d86b..1f25d37 100644
--- a/statify-blacklist.php
+++ b/statify-blacklist.php
@@ -70,6 +70,7 @@ function statify_blacklist_autoload( $class ) {
 	$plugin_classes = array(
 		'StatifyBlacklist',
 		'StatifyBlacklist_Admin',
+		'StatifyBlacklist_Settings',
 		'StatifyBlacklist_System',
 	);
 
diff --git a/test/StatifyBlacklist_Admin_Test.php b/test/StatifyBlacklist_Admin_Test.php
deleted file mode 100644
index 3ad9f20..0000000
--- a/test/StatifyBlacklist_Admin_Test.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * Statify Filter: Unit Test
- *
- * This is a PHPunit test class for the plugin's functionality
- *
- * @package Statify_Blacklist
- */
-
-/**
- * Class StatifyBlacklist_Admin_Test.
- *
- * PHPUnit test class for StatifyBlacklist_Admin.
- */
-class StatifyBlacklist_Admin_Test extends PHPUnit\Framework\TestCase {
-
-
-	/**
-	 * Test sanitization of IP addresses.
-	 *
-	 * @return void
-	 */
-	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  = invoke_static( StatifyBlacklist_Admin::class, 'sanitize_ips', array( array_merge( $valid, $invalid ) ) );
-		$this->assertNotFalse( $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.
-		$valid   = array(
-			'2001:db8:a0b:12f0::',
-			'2001:db8:a0b:12f0::1',
-			'2001:db8:a0b:12f0::1/128',
-			'2001:DB8:A0B:12F0::/64',
-			'fe80::7645:6de2:ff:1',
-			'::ffff:192.0.2.123',
-		);
-		$invalid = array(
-			'2001:db8:a0b:12f0::x',
-			'2001:db8:a0b:12f0:::',
-			'2001:fffff:a0b:12f0::1',
-			'2001:DB8:A0B:12F0::/129',
-			'1:2:3:4:5:6:7:8:9',
-			'::ffff:12.34.56.789',
-		);
-		$result  = invoke_static( StatifyBlacklist_Admin::class, 'sanitize_ips', array( array_merge( $valid, $invalid ) ) );
-		$this->assertNotFalse( $result );
-		if ( method_exists( $this, 'assertIsArray' ) ) {
-			$this->assertIsArray( $result );
-		} else {
-			$this->assertInternalType( 'array', $result );
-		}
-		$this->assertEquals( array_map( 'strtolower', $valid ), $result );
-	}
-}
diff --git a/test/StatifyBlacklist_Settings_Test.php b/test/StatifyBlacklist_Settings_Test.php
new file mode 100644
index 0000000..16e7620
--- /dev/null
+++ b/test/StatifyBlacklist_Settings_Test.php
@@ -0,0 +1,329 @@
+<?php
+/**
+ * Statify Filter: Unit Test
+ *
+ * This is a PHPunit test class for the plugin's functionality
+ *
+ * @package Statify_Blacklist
+ */
+
+/**
+ * Class StatifyBlacklist_Settings_Test.
+ *
+ * PHPUnit test class for StatifyBlacklist_Settings.
+ */
+class StatifyBlacklist_Settings_Test extends PHPUnit\Framework\TestCase {
+
+	/**
+	 * Test options sanitization.
+	 *
+	 * @return void
+	 */
+	public function test_sanitize_options() {
+		global $settings_error;
+
+		// Emulate default submission: nothing checked, all textareas empty.
+		$raw = array(
+			'referer' => array(
+				'blacklist' => '',
+				'regexp'    => '0',
+			),
+			'target'  => array(
+				'blacklist' => '',
+				'regexp'    => '0',
+			),
+			'ip'      => array( 'blacklist' => '' ),
+			'ua'      => array(
+				'blacklist' => '',
+				'regexp'    => '0',
+			),
+		);
+
+		$sanitized = StatifyBlacklist_Settings::sanitize_options( $raw );
+
+		self::assertEmpty( $settings_error );
+		self::assertEquals(
+			array(
+				'referer' => array(
+					'active'    => 0,
+					'cron'      => 0,
+					'blacklist' => array(),
+					'regexp'    => StatifyBlacklist::MODE_NORMAL,
+				),
+				'target'  => array(
+					'active'    => 0,
+					'cron'      => 0,
+					'blacklist' => array(),
+					'regexp'    => StatifyBlacklist::MODE_NORMAL,
+				),
+				'ip'      => array(
+					'active'    => 0,
+					'blacklist' => array(),
+				),
+				'ua'      => array(
+					'active'    => 0,
+					'regexp'    => StatifyBlacklist::MODE_NORMAL,
+					'blacklist' => array(),
+				),
+				'version' => StatifyBlacklist::VERSION_MAIN,
+			),
+			$sanitized
+		);
+
+		// Some checked options and some valid entries.
+		$raw = array(
+			'referer' => array(
+				'cron'      => '1',
+				'blacklist' => "example.com\r\nexample.net\r\nexample.org",
+				'regexp'    => '0',
+			),
+			'target'  => array(
+				'active'    => '1',
+				'blacklist' => "foo\r\nbar\r\ntest",
+				'regexp'    => '3',
+			),
+			'ip'      => array(
+				'active'    => '1',
+				'blacklist' => "127.0.0.1/8\r\n::1",
+			),
+			'ua'      => array(
+				'blacklist' => 'MyBot/1.23',
+				'regexp'    => '1',
+			),
+		);
+
+		$sanitized = StatifyBlacklist_Settings::sanitize_options( $raw );
+
+		self::assertEmpty( $settings_error );
+		self::assertEquals(
+			array(
+				'referer' => array(
+					'active'    => 0,
+					'cron'      => 1,
+					'blacklist' => array(
+						'example.com' => 0,
+						'example.net' => 1,
+						'example.org' => 2,
+					),
+					'regexp'    => StatifyBlacklist::MODE_NORMAL,
+				),
+				'target'  => array(
+					'active'    => 1,
+					'cron'      => 0,
+					'blacklist' => array(
+						'foo'  => 0,
+						'bar'  => 1,
+						'test' => 2,
+					),
+					'regexp'    => StatifyBlacklist::MODE_KEYWORD,
+				),
+				'ip'      => array(
+					'active'    => 1,
+					'blacklist' => array(
+						'127.0.0.1/8',
+						'::1',
+					),
+				),
+				'ua'      => array(
+					'active'    => 0,
+					'regexp'    => StatifyBlacklist::MODE_REGEX,
+					'blacklist' => array(
+						'MyBot/1.23' => 0,
+					),
+				),
+				'version' => StatifyBlacklist::VERSION_MAIN,
+			),
+			$sanitized
+		);
+
+		// Now we have some additional nonsense fields and invalid entries.
+		$raw = array(
+			'testme ' => 'whatever',
+			'referer' => array(
+				'cron'      => '1',
+				'blacklist' => "  example\\.com   \r\nexample(\\.net\r\nexample\\.com",
+				'regexp'    => '1',
+			),
+			'target'  => array(
+				'active'    => '1',
+				'blacklist' => "fo.\r\n[bar\r\n*test",
+				'regexp'    => '2',
+			),
+			'ip'      => array(
+				'active'    => '1',
+				'blacklist' => "127.0.0.1/8\r\nthisisnotanip",
+			),
+			'ua'      => array(
+				'blacklist' => 'MyBot/1.23',
+				'regexp'    => '1',
+			),
+		);
+
+		$sanitized = StatifyBlacklist_Settings::sanitize_options( $raw );
+
+		self::assertEquals(
+			array(
+				'referer' => array(
+					'active'    => 0,
+					'cron'      => 1,
+					'blacklist' => array(
+						'example\.com'  => 2,
+						'example(\.net' => 1,
+					),
+					'regexp'    => StatifyBlacklist::MODE_REGEX,
+				),
+				'target'  => array(
+					'active'    => 1,
+					'cron'      => 0,
+					'blacklist' => array(
+						'fo.'   => 0,
+						'[bar'  => 1,
+						'*test' => 2,
+					),
+					'regexp'    => StatifyBlacklist::MODE_REGEX_CI,
+				),
+				'ip'      => array(
+					'active'    => 1,
+					'blacklist' => array(
+						'127.0.0.1/8',
+					),
+				),
+				'ua'      => array(
+					'active'    => 0,
+					'regexp'    => StatifyBlacklist::MODE_REGEX,
+					'blacklist' => array(
+						'MyBot/1.23' => 0,
+					),
+				),
+				'version' => StatifyBlacklist::VERSION_MAIN,
+			),
+			$sanitized
+		);
+
+		self::assertEquals(
+			array(
+				array( 'statify-blacklist', 'referer-invalid', 'Some regular expressions for referrers are invalid:<br>example(\.net', 'error' ),
+				array( 'statify-blacklist', 'target-invalid', 'Some regular expressions for targets are invalid:<br>[bar<br>*test', 'error' ),
+				array( 'statify-blacklist', 'ip-diff', 'Some IPs are invalid: thisisnotanip', 'warning' ),
+			),
+			$settings_error
+		);
+	}
+
+	/**
+	 * Test sanitization of IP addresses.
+	 *
+	 * @return void
+	 */
+	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  = invoke_static( StatifyBlacklist_Settings::class, 'sanitize_ips', array( array_merge( $valid, $invalid ) ) );
+		$this->assertNotFalse( $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.
+		$valid   = array(
+			'2001:db8:a0b:12f0::',
+			'2001:db8:a0b:12f0::1',
+			'2001:db8:a0b:12f0::1/128',
+			'2001:DB8:A0B:12F0::/64',
+			'fe80::7645:6de2:ff:1',
+			'::ffff:192.0.2.123',
+		);
+		$invalid = array(
+			'2001:db8:a0b:12f0::x',
+			'2001:db8:a0b:12f0:::',
+			'2001:fffff:a0b:12f0::1',
+			'2001:DB8:A0B:12F0::/129',
+			'1:2:3:4:5:6:7:8:9',
+			'::ffff:12.34.56.789',
+		);
+		$result  = invoke_static( StatifyBlacklist_Settings::class, 'sanitize_ips', array( array_merge( $valid, $invalid ) ) );
+		$this->assertNotFalse( $result );
+		if ( method_exists( $this, 'assertIsArray' ) ) {
+			$this->assertIsArray( $result );
+		} else {
+			$this->assertInternalType( 'array', $result );
+		}
+		$this->assertEquals(
+			array(
+				'2001:db8:a0b:12f0::',
+				'2001:db8:a0b:12f0::1',
+				'2001:db8:a0b:12f0::1/128',
+				'2001:db8:a0b:12f0::/64',
+				'fe80::7645:6de2:ff:1',
+				'::ffff:192.0.2.123',
+			),
+			array_values( $result )
+		);
+	}
+
+	/**
+	 * Test settings registration.
+	 *
+	 * @return void
+	 */
+	public function test_register_settings() {
+		global $settings;
+		$settings = array();
+
+		StatifyBlacklist_Settings::register_settings();
+		$this->assertEquals( array( 'statify-blacklist' ), array_keys( $settings ), 'unexpected settings pages' );
+		$this->assertEquals(
+			array(
+				'statifyblacklist-referer',
+				'statifyblacklist-target',
+				'statifyblacklist-ip',
+				'statifyblacklist-ua',
+			),
+			array_keys( $settings['statify-blacklist']['sections'] ),
+			'unexpected settings sections'
+		);
+		$this->assertEquals(
+			array(
+				'statifyblacklist-referer-active',
+				'statifyblacklist-referer-cron',
+				'statifyblacklist-referer-regexp',
+				'statifyblacklist-referer-blacklist',
+			),
+			array_keys( $settings['statify-blacklist']['sections']['statifyblacklist-referer']['fields'] ),
+			'unexpected fields in referrer section'
+		);
+		$this->assertEquals(
+			array(
+				'statifyblacklist-target-active',
+				'statifyblacklist-target-cron',
+				'statifyblacklist-target-regexp',
+				'statifyblacklist-target-blacklist',
+			),
+			array_keys( $settings['statify-blacklist']['sections']['statifyblacklist-target']['fields'] ),
+			'unexpected fields in target section'
+		);
+		$this->assertEquals(
+			array( 'statifyblacklist-ip-active', 'statifyblacklist-ip-blacklist' ),
+			array_keys( $settings['statify-blacklist']['sections']['statifyblacklist-ip']['fields'] ),
+			'unexpected fields in ip section'
+		);
+		$this->assertEquals(
+			array(
+				'statifyblacklist-ua-active',
+				'statifyblacklist-ua-regexp',
+				'statifyblacklist-ua-blacklist',
+			),
+			array_keys( $settings['statify-blacklist']['sections']['statifyblacklist-ua']['fields'] ),
+			'unexpected fields in user agent section'
+		);
+	}
+}
diff --git a/test/bootstrap.php b/test/bootstrap.php
index 6a3796e..97228e7 100644
--- a/test/bootstrap.php
+++ b/test/bootstrap.php
@@ -17,6 +17,7 @@ const ABSPATH = false;
  */
 require_once __DIR__ . '/../inc/class-statifyblacklist.php';
 require_once __DIR__ . '/../inc/class-statifyblacklist-admin.php';
+require_once __DIR__ . '/../inc/class-statifyblacklist-settings.php';
 require_once __DIR__ . '/../inc/class-statifyblacklist-system.php';
 
 // Include Composer autoloader.
@@ -36,6 +37,8 @@ function invoke_static( $class, $method_name, $parameters = array() ) {
 // Some mocked WP functions.
 $mock_options   = array();
 $mock_multisite = false;
+$settings_error = array();
+$settings       = array();
 
 /** @ignore */
 function is_multisite() {
@@ -87,3 +90,45 @@ function wp_parse_url( $value ) {
 function wp_unslash( $value ) {
 	return is_string( $value ) ? stripslashes( $value ) : $value;
 }
+
+/** @ignore */
+function __( $text, $domain = 'default' ) {
+	return $text;
+}
+
+/** @ignore */
+function add_settings_error( $setting, $code, $message, $type = 'error' ) {
+	global $settings_error;
+	$settings_error[] = array( $setting, $code, $message, $type );
+}
+
+/** @ignore */
+function register_setting( $option_group, $option_name, $args = array() ) {
+	global $settings;
+	$settings[ $option_name ] = array(
+		'group'    => $option_group,
+		'args'     => $args,
+		'sections' => array(),
+	);
+}
+
+/** @ignore */
+function add_settings_section( $id, $title, $callback, $page, $args = array() ) {
+	global $settings;
+	$settings[ $page ]['sections'][ $id ] = array(
+		'title'    => $title,
+		'callback' => $callback,
+		'args'     => $args,
+		'fields'   => array(),
+	);
+}
+
+/** @ignore */
+function add_settings_field( $id, $title, $callback, $page, $section = 'default', $args = array() ) {
+	global $settings;
+	$settings[ $page ]['sections'][ $section ]['fields'][ $id ] = array(
+		'title'    => $title,
+		'callback' => $callback,
+		'args'     => $args,
+	);
+}
diff --git a/views/settings-page.php b/views/settings-page.php
deleted file mode 100755
index 3c082cd..0000000
--- a/views/settings-page.php
+++ /dev/null
@@ -1,488 +0,0 @@
-<?php
-/**
- * Statify Filter: Settings View
- *
- * This file contains the dynamic HTML skeleton for the plugin's settings page.
- *
- * @package    Statify_Blacklist
- * @subpackage Admin
- * @since      1.0.0
- */
-
-// phpcs:disable WordPress.WhiteSpace.PrecisionAlignment.Found
-
-// Quit.
-defined( 'ABSPATH' ) || exit;
-
-// Update plugin options.
-if ( ! empty( $_POST['statifyblacklist'] ) ) {
-	// Verify nonce.
-	check_admin_referer( 'statify-blacklist-settings' );
-
-	// Check user capabilities.
-	if ( ! current_user_can( 'manage_options' ) ) {
-		die( esc_html__( 'Are you sure you want to do this?', 'statify-blacklist' ) );
-	}
-
-	if ( ! empty( $_POST['cleanUp'] ) ) {
-		// CleanUp DB.
-		StatifyBlacklist_Admin::cleanup_database();
-	} else {
-		// Extract referer array.
-		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 = array_filter(
-				array_map(
-					function ( $a ) {
-						return trim( $a );
-					},
-					explode( "\r\n", $referer_str )
-				),
-				function ( $a ) {
-					return ! empty( $a );
-				}
-			);
-		}
-
-		// Extract target array.
-		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 = 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 ( 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 = array_filter(
-				array_map(
-					function ( $a ) {
-						return trim( $a );
-					},
-					explode( "\r\n", $ip_str )
-				),
-				function ( $a ) {
-					return ! empty( $a );
-				}
-			);
-		}
-
-		// Extract user agent array.
-		if ( isset( $_POST['statifyblacklist']['ua']['blacklist'] ) ) {
-			$ua_string = sanitize_textarea_field( wp_unslash( $_POST['statifyblacklist']['ua']['blacklist'] ) );
-		}
-		if ( empty( trim( $ua_string ) ) ) {
-			$ua = array();
-		} else {
-			$ua = array_filter(
-				array_map(
-					function ( $a ) {
-						return trim( $a );
-					},
-					explode( "\r\n", str_replace( '\\\\', '\\', $ua_string ) )
-				),
-				function ( $a ) {
-					return ! empty( $a );
-				}
-			);
-		}
-
-		// Update options (data will be sanitized).
-		$statifyblacklist_update_result = StatifyBlacklist_Admin::update_options(
-			array(
-				'referer' => array(
-					'active'    => isset( $_POST['statifyblacklist']['referer']['active'] )
-						? (int) $_POST['statifyblacklist']['referer']['active'] : 0,
-					'cron'      => isset( $_POST['statifyblacklist']['referer']['cron'] )
-						? (int) $_POST['statifyblacklist']['referer']['cron'] : 0,
-					'regexp'    => isset( $_POST['statifyblacklist']['referer']['regexp'] )
-						? (int) $_POST['statifyblacklist']['referer']['regexp'] : 0,
-					'blacklist' => array_flip( $referer ),
-				),
-				'target'  => array(
-					'active'    => isset( $_POST['statifyblacklist']['target']['active'] )
-						? (int) $_POST['statifyblacklist']['target']['active'] : 0,
-					'cron'      => isset( $_POST['statifyblacklist']['target']['cron'] )
-						? (int) $_POST['statifyblacklist']['target']['cron'] : 0,
-					'regexp'    => isset( $_POST['statifyblacklist']['target']['regexp'] )
-						? (int) $_POST['statifyblacklist']['target']['regexp'] : 0,
-					'blacklist' => array_flip( $target ),
-				),
-				'ip'      => array(
-					'active'    => isset( $_POST['statifyblacklist']['ip']['active'] )
-						? (int) $_POST['statifyblacklist']['ip']['active'] : 0,
-					'blacklist' => $ip,
-				),
-				'ua'      => array(
-					'active'    => isset( $_POST['statifyblacklist']['ua']['active'] )
-						? (int) $_POST['statifyblacklist']['ua']['active'] : 0,
-					'regexp'    => isset( $_POST['statifyblacklist']['ua']['regexp'] )
-						? (int) $_POST['statifyblacklist']['ua']['regexp'] : 0,
-					'blacklist' => array_flip( $ua ),
-				),
-				'version' => StatifyBlacklist::VERSION_MAIN,
-			)
-		);
-
-		// Generate messages.
-		if ( false !== $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' ) . '<br>' . implode( '<br>', $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']['diff'] ) );
-			}
-		} else {
-			$statifyblacklist_post_success = __( 'Settings updated successfully.', 'statify-blacklist' );
-		}
-	}
-}
-
-/*
- * Disable some code style rules that are impractical for textarea content:
- *
- * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentBeforeOpen
- * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentAfterEnd
- */
-?>
-
-<div class="wrap">
-	<h1><?php esc_html_e( 'Statify Filter', 'statify-blacklist' ); ?></h1>
-	<?php
-	if ( is_plugin_inactive( 'statify/statify.php' ) ) {
-		print '<div class="notice notice-warning"><p>';
-		esc_html_e( 'Statify plugin is not active.', 'statify-blacklist' );
-		print '</p></div>';
-	}
-	if ( isset( $statifyblacklist_post_warning ) ) {
-		foreach ( $statifyblacklist_post_warning as $w ) {
-			print '<div class="notice notice-warning"><p>' .
-				wp_kses( $w, array( 'br' => array() ) ) .
-				'</p></div>';
-		}
-		print '<div class="notice notice-warning"><p>' . esc_html__( 'Settings have not been saved yet.', 'statify-blacklist' ) . '</p></div>';
-	}
-	if ( isset( $statifyblacklist_post_success ) ) {
-		print '<div class="notice notice-success"><p>' .
-			esc_html( $statifyblacklist_post_success ) .
-			'</p></div>';
-	}
-	?>
-	<form action="" method="post" id="statify-blacklist-settings">
-		<?php wp_nonce_field( 'statify-blacklist-settings' ); ?>
-
-		<h2><?php esc_html_e( 'Referer filter', 'statify-blacklist' ); ?></h2>
-
-		<table class="form-table">
-			<tbody>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_active_referer">
-						<?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<input type="checkbox" name="statifyblacklist[referer][active]"
-						   id="statify-blacklist_active_referer"
-						   value="1" <?php checked( StatifyBlacklist::$options['referer']['active'], 1 ); ?>>
-					<p class="description">
-						<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_cron_referer">
-						<?php esc_html_e( 'CronJob execution', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<input type="checkbox" name="statifyblacklist[referer][cron]" id="statify-blacklist_cron_referer"
-						   value="1" <?php checked( StatifyBlacklist::$options['referer']['cron'], 1 ); ?>>
-					<p class="description"><?php esc_html_e( 'Periodically clean up database in background', 'statify-blacklist' ); ?></p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_referer_regexp"><?php esc_html_e( 'Matching method', 'statify-blacklist' ); ?></label>
-				</th>
-				<td>
-					<select name="statifyblacklist[referer][regexp]" id="statify-blacklist_referer_regexp">
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
-							<?php esc_html_e( 'Domain', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_KEYWORD ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_KEYWORD ); ?>>
-							<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
-							<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['referer']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
-							<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
-						</option>
-					</select>
-
-					<p class="description">
-						<?php esc_html_e( 'Domain', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match given domain including subdomains', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match every referer that contains one of the keywords', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match referer by regular expression', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_referer"><?php esc_html_e( 'Referer filter', 'statify-blacklist' ); ?></label>
-				</th>
-				<td>
-					<textarea cols="40" rows="5" name="statifyblacklist[referer][blacklist]" id="statify-blacklist_referer"><?php
-					if ( empty( $statifyblacklist_update_result['referer'] ) ) {
-						print esc_html( implode( "\r\n", array_keys( StatifyBlacklist::$options['referer']['blacklist'] ) ) );
-					} else {
-						print esc_html( implode( "\r\n", array_keys( $statifyblacklist_update_result['referer']['sanitized'] ) ) );
-					}
-					?></textarea>
-					<p class="description">
-						<?php esc_html_e( 'Add one domain (without subdomains) each line, e.g. example.com', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			</tbody>
-		</table>
-
-		<h2><?php esc_html_e( 'Target filter', 'statify-blacklist' ); ?></h2>
-
-		<table class="form-table">
-			<tbody>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_active_target">
-						<?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<input type="checkbox" name="statifyblacklist[target][active]"
-						   id="statify-blacklist_active_target"
-						   value="1" <?php checked( StatifyBlacklist::$options['target']['active'], 1 ); ?>>
-					<p class="description">
-						<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_cron_target">
-						<?php esc_html_e( 'CronJob execution', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<input type="checkbox" name="statifyblacklist[target][cron]" id="statify-blacklist_cron_target"
-						   value="1" <?php checked( StatifyBlacklist::$options['target']['cron'], 1 ); ?>>
-					<p class="description">
-						<?php esc_html_e( 'Clean database periodically in background', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_target_regexp">
-						<?php esc_html_e( 'Matching method', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<select name="statifyblacklist[target][regexp]" id="statify-blacklist_target_regexp">
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['target']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
-							<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$options['target']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
-							<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['target']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
-							<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
-						</option>
-					</select>
-
-					<p class="description">
-						<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match only given targets', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match target by regular expression', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_target">
-						<?php esc_html_e( 'Target filter', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<textarea cols="40" rows="5" name="statifyblacklist[target][blacklist]" id="statify-blacklist_target"><?php
-					if ( empty( $statifyblacklist_update_result['target'] ) ) {
-						print esc_html( implode( "\r\n", array_keys( StatifyBlacklist::$options['target']['blacklist'] ) ) );
-					} else {
-						print esc_html( implode( "\r\n", array_keys( $statifyblacklist_update_result['target']['sanitized'] ) ) );
-					}
-					?></textarea>
-
-					<p class="description">
-						<?php esc_html_e( 'Add one target URL each line, e.g.', 'statify-blacklist' ); ?> /, /test/page/, /?page_id=123
-					</p>
-				</td>
-			</tr>
-			</tbody>
-		</table>
-
-		<h2><?php esc_html_e( 'IP filter', 'statify-blacklist' ); ?></h2>
-
-		<table class="form-table">
-			<tbody>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_active_ip">
-						<?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<input type="checkbox" name="statifyblacklist[ip][active]" id="statify-blacklist_active_ip"
-						   value="1" <?php checked( StatifyBlacklist::$options['ip']['active'], 1 ); ?>>
-					<p class="description">
-						<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'Cron execution is not possible for IP filter, because IP addresses are not stored.', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_ip"><?php esc_html_e( 'IP filter', 'statify-blacklist' ); ?></label>:
-				</th>
-				<td>
-					<textarea cols="40" rows="5" name="statifyblacklist[ip][blacklist]" id="statify-blacklist_ip"><?php
-					if ( empty( $statifyblacklist_update_result['ip'] ) ) {
-						print esc_html( implode( "\r\n", StatifyBlacklist::$options['ip']['blacklist'] ) );
-					} else {
-						print esc_html( implode( "\r\n", $statifyblacklist_update_result['ip']['sanitized'] ) );
-					}
-					?></textarea>
-
-					<p class="description">
-						<?php esc_html_e( 'Add one IP address or range per line, e.g.', 'statify-blacklist' ); ?>
-						127.0.0.1, 192.168.123.0/24, 2001:db8:a0b:12f0::1/64
-					</p>
-				</td>
-			</tr>
-			</tbody>
-		</table>
-
-		<h2><?php esc_html_e( 'User agent filter', 'statify-blacklist' ); ?></h2>
-
-		<table class="form-table">
-			<tbody>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_active_ua">
-						<?php esc_html_e( 'Activate live filter', 'statify-blacklist' ); ?>
-					</label>
-				</th>
-				<td>
-					<input type="checkbox" name="statifyblacklist[ua][active]" id="statify-blacklist_active_ua"
-						   value="1" <?php checked( StatifyBlacklist::$options['ua']['active'], 1 ); ?>>
-					<p class="description">
-						<?php esc_html_e( 'Filter at time of tracking, before anything is stored', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'Cron execution is not possible for user agent filter, because the user agent is stored.', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_ua_regexp"><?php esc_html_e( 'Matching method', 'statify-blacklist' ); ?></label>
-				</th>
-				<td>
-					<select name="statifyblacklist[ua][regexp]" id="statify-blacklist_ua_regexp">
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_NORMAL ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_NORMAL ); ?>>
-							<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_KEYWORD ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_KEYWORD ); ?>>
-							<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_REGEX ); ?>>
-							<?php esc_html_e( 'RegEx case-sensitive', 'statify-blacklist' ); ?>
-						</option>
-						<option value="<?php print esc_attr( StatifyBlacklist::MODE_REGEX_CI ); ?>" <?php selected( StatifyBlacklist::$options['ua']['regexp'], StatifyBlacklist::MODE_REGEX_CI ); ?>>
-							<?php esc_html_e( 'RegEx case-insensitive', 'statify-blacklist' ); ?>
-						</option>
-					</select>
-
-					<p class="description">
-						<?php esc_html_e( 'Exact', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match only given user agents', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'Keyword', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match every referer that contains one of the keywords', 'statify-blacklist' ); ?>
-						<br>
-						<?php esc_html_e( 'RegEx', 'statify-blacklist' ); ?> - <?php esc_html_e( 'Match user agent by regular expression', 'statify-blacklist' ); ?>
-					</p>
-				</td>
-			</tr>
-			<tr>
-				<th scope="row">
-					<label for="statify-blacklist_ua"><?php esc_html_e( 'User agent filter', 'statify-blacklist' ); ?></label>:
-				</th>
-				<td>
-					<textarea cols="40" rows="5" name="statifyblacklist[ua][blacklist]" id="statify-blacklist_ua"><?php
-					if ( empty( $statifyblacklist_update_result['ua'] ) ) {
-						print esc_html( implode( "\r\n", array_keys( StatifyBlacklist::$options['ua']['blacklist'] ) ) );
-					} else {
-						print esc_html( implode( "\r\n", array_keys( $statifyblacklist_update_result['ua']['sanitized'] ) ) );
-					}
-					?></textarea>
-
-					<p class="description">
-						<?php esc_html_e( 'Add one user agent string per line, e.g.', 'statify-blacklist' ); ?>
-						MyBot/1.23
-					</p>
-				</td>
-			</tr>
-			</tbody>
-		</table>
-
-		<p class="submit">
-			<input class="button-primary" type="submit" name="submit" value="<?php esc_html_e( 'Save Changes', 'statify-blacklist' ); ?>">
-			<hr>
-			<input class="button-secondary" type="submit" name="cleanUp"
-				   value="<?php esc_html_e( 'CleanUp Database', 'statify-blacklist' ); ?>"
-				   onclick="return confirm('Do you really want to apply filters to database? This cannot be undone.');">
-			<br>
-			<p class="description">
-				<?php esc_html_e( 'Applies referer and target filter (even if disabled) to data stored in database.', 'statify-blacklist' ); ?>
-				<em><?php esc_html_e( 'This cannot be undone!', 'statify-blacklist' ); ?></em>
-			</p>
-		</p>
-	</form>
-</div>