Validate listing's description field for prohibited words

I have a PHP script (Wordpress plugin) which attempts to offer a workaround for missing feature Blocklist for moderating listings by blocking Wordpress web forms data submission/update, in case form data contains blocklisted/blacklisted phrases (defined in a text file).

But HivePress at /account/listings/1234/ still reports “Changes have been saved.” (despite the listing was not updated and a browser dev. console shows XHR request forbidden 403. /submit-listing/details/ just reloads/clears the form without bothering to explain what happened.

Please what to replace by what so the user is informed that their submission (no matter if new or update) failed. Maybe ideally mentioning what was the blocked string (it may contain any character incl. ‘ “ ; fancy text: Lᴜᴄʏ).

Following PHP code may by used as a WP plugin /wp-content/plugins/global-form-blocker/global-form-blocker.php while the blocklist would be at /wp-content/plugins/global-form-blocker/blocked-phrases.txt (one string per line)

<?php
/*
Plugin Name: Global Form Blocker
Description: Avoid any web form submission if it contains words/strings/phrases contained in the file /wp-content/plugins/global-form-blocker/blocked-phrases.txt
Version: 1.0
*/

if (!defined('ABSPATH')) exit;

// 1. Path to file
$file = __DIR__ . '/blocked-phrases.txt';

// 2. Check File Existence Immediately
if (!file_exists($file)) {
    // Force error for ALL contexts
    if (defined('DOING_AJAX') && DOING_AJAX) {
        wp_send_json_error(['message' => 'CRITICAL ERROR: Blocklist file blocked-phrases.txt of a form input filtering plugin is missing.']);
    } else {
        die('<h1>CRITICAL ERROR</h1><p>Blocklist file blocked-phrases.txt of a form input filtering plugin is missing.</p>');
    }
}

// 3. Load Phrases
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$phrases = array_map('trim', $lines);

// 4. Main Check Logic
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $input_data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
    
    $blocked = false;
    $reason = '';

    foreach ($phrases as $phrase) {
        if (empty($phrase)) continue;

        // ESCAPE the phrase for regex to handle quotes, slashes, etc.
        // We do NOT use \b (word boundary) because it breaks on quotes like s'pam
        // We just search for the exact escaped string
        $escaped_phrase = preg_quote($phrase, '/');
        
        // Case insensitive search
        if (preg_match('/' . $escaped_phrase . '/i', $input_data)) {
            $blocked = true;
            $reason = "Blocked phrase detected: " . esc_html($phrase);
            break;
        }
    }

    if ($blocked) {
        // FORCE STOP AND RETURN ERROR
        if (defined('DOING_AJAX') && DOING_AJAX) {
            // HIVEPRESS SPECIFIC FIX:
            // HivePress expects a specific JSON structure. 
            // We return 'success' => false and put the message in 'data' => 'message'
            wp_send_json_error([
                'message' => $reason,
                'errors' => ['_general' => $reason] // HivePress looks here sometimes
            ]);
            exit; // Hard exit
        }

        // Non-AJAX fallback
        if (ob_get_level()) ob_end_clean();
        header('HTTP/1.1 403 Forbidden');
        die('<h1>Submission Blocked</h1><p>' . $reason . '</p>');
    }
}

// 5. HIVEPRESS SPECIFIC HOOK - HARD STOP WITH 403
add_filter('hivepress/v1/forms/*/submit/response', function($response, $form) {
    $file = __DIR__ . '/blocked-phrases.txt';
    
    // 1. Check File
    if (!file_exists($file)) {
        status_header(403);
        header('Content-Type: application/json');
        echo json_encode([
            'success' => false,
            'message' => 'CRITICAL ERROR: Blocklist file missing.',
            'errors' => ['_general' => 'CRITICAL ERROR: Blocklist file missing.']
        ]);
        exit;
    }

    $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    $phrases = array_map('trim', $lines);
    $input_data = json_encode($_POST, JSON_UNESCAPED_UNICODE);

    foreach ($phrases as $phrase) {
        if (empty($phrase)) continue;
        $escaped = preg_quote($phrase, '/');
        
        if (preg_match('/' . $escaped . '/i', $input_data)) {
            // 2. FORCE 403 HEADER AND EXIT
            status_header(403);
            header('Content-Type: application/json');
            
            // Manually build JSON to ensure no escaping issues with quotes
            $error_msg = 'BLOCKED: Your submission contains restricted content: "' . $phrase . '"';
            
            echo json_encode([
                'success' => false,
                'message' => $error_msg,
                'errors' => [
                    '_general' => $error_msg,
                    'form' => $error_msg
                ]
            ]);
            exit; // CRITICAL: Stop execution immediately
        }
    }

    return $response;
}, 1, 2);

If any donation is needed, please forward your payment address and amount via PM.

Hi @obtrusive170,

I asked the HivePress AI to assist with returning an error message upon failed submission.

I haven’t tested the following code, but hopefully, it at least points you in the right direction:

<?php
/*
Plugin Name: HivePress Form Phrase Blocker
Description: Block form submissions containing disallowed phrases and inform users
Version: 1.0
*/

if (!defined('ABSPATH')) exit;

$blocked_phrases_file = __DIR__ . '/blocked-phrases.txt';

// Load blocked phrases
function get_blocked_phrases() {
    global $blocked_phrases_file;
    
    if (!file_exists($blocked_phrases_file)) {
        return [];
    }
    
    $lines = file($blocked_phrases_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    return array_map('trim', $lines);
}

// Check for blocked phrases
function check_for_blocked_phrases($input_data) {
    $phrases = get_blocked_phrases();
    
    foreach ($phrases as $phrase) {
        if (empty($phrase)) continue;
        
        $escaped = preg_quote($phrase, '/');
        if (preg_match('/' . $escaped . '/i', $input_data)) {
            return $phrase;
        }
    }
    
    return false;
}

// HivePress form submission filter
add_filter(
    'hivepress/v1/forms/listing_submit/errors',
    function($errors, $form) {
        $input_data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
        $blocked_phrase = check_for_blocked_phrases($input_data);
        
        if ($blocked_phrase) {
            $errors[] = sprintf(
                __('Your submission was denied because it contains restricted content: "%s". Please review your submission and try again.', 'hivepress'),
                esc_html($blocked_phrase)
            );
        }
        
        return $errors;
    },
    10,
    2
);

// Also apply to listing updates
add_filter(
    'hivepress/v1/forms/listing_update/errors',
    function($errors, $form) {
        $input_data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
        $blocked_phrase = check_for_blocked_phrases($input_data);
        
        if ($blocked_phrase) {
            $errors[] = sprintf(
                __('Your submission was denied because it contains restricted content: "%s". Please review your submission and try again.', 'hivepress'),
                esc_html($blocked_phrase)
            );
        }
        
        return $errors;
    },
    10,
    2
);

// Apply to messages
add_filter(
    'hivepress/v1/forms/message_submit/errors',
    function($errors, $form) {
        $input_data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
        $blocked_phrase = check_for_blocked_phrases($input_data);
        
        if ($blocked_phrase) {
            $errors[] = sprintf(
                __('Your message was not sent because it contains restricted content: "%s". Please review and try again.', 'hivepress'),
                esc_html($blocked_phrase)
            );
        }
        
        return $errors;
    },
    10,
    2
);

I hope this helps!

Cheers,
Chris :victory_hand:

No Chris, it does not work (does not show any error when blocked phrase is submit, just “Changes have been saved.” which is not true). Please DM me if possible, if you want me to continue providing me with untested code (i do not see a DM option on your profile), otherwise thanks for your attempt.

Hi,

The solution is quite simple. You can use the hivepress/v1/forms/listing_submit/errors filter for this: Filter: hivepress/v1/forms/{form_name}/errors | HivePress Hook Reference.

The idea is to check if the description field contains any of the prohibited words, and if so, add a custom error message to the $errors array that the filter receives and returns.

Hope this helps

Yes, that was it Kseniia. But I would not be able to apply your suggestion without knowing about AI linked from the Support menu item on the HivePress.io ( HivePress AI Assistant )

My request to AI:

Please provide me with an example on how to use filter hivepress/v1/forms/{form_name}/errors to check listing_update form input against the list of hundreds of forbidden strings (these strings may contain quotation marks and special characters and ideally should be supplied in a file, one string per line.

Response (in my own words):

Use WP plugin Code Snippets to create new PHP snippet, pasting:

add_filter(
    'hivepress/v1/forms/listing_update/errors',
    function( $errors, $form ) {
        // Load forbidden strings from a file
        $forbidden_file = get_stylesheet_directory() . '/forbidden-strings.txt';
        
        if ( ! file_exists( $forbidden_file ) ) {
            return $errors;
        }
        
        // Read the file and get forbidden strings
        $forbidden_strings = file( $forbidden_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
        
        if ( ! $forbidden_strings ) {
            return $errors;
        }
        
        // Get form values to check
        $values = $form->get_values();
        $title = isset( $values['title'] ) ? $values['title'] : '';
        $description = isset( $values['description'] ) ? $values['description'] : '';
        
        // Combine fields to check
        $content_to_check = $title . ' ' . $description;
        
        // Convert to lowercase for case-insensitive matching
        $content_lower = strtolower( $content_to_check );
        
        // Check against forbidden strings
        foreach ( $forbidden_strings as $forbidden ) {
            $forbidden_lower = strtolower( trim( $forbidden ) );
            
            if ( ! empty( $forbidden_lower ) && strpos( $content_lower, $forbidden_lower ) !== false ) {
                $errors[] = 'Your listing contains forbidden content: "' . esc_html( $forbidden ) . '"';
                break; // Stop after first match
            }
        }
        
        return $errors;
    },
    1000,
    2
);

then upload your blocklist (one blocked string per line) to /wp-content/themes/listinghive/forbidden-strings.txt (change “listinghive” to your theme name).

It started working on the listing addition form.

Note that

  1. it may not be optimal to disclose the blocked phrase : "' . esc_html( $forbidden ) . '" to the user (especially in case listings does not require admin approval), because users can then workaround blocked string easily.

  2. case sensitivity : The example uses case-insensitive matching. Remove strtolower() if you need exact case matching

  3. partial matching : The code uses strpos() which finds partial matches. If you need exact word matching, use preg_match() instead.

Later I have enhanced the script using your AI to have more features like also exact match and regular expression blocklist and permanent + temporary banning of a user. Code of a new script: Blocklist for moderating listings - #10 by obtrusive170

Thanks for the solution.

It can be helpful for other users.