PHPackages                             tearoom1/uniform-spam-words - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. tearoom1/uniform-spam-words

ActiveKirby-plugin[Utility &amp; Helpers](/categories/utility)

tearoom1/uniform-spam-words
===========================

Simple spam guard for 'mzur/kirby-uniform' &amp; Kirby

1.8.3(6mo ago)41691MITPHPCI passing

Since Nov 17Pushed 6mo ago2 watchersCompare

[ Source](https://github.com/tearoom1/uniform-spam-words)[ Packagist](https://packagist.org/packages/tearoom1/uniform-spam-words)[ Docs](https://github.com/tearoom1/uniform-spam-words)[ Fund](https://buymeacoffee.com/tearoom1)[ Fund](https://github.com/sponsors/tearoom1)[ RSS](/packages/tearoom1-uniform-spam-words/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (4)Versions (30)Used By (1)

Spam Words Guard for Kirby Uniform
==================================

[](#spam-words-guard-for-kirby-uniform)

This plugin implements a simple spam words guard for Martin Zurowietz' [`kirby-uniform`](https://github.com/mzur/kirby-uniform) plugin for Kirby.

Getting started
---------------

[](#getting-started)

Use one of the following methods to install &amp; use `tearoom1/uniform-spam-words`:

### Git submodule

[](#git-submodule)

If you know your way around Git, you can download this plugin as a [submodule](https://github.com/blog/2104-working-with-submodules):

```
git submodule add https://github.com/tearoom1/uniform-spam-words.git site/plugins/uniform-spam-words

```

### Composer

[](#composer)

```
composer require tearoom1/uniform-spam-words

```

### Clone or download

[](#clone-or-download)

1. Clone or download this repository from github:
2. Unzip / Move the folder to `site/plugins`.

Usage
-----

[](#usage)

### Controller

[](#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](https://kirby-uniform.readthedocs.io/en/latest/guards/guards/#magic-methods):

```
$form = new Form();

if ($kirby->request()->is('POST')) {
    # Call security
    $form->spamWordsGuard();

    # .. more code
}
```

### How it works

[](#how-it-works)

1. Checks message length constraints
2. Checks word count constraints
3. Validates regex pattern (if configured)
4. Runs custom validator (if configured)
5. If addresses found (&gt;= minAddresses), calculates spam score from keywords
6. Rejects if thresholds exceeded

Configuration
-------------

[](#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

[](#configuration-options)

OptionTypeDefaultDescription**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 &amp; Spam Score

[](#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 `addressThreshold` but 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

[](#common-use-cases)

#### Full Configuration Example

[](#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

[](#contact-form-with-basic-spam-protection)

```
return [
    'tearoom1.uniform-spam-words' => [
        'minLength' => 10,
        'minWords' => 3,
        'addressThreshold' => 2,
        'spamThreshold' => 8,
    ],
];
```

#### Strict Comment System

[](#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

[](#multi-field-spam-checking)

```
return [
    'tearoom1.uniform-spam-words' => [
        'fields' => ['subject', 'message', 'company'], // Check multiple fields
        'spamThreshold' => 10,
    ],
];
```

#### Increase Weight for Links/Emails

[](#increase-weight-for-linksemails)

```
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)

[](#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

[](#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

[](#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
-----------------

[](#custom-word-lists)

You can use your own spam word list files instead of or in addition to the built-in lists.

### File Format

[](#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.txt` where `n` is 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

[](#configuration-options-1)

**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

[](#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

[](#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
-------------------

[](#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

[](#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
-------

[](#license)

This plugin is licensed under the [MIT License](LICENSE)

Credits
-------

[](#credits)

- Developed by Mathis Koblin

[!["Buy Me A Coffee"](https://camo.githubusercontent.com/9f44ce2dc3b3eecdd02598900866ffc518801df1932849703dae1e5ce5031070/68747470733a2f2f7777772e6275796d6561636f666665652e636f6d2f6173736574732f696d672f637573746f6d5f696d616765732f6f72616e67655f696d672e706e67)](https://coff.ee/tearoom1)

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance68

Regular maintenance activity

Popularity16

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~13 days

Recently: every ~0 days

Total

29

Last Release

184d ago

Major Versions

0.1.0 → 1.1.42025-07-21

### Community

Maintainers

![](https://www.gravatar.com/avatar/36dc71519a44dd7f6f97f46e423d21fee308f57d1496ad6f2477392111c086ca?d=identicon)[tearoom1](/maintainers/tearoom1)

---

Top Contributors

[![morja](https://avatars.githubusercontent.com/u/29610198?v=4)](https://github.com/morja "morja (95 commits)")

---

Tags

contact-formkirbykirby-cmskirby-pluginkirby4kirby5spam-detectionformkirby

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/tearoom1-uniform-spam-words/health.svg)

```
[![Health](https://phpackages.com/badges/tearoom1-uniform-spam-words/health.svg)](https://phpackages.com/packages/tearoom1-uniform-spam-words)
```

###  Alternatives

[mzur/kirby-uniform

A versatile Kirby plugin to handle web form actions.

26068.3k13](/packages/mzur-kirby-uniform)[oblik/kirby-link-field

Kirby 4 field for all types of links.

7650.6k2](/packages/oblik-kirby-link-field)[belugadigital/kirby-navigation

Kirby 5 field for hierarchical menus with drag &amp; drop level indentation.

8713.4k](/packages/belugadigital-kirby-navigation)[bnomei/kirby3-dotenv

Kirby Plugin for environment variables from .env

4144.1k1](/packages/bnomei-kirby3-dotenv)[tobimori/kirby-icon-field

A simple Icon field plugin for Kirby CMS

5117.1k1](/packages/tobimori-kirby-icon-field)[tobimori/kirby-tailwind-merge

Tailwind Merge for Kirby CMS

276.3k](/packages/tobimori-kirby-tailwind-merge)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
