Add content filter to block ads, press releases, and promotional content

Two-tier filtering during article fetch: keyword/pattern rules as a fast
first pass, then optional LLM classification for borderline cases via
OpenRouter. Configurable sensitivity (strict/moderate/lenient), editable
keyword lists, blocked domain list, and admin stats display. Includes
30 unit tests covering domain blocking, title/body keywords, URL paths,
edge cases, and prompt structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Terence Ong
2026-04-06 14:14:16 +07:00
parent 849ffa1260
commit e6887e810f
6 changed files with 1275 additions and 6 deletions

View File

@@ -84,6 +84,35 @@ if ( ! function_exists( 'get_option' ) ) {
return isset( $_test_options[ $option ] ) ? $_test_options[ $option ] : $default;
}
}
if ( ! function_exists( 'update_option' ) ) {
function update_option( $option, $value, $autoload = null ) {
global $_test_options;
$_test_options[ $option ] = $value;
return true;
}
}
if ( ! function_exists( 'current_time' ) ) {
function current_time( $type, $gmt = 0 ) {
if ( 'mysql' === $type ) {
return gmdate( 'Y-m-d H:i:s' );
}
return time();
}
}
if ( ! function_exists( 'is_wp_error' ) ) {
function is_wp_error( $thing ) {
return ( $thing instanceof WP_Error );
}
}
if ( ! function_exists( 'wp_strip_all_tags' ) ) {
function wp_strip_all_tags( $string, $remove_breaks = false ) {
$string = strip_tags( $string );
if ( $remove_breaks ) {
$string = preg_replace( '/[\r\n\t ]+/', ' ', $string );
}
return trim( $string );
}
}
if ( ! function_exists( 'add_filter' ) ) {
function add_filter( $tag, $callback, $priority = 10, $accepted_args = 1 ) {
return true;
@@ -110,6 +139,11 @@ if ( ! function_exists( 'esc_attr' ) ) {
return htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' );
}
}
if ( ! function_exists( 'esc_textarea' ) ) {
function esc_textarea( $text ) {
return htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' );
}
}
if ( ! function_exists( '__' ) ) {
function __( $text, $domain = 'default' ) {
return $text;
@@ -150,6 +184,10 @@ if ( ! class_exists( 'WP_Error' ) ) {
}
}
// ── Load plugin classes for testing ───────────────────────────────────
// Content filter can be loaded standalone (no WordPress runtime needed).
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-content-filter.php';
// Stub WP_REST_Request for REST API tests.
if ( ! class_exists( 'WP_REST_Request' ) ) {
class WP_REST_Request {