tearoom1 / uniform-spam-words
Simple spam guard for 'mzur/kirby-uniform' & Kirby
Fund package maintenance!
Buy Me A Coffee
Github Sponsors
Installs: 156
Dependents: 1
Suggesters: 0
Security: 0
Stars: 4
Watchers: 2
Forks: 0
Open Issues: 0
Type:kirby-plugin
pkg:composer/tearoom1/uniform-spam-words
Requires
- getkirby/composer-installer: ^1.1
- mzur/kirby-uniform: ^5.0
Requires (Dev)
- getkirby/cms: ^4.1.0
- phpunit/phpunit: ^11.4.3
README
This plugin implements a simple spam words guard for Martin Zurowietz' kirby-uniform plugin for Kirby.
Getting started
Use one of the following methods to install & use tearoom1/uniform-spam-words:
Git submodule
If you know your way around Git, you can download this plugin as a submodule:
git submodule add https://github.com/tearoom1/uniform-spam-words.git site/plugins/uniform-spam-words
Composer
composer require tearoom1/uniform-spam-words
Clone or download
- Clone or download this repository from github: https://github.com/tearoom1/uniform-spam-words.git
- Unzip / Move the folder to
site/plugins.
Usage
Controller
To use the plugin, you have to enable the guard by calling spamWordsGuard() on the $form object.
For more information, check out the kirby-uniform docs on its magic methods:
$form = new Form(); if ($kirby->request()->is('POST')) { # Call security $form->spamWordsGuard(); # .. more code }
How it works
- Checks message length constraints
- Checks word count constraints
- Validates regex pattern (if configured)
- Runs custom validator (if configured)
- If addresses found (>= minAddresses), calculates spam score from keywords
- Rejects if thresholds exceeded
Configuration
The plugin comes with a few lists of words and phrases that are used to build a spam score. Thus the plugin should work out of the box.
You may change certain options in your global config.php
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
| Validation Options | |||
minLength |
int|null | null |
Minimum character length required for the message. Set to null to disable. |
maxLength |
int|null | null |
Maximum character length allowed for the message. Set to null to disable. |
minWords |
int|null | null |
Minimum word count required for the message. Set to null to disable. |
maxWords |
int|null | null |
Maximum word count allowed for the message. Set to null to disable. |
regexMatch |
string|null | null |
Optional regex pattern that the message must match (e.g., /^[a-zA-Z0-9\s]+$/). Set to null to disable. |
customValidator |
callable|null | null |
Custom validation callback that receives the message and returns true (valid) or false (invalid). Set to null to disable. |
| Spam Detection Options | |||
minAddresses |
int | 1 |
Minimum number of addresses required to trigger spam word checking. Set to 0 to always check. |
addressThreshold |
int | 2 |
Number of addresses (links/emails) allowed before triggering spam check. |
addressesWeight |
int | 1 |
Weight multiplier for each address (link/email) found. Each address contributes addressesWeight points to the total spam score. |
spamThreshold |
int | 8 |
Spam score threshold for rejection. Higher values are more lenient. |
useWordLists |
bool | true |
Use built-in spam word lists. Set to false to disable built-in lists. |
wordListPaths |
string|array|null | null |
Custom paths to word list files or directories. Always additive (added to built-in lists if enabled). Can be a single path (string) or multiple paths (array). |
spamWords |
array | [] |
Custom spam words with weights. Format: [weight => ['word1', 'word2']]. Higher weight = stronger spam signal. |
| Other Options | |||
enabled |
bool | true |
Enable or disable the plugin globally. |
fields |
array | ['message'] |
Form fields to check for spam. Specify multiple fields to combine them for spam checking (e.g., ['message', 'subject', 'body']). |
wordListCache |
bool | true |
Enable caching of word lists. Set to false to reload word lists on every request (useful for development). |
silentReject |
bool | false |
Reject spam without showing error messages (returns a space character). |
debug |
bool | false |
Enable debug logging. Logs ALL validation attempts with reason, metrics, form data, checked fields, anonymized IP, and timestamp. |
debugLogFile |
string|null | null |
Path to debug log file. Defaults to uniform-spam-words.log in the kirby logs directory (e.g. site/logs/uniform-spam-words.log). |
attachDebugInfo |
bool | false |
Attach spam check data to Form object (accessible via $form->data('spam_words_guard_info')) for use in email templates. Be aware that by default all fields are sent in uniform emails. |
Message Rejection & Spam Score
- A message is rejected if the spam score is higher than the threshold. Or if the number of addresses are higher than twice the
addressThreshold. - A messages is soft rejected (different message) if the number of addresses are higher than
addressThresholdbut the spam score is lower than the threshold. - The spam score is calculated by adding the weights of all matched words.
Plus the number of addresses found times the
addressesWeight.
Common Use Cases
Full Configuration Example
return [ 'tearoom1.uniform-spam-words' => [ 'enabled' => true, // enable the plugin, default true 'minLength' => 10, // the minimum length of the message, default null (disabled) 'maxLength' => 500, // the maximum length of the message, default null (disabled) 'minWords' => 3, // the minimum number of words in the message, default null (disabled) 'maxWords' => null, // the maximum number of words in the message, default null (disabled) 'regexMatch' => null, // the regex pattern to match against the message, default null (disabled) 'customValidator' => null, // custom validation callback, default null (disabled) 'minAddresses' => 0, // the minimum number of addresses like links and emails that are needed to check for spam, default 1 'addressThreshold' => 2, // the number of addresses like links and emails that are allowed, default 2 'addressesWeight' => 2, // the weight multiplier for each address (link/email), default 1 'spamThreshold' => 10, // the threshold for the spam score, default 8 'useWordLists' => true, // Use the default word lists, default true 'spamWords' => [ // define your own spam words, the key number defines the weight of the words 1 => ['promotion', 'free'], // weight 1, increases spam likelihood only a little 6 => ['seo', 'marketing'], // weight 6, increases spam likelihood a lot // Note: If a word exists in both built-in/file lists and spamWords config, // the config weight will override the file weight (no double counting) ], 'silentReject' => false, // Reject spam without showing error messages (returns a space as error message), default false 'debug' => true, // Enable debug logging, default false 'debugLogFile' => 'site/logs/my_custom_logfile.log', // Path to debug log file, default null (uses Kirby's log directory) ], ];
Contact Form with Basic Spam Protection
return [ 'tearoom1.uniform-spam-words' => [ 'minLength' => 10, 'minWords' => 3, 'addressThreshold' => 2, 'spamThreshold' => 8, ], ];
Strict Comment System
return [ 'tearoom1.uniform-spam-words' => [ 'minLength' => 20, 'maxLength' => 1000, 'minWords' => 5, 'maxWords' => 200, 'addressThreshold' => 1, // Only allow 1 link 'spamThreshold' => 5, // Stricter threshold ], ];
Multi-Field Spam Checking
return [ 'tearoom1.uniform-spam-words' => [ 'fields' => ['subject', 'message', 'company'], // Check multiple fields 'spamThreshold' => 10, ], ];
Increase Weight for Links/Emails
return [ 'tearoom1.uniform-spam-words' => [ 'addressesWeight' => 3, // Each link/email counts as 3 points instead of 1 'spamThreshold' => 12, // Adjust threshold accordingly ], ];
Development Mode (No Caching)
return [ 'tearoom1.uniform-spam-words' => [ 'wordListCache' => false, // Disable caching for testing 'debug' => true, // Enable detailed logging 'debugLogFile' => '/path/to/spam-debug.log', ], ];
Custom Validation with Industry-Specific Terms
return [ 'tearoom1.uniform-spam-words' => [ 'spamWords' => [ 3 => ['crypto', 'nft', 'investment'], 5 => ['guaranteed', 'risk-free'], 8 => ['get rich quick'], ], 'customValidator' => function($message) { // Reject if message contains phone numbers return !preg_match('/\d{3}[-.\s]?\d{3}[-.\s]?\d{4}/', $message); }, ], ];
Debug Mode for Tuning
return [ 'tearoom1.uniform-spam-words' => [ 'debug' => true, 'debugLogFile' => '/path/to/spam-debug.log', ], ];
Debug logging captures:
- ✅ All rejections (regex, length, words, custom validator, spam detection)
- ✅ Successful validations (messages that pass all checks)
- ✅ Rejection reason and relevant metrics for each attempt
- ✅ Form data - All submitted form fields (truncated to 100 chars, excludes passwords/tokens)
- ✅ Checked fields - Which fields were combined for spam checking
- ✅ Anonymized IP address (GDPR compliant) and timestamp for tracking
Example log entry:
{
"status": "rejected",
"reason": "spam_score",
"checked_fields": ["message"],
"message_length": 150,
"word_count": 25,
"address_count": 3,
"addresses_weight": 1,
"address_score": 3,
"spam_score": 12,
"total_score": 15,
"matched_words": {
"seo": {
"weight": 6,
"count": 2,
"subtotal": 12
}
},
"thresholds": {
"spam": 8,
"address": 2
},
"form_data": {
"name": "John Doe",
"email": "spam@example.com",
"message": "Buy now! Amazing SEO services..."
}
}
IP Anonymization (GDPR Compliant):
- IPv4: Last octet masked (e.g.,
192.168.1.123→192.168.1.0) - IPv6: Last 4 segments masked (e.g.,
2001:db8:85a3:8d3:1319:8a2e:370:7348→2001:db8:85a3:8d3:0000:0000:0000:0000)
Custom Word Lists
You can use your own spam word list files instead of or in addition to the built-in lists.
File Format
Word list files should be plain text files (.txt) with:
- One term (single word or combination) per line
- Weight encoded in filename as
_n.txtwherenis the weight (1-9) - Example:
custom_spam_5.txt(weight 5)
Example file content (my_spam_words_7.txt):
casino
lottery
winner
prize
Configuration Options
Single file:
'tearoom1.uniform-spam-words' => [ 'wordListPaths' => '/path/to/my_spam_words_7.txt', ],
Multiple files:
'tearoom1.uniform-spam-words' => [ 'wordListPaths' => [ '/path/to/my_spam_words_5.txt', '/path/to/another_list_8.txt', ], ],
Directory (loads all .txt files):
'tearoom1.uniform-spam-words' => [ 'wordListPaths' => '/path/to/my-spam-lists', ],
Multiple directories:
'tearoom1.uniform-spam-words' => [ 'wordListPaths' => [ '/path/to/spam-lists', '/path/to/more-lists', ], ],
Combining with Built-in Lists
Custom word lists are added to the built-in lists (if enabled). The options work independently:
Built-in + Custom (default):
'tearoom1.uniform-spam-words' => [ 'useWordLists' => true, // Use built-in lists (default) 'wordListPaths' => '/path/to/my-lists', // Add custom lists ],
Notes
- Custom word lists are cached for 24 hours (same as built-in lists)
- Weight determines spam score contribution (higher = more spam-like)
- Words are case-insensitive
- Empty lines are skipped
- Files must be readable by the web server
Supported Languages
The plugin includes built-in translations for error messages in the following languages:
- English (en) - Default language
- German (de) - Deutsche Übersetzungen
- French (fr) - Traductions françaises
The plugin automatically uses the appropriate language based on your Kirby site's language configuration.
Custom Error Messages
Single-language sites: Define custom messages in config.php:
'tearoom1.uniform-spam-words' => [ 'msg' => [ 'rejected' => 'Your custom spam rejection message', 'soft-reject' => 'Your custom soft rejection message', 'regex-mismatch' => 'Message does not match the required pattern.', 'too-short' => 'Message is too short.', 'too-long' => 'Message is too long.', 'too-few-words' => 'Message contains too few words.', 'too-many-words' => 'Message contains too many words.', 'custom-validation-failed' => 'Message failed custom validation.', ], ],
Multi-language sites: Define translations in your language files (site/languages/*.php):
return [ 'code' => 'en', 'name' => 'English', 'translations' => [ 'tearoom1.uniform-spam-words.msg.rejected' => 'Your custom spam rejection message', 'tearoom1.uniform-spam-words.msg.soft-reject' => 'Your custom soft rejection message', 'tearoom1.uniform-spam-words.msg.regex-mismatch' => 'Message does not match the required pattern.', 'tearoom1.uniform-spam-words.msg.too-short' => 'Message is too short.', 'tearoom1.uniform-spam-words.msg.too-long' => 'Message is too long.', 'tearoom1.uniform-spam-words.msg.too-few-words' => 'Message contains too few words.', 'tearoom1.uniform-spam-words.msg.too-many-words' => 'Message contains too many words.', 'tearoom1.uniform-spam-words.msg.custom-validation-failed' => 'Message failed custom validation.', ], ];
License
This plugin is licensed under the MIT License
Credits
- Developed by Mathis Koblin