{"id":36373017,"url":"https://github.com/overtrue/laravel-text-guard","last_synced_at":"2026-01-11T14:02:47.926Z","repository":{"id":313755484,"uuid":"1052437996","full_name":"overtrue/laravel-text-guard","owner":"overtrue","description":"A powerful string sanitization and normalization tool for Laravel that can be used as validation rules or standalone utility. / 可作 Rule 也可单用的字符串清洗/规范化工具","archived":false,"fork":false,"pushed_at":"2025-11-21T09:06:20.000Z","size":73,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-21T11:10:37.585Z","etag":null,"topics":["laravel-package","sanitizer","text-filter","text-sanitizer"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/overtrue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":["overtrue"]}},"created_at":"2025-09-08T03:55:47.000Z","updated_at":"2025-11-21T09:06:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"bbb05fc8-6dd2-4219-bc04-947bd04f6bce","html_url":"https://github.com/overtrue/laravel-text-guard","commit_stats":null,"previous_names":["overtrue/laravel-text-guard"],"tags_count":3,"template":false,"template_full_name":"overtrue/laravel-package","purl":"pkg:github/overtrue/laravel-text-guard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/overtrue%2Flaravel-text-guard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/overtrue%2Flaravel-text-guard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/overtrue%2Flaravel-text-guard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/overtrue%2Flaravel-text-guard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/overtrue","download_url":"https://codeload.github.com/overtrue/laravel-text-guard/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/overtrue%2Flaravel-text-guard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28306985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T11:18:18.743Z","status":"ssl_error","status_checked_at":"2026-01-11T11:07:56.842Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["laravel-package","sanitizer","text-filter","text-sanitizer"],"created_at":"2026-01-11T14:02:47.861Z","updated_at":"2026-01-11T14:02:47.910Z","avatar_url":"https://github.com/overtrue.png","language":"PHP","funding_links":["https://github.com/sponsors/overtrue"],"categories":[],"sub_categories":[],"readme":"# Laravel TextGuard\n\nA powerful string sanitization and normalization tool for Laravel that can be used as validation rules or standalone utility.\n\n[中文文档](README.zh-CN.md) | [English Documentation](README.md)\n\n## Documentation\n\n- [Pipeline Guide](PIPELINE_GUIDE.md) - Complete guide to all pipeline steps\n- [Pipeline 指南](PIPELINE_GUIDE.zh-CN.md) - 所有 pipeline 步骤的完整指南\n\n## Installation\n\n```bash\ncomposer require overtrue/laravel-text-guard\n```\n\n## Publish Configuration\n\n```bash\nphp artisan vendor:publish --tag=text-guard-config\n```\n\n## Basic Usage\n\n### As a Utility\n\n```php\nuse Overtrue\\TextGuard\\TextGuard;\n\n// Use default 'safe' preset\n$clean = TextGuard::filter($dirty);\n\n// Use specified preset\n$clean = TextGuard::filter($dirty, 'username');\n\n// Override configuration\n$clean = TextGuard::filter($dirty, 'safe', [\n    'truncate_length' =\u003e ['max' =\u003e 100]\n]);\n```\n\n### As Validation Rules\n\n```php\nuse Overtrue\\TextGuard\\Rules\\Filtered;\nuse Overtrue\\TextGuard\\Rules\\Sanitized;\n\n// Filter then validate\n$validator = validator($data, [\n    'nickname' =\u003e [new Filtered('username')]\n]);\n\n// Validate visibility only\n$validator = validator($data, [\n    'content' =\u003e [new Sanitized(0.8, 1)]\n]);\n```\n\n### Model Auto-Filtering\n\nUse the `TextGuardable` trait to provide automatic filtering for model fields:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Overtrue\\TextGuard\\TextGuardable;\n\nclass User extends Model\n{\n    use TextGuardable;\n\n    protected $fillable = ['name', 'bio', 'description'];\n\n    // Method 1: Associative array (specify different presets)\n    protected $textGuardFields = [\n        'name' =\u003e 'username',        // Username uses stricter filtering\n        'bio' =\u003e 'safe',             // Bio uses safe filtering\n        'description' =\u003e 'rich_text' // Description allows rich text\n    ];\n\n    // Method 2: Indexed array (use default preset)\n    protected $textGuardFields = ['name', 'bio', 'description'];\n    protected $textGuardDefaultPreset = 'safe';\n\n    // Method 3: Mixed configuration (some fields use default, some specify preset)\n    protected $textGuardFields = [\n        'name',  // Use default preset\n        'bio' =\u003e 'safe',  // Specify preset\n        'description' =\u003e 'rich_text'  // Specify preset\n    ];\n    protected $textGuardDefaultPreset = 'username';\n}\n```\n\nWhen the model is saved, specified fields are automatically filtered:\n\n```php\n$user = new User();\n$user-\u003efill([\n    'name' =\u003e 'ＵｓｅｒＮａｍｅ１２３！！！',  // Full-width characters\n    'bio' =\u003e 'Normal text' . json_decode('\"\\u200B\"') . 'hidden content',  // Zero-width characters\n    'description' =\u003e '\u003cscript\u003ealert(\"XSS\")\u003c/script\u003e\u003cp\u003eNormal content\u003c/p\u003e',  // HTML\n]);\n$user-\u003esave();\n\n// After saving, data has been automatically filtered:\n// $user-\u003ename = 'UserName123!!!'  // Full-width to half-width\n// $user-\u003ebio = 'Normal texthidden content'  // Zero-width characters removed\n// $user-\u003edescription = '\u003cp\u003eNormal content\u003c/p\u003e'  // Dangerous tags removed, safe tags preserved\n```\n\n#### Dynamic Field Management\n\n```php\n$user = new User();\n\n\n// Manually filter fields\n$filtered = $user-\u003efilterField('bio', 'safe');\n\n// Get current configuration\n$fields = $user-\u003egetTextGuardFields(); // Returns filtering field configuration\n$fields = $user-\u003egetTextGuardFields(); // Returns field list\n```\n\n### FormRequest Integration\n\n```php\nclass UpdateProfileRequest extends FormRequest\n{\n    protected function prepareForValidation(): void\n    {\n        if ($this-\u003ehas('nickname')) {\n            $this-\u003emerge([\n                'nickname' =\u003e TextGuard::filter(\n                    (string)$this-\u003einput('nickname'),\n                    'username'\n                ),\n            ]);\n        }\n    }\n\n    public function rules(): array\n    {\n        return [\n            'nickname' =\u003e ['required', 'string', new Sanitized(0.9, 1)],\n            'bio' =\u003e ['nullable', new Filtered('safe', false)],\n        ];\n    }\n}\n```\n\n## Pipeline Usage\n\nLaravel TextGuard provides 14 built-in pipeline steps for text processing. Each pipeline step can be configured with specific parameters to meet different requirements.\n\n### Available Pipeline Steps\n\n#### Basic Text Processing\n- **`trim_whitespace`** - Remove leading/trailing whitespace (including full-width spaces)\n- **`collapse_spaces`** - Collapse multiple consecutive spaces into single space\n- **`remove_control_chars`** - Remove control characters while preserving newlines and tabs\n- **`remove_zero_width`** - Remove zero-width characters (U+200B..200D, U+FEFF)\n\n#### Unicode Processing\n- **`unicode_normalization`** - Unicode normalization (NFC, NFD, NFKC, NFKD)\n- **`fullwidth_to_halfwidth`** - Convert fullwidth characters to halfwidth\n- **`normalize_punctuations`** - Normalize punctuation based on locale (zh/en)\n\n#### HTML Processing\n- **`strip_html`** - Remove all HTML tags\n- **`html_decode`** - Decode HTML entities\n- **`whitelist_html`** - Keep only allowed HTML tags and attributes\n\n#### Character Filtering\n- **`character_whitelist`** - Keep only allowed character types (with emoji support)\n- **`collapse_repeated_marks`** - Limit repeated punctuation marks\n\n#### Length Control\n- **`visible_ratio_guard`** - Check visible character ratio\n- **`truncate_length`** - Truncate text to maximum length\n\n### Pipeline Configuration Examples\n\n#### Basic Text Cleaning\n```php\n'basic_clean' =\u003e [\n    'trim_whitespace' =\u003e true,\n    'collapse_spaces' =\u003e true,\n    'remove_control_chars' =\u003e true,\n    'remove_zero_width' =\u003e true,\n    'strip_html' =\u003e true,\n    'visible_ratio_guard' =\u003e ['min_ratio' =\u003e 0.6],\n    'truncate_length' =\u003e ['max' =\u003e 1000],\n],\n```\n\n#### Username Processing\n```php\n'username' =\u003e [\n    'trim_whitespace' =\u003e true,\n    'collapse_spaces' =\u003e true,\n    'remove_control_chars' =\u003e true,\n    'remove_zero_width' =\u003e true,\n    'unicode_normalization' =\u003e 'NFKC',\n    'fullwidth_to_halfwidth' =\u003e [\n        'ascii' =\u003e true,\n        'digits' =\u003e true,\n        'latin' =\u003e true,\n        'punct' =\u003e true,\n    ],\n    'normalize_punctuations' =\u003e 'en',\n    'strip_html' =\u003e true,\n    'collapse_repeated_marks' =\u003e [\n        'max_repeat' =\u003e 1,\n        'charset' =\u003e '_-.',\n    ],\n    'visible_ratio_guard' =\u003e ['min_ratio' =\u003e 0.9],\n    'truncate_length' =\u003e ['max' =\u003e 50],\n],\n```\n\n#### Rich Text Processing\n```php\n'rich_text' =\u003e [\n    'trim_whitespace' =\u003e true,\n    'remove_control_chars' =\u003e true,\n    'remove_zero_width' =\u003e true,\n    'unicode_normalization' =\u003e 'NFC',\n    'whitelist_html' =\u003e [\n        'tags' =\u003e ['p', 'b', 'i', 'u', 'a', 'ul', 'ol', 'li', 'code', 'pre', 'br', 'blockquote', 'h1', 'h2', 'h3'],\n        'attrs' =\u003e ['href', 'title', 'rel'],\n        'protocols' =\u003e ['http', 'https', 'mailto'],\n    ],\n    'visible_ratio_guard' =\u003e ['min_ratio' =\u003e 0.5],\n    'truncate_length' =\u003e ['max' =\u003e 20000],\n],\n```\n\n#### Nickname with Emoji Support\n```php\n'nickname' =\u003e [\n    'trim_whitespace' =\u003e true,\n    'collapse_spaces' =\u003e true,\n    'remove_control_chars' =\u003e true,\n    'remove_zero_width' =\u003e true,\n    'unicode_normalization' =\u003e 'NFKC',\n    'fullwidth_to_halfwidth' =\u003e [\n        'ascii' =\u003e true,\n        'digits' =\u003e true,\n        'latin' =\u003e true,\n        'punct' =\u003e false, // Preserve Chinese punctuation\n    ],\n    'html_decode' =\u003e true,\n    'strip_html' =\u003e true,\n    'character_whitelist' =\u003e [\n        'enabled' =\u003e true,\n        'allow_emoji' =\u003e true,\n        'allow_chinese_punctuation' =\u003e true,\n        'allow_english_punctuation' =\u003e true,\n        'emoji_ranges' =\u003e [\n            'emoticons' =\u003e true,\n            'misc_symbols' =\u003e true,\n            'transport_map' =\u003e true,\n            'misc_symbols_2' =\u003e true,\n            'dingbats' =\u003e true,\n        ],\n    ],\n    'visible_ratio_guard' =\u003e ['min_ratio' =\u003e 0.7],\n    'truncate_length' =\u003e ['max' =\u003e 30],\n],\n```\n\n### Pipeline Parameter Types\n\nThe system automatically passes configuration to constructors based on type:\n\n- **Boolean values** (`true`/`false`) → No parameter constructor: `new Class()`\n- **String values** (`'NFKC'`) → Single parameter constructor: `new Class('NFKC')`\n- **Array values** (`['max' =\u003e 100]`) → Array parameter constructor: `new Class(['max' =\u003e 100])`\n\n### Usage Tips\n\n1. **Order matters**: Pipeline steps are executed in configuration order\n2. **Basic cleanup first**: Start with `trim_whitespace`, `collapse_spaces`, `remove_control_chars`, `remove_zero_width`\n3. **Unicode processing**: Use `unicode_normalization` and `fullwidth_to_halfwidth` for international text\n4. **HTML handling**: Use `whitelist_html` for rich text, `strip_html` for plain text\n5. **Length control last**: Place `visible_ratio_guard` and `truncate_length` at the end\n\nFor detailed parameter information and more examples, see the [Pipeline Guide](PIPELINE_GUIDE.md).\n\n## Preset Configurations\n\n### Level-based Presets\n\n#### `safe` Preset\nDefault preset suitable for most normal text input fields. Includes basic text sanitization features.\n\n#### `strict` Preset\nMore restrictive filtering mode with stricter rules:\n- No emoji characters allowed\n- Converts all punctuation to half-width\n- Higher visible character ratio requirement (0.8)\n- Shorter text length limit (5000)\n\n### Function-specific Presets (Examples)\n\n#### `username` Preset\nSuitable for username input with stricter normalization.\n\n#### `nickname` Preset\nSuitable for user nicknames with emoji and Chinese punctuation support.\n\n#### `rich_text` Preset\nSuitable for rich text content, preserving safe HTML tags.\n\n## Extended Features\n\n### Register Custom Pipeline Steps\n\n```php\nuse Overtrue\\TextGuard\\TextGuard;\n\n// Register custom step\nTextGuard::registerPipelineStep('custom_step', YourCustomPipeline::class);\n\n// Use in preset\n$clean = TextGuard::filter($dirty, 'custom', [\n    'custom_step' =\u003e ['option' =\u003e 'value']\n]);\n```\n\n### Get Available Steps\n\n```php\n$availableSteps = TextGuard::getAvailableSteps();\n// Returns: ['trim_whitespace', 'collapse_spaces', 'remove_control_chars', ...]\n```\n\n### Create Custom Pipeline Steps\n\n```php\nuse Overtrue\\TextGuard\\Pipeline\\PipelineStep;\n\nclass CustomStep implements PipelineStep\n{\n    public function __construct(protected array $options = []) {}\n\n    public function __invoke(string $text): string\n    {\n        // Your custom logic\n        return $text;\n    }\n}\n```\n\n### Constructor Configuration\n\nAll Pipeline steps now use constructor configuration, following traditional OOP design:\n\n```php\n// config/text-guard.php\nreturn [\n    'pipeline_map' =\u003e [\n        // Simplified syntax: use class names directly\n        'trim_whitespace' =\u003e \\Overtrue\\TextGuard\\Pipeline\\TrimWhitespace::class,\n        'strip_html' =\u003e \\Overtrue\\TextGuard\\Pipeline\\StripHtml::class,\n    ],\n\n    'presets' =\u003e [\n        'safe' =\u003e [\n            // Boolean configuration: enable feature\n            'trim_whitespace' =\u003e true,\n\n            // String configuration: pass to constructor\n            'unicode_normalization' =\u003e 'NFKC',\n\n            // Array configuration: pass to constructor\n            'truncate_length' =\u003e ['max' =\u003e 100],\n        ],\n    ],\n];\n```\n\n### Configuration Passing Mechanism\n\nThe system automatically passes configuration to constructors based on type:\n\n- `true` → No parameter constructor `new Class()`\n- `'NFKC'` → Single parameter constructor `new Class('NFKC')`\n- `['max' =\u003e 100]` → Array parameter constructor `new Class(['max' =\u003e 100])`\n\n## Real-world Usage Scenarios\n\n### User Registration Form\n\n```php\n// Clean nickname during user registration\nclass RegisterRequest extends FormRequest\n{\n    protected function prepareForValidation(): void\n    {\n        if ($this-\u003ehas('nickname')) {\n            $this-\u003emerge([\n                'nickname' =\u003e TextGuard::filter(\n                    (string)$this-\u003einput('nickname'),\n                    'username'\n                ),\n            ]);\n        }\n    }\n\n    public function rules(): array\n    {\n        return [\n            'nickname' =\u003e ['required', 'string', 'max:20', new Sanitized(0.9, 1)],\n            'email' =\u003e ['required', 'email'],\n        ];\n    }\n}\n\n// Processing result:\n// Input: \"  ＵｓｅｒＮａｍｅ１２３！！！  \"\n// Output: \"UserName123!!\"\n```\n\n### Article Content Management\n\n```php\n// Clean content when publishing articles\nclass ArticleRequest extends FormRequest\n{\n    protected function prepareForValidation(): void\n    {\n        if ($this-\u003ehas('content')) {\n            $this-\u003emerge([\n                'content' =\u003e TextGuard::filter(\n                    (string)$this-\u003einput('content'),\n                    'rich_text'\n                ),\n            ]);\n        }\n    }\n\n    public function rules(): array\n    {\n        return [\n            'title' =\u003e ['required', 'string', new Filtered('safe')],\n            'content' =\u003e ['required', 'string', new Sanitized(0.8, 10)],\n        ];\n    }\n}\n\n// Processing result:\n// Input: \"\u003cp\u003eHello \u003cscript\u003ealert('xss')\u003c/script\u003e World\u003c/p\u003e\"\n// Output: \"\u003cp\u003eHello  World\u003c/p\u003e\"\n```\n\n### Comment System\n\n```php\n// Clean content when submitting comments\nclass CommentRequest extends FormRequest\n{\n    protected function prepareForValidation(): void\n    {\n        if ($this-\u003ehas('content')) {\n            $this-\u003emerge([\n                'content' =\u003e TextGuard::filter(\n                    (string)$this-\u003einput('content'),\n                    'safe'\n                ),\n            ]);\n        }\n    }\n\n    public function rules(): array\n    {\n        return [\n            'content' =\u003e ['required', 'string', 'max:500', new Sanitized(0.7, 1)],\n        ];\n    }\n}\n\n// Processing result:\n// Input: \"  Great article!!!👍👍👍   \"\n// Output: \"Great article!!👍👍👍\"\n```\n\n### Search Keyword Processing\n\n```php\n// Clean keywords during search\nclass SearchController extends Controller\n{\n    public function search(Request $request)\n    {\n        $keyword = TextGuard::filter($request-\u003einput('q', ''), 'safe');\n\n        if (empty($keyword)) {\n            return redirect()-\u003eback()-\u003ewith('error', 'Please enter a valid search term');\n        }\n\n        $results = $this-\u003esearchService-\u003esearch($keyword);\n\n        return view('search.results', compact('results', 'keyword'));\n    }\n}\n\n// Processing result:\n// Input: \"  Laravel Framework  \"\n// Output: \"Laravel Framework\"\n```\n\n### Batch Data Processing\n\n```php\n// Clean user data during batch import\nclass UserImportService\n{\n    public function importUsers(array $users): void\n    {\n        foreach ($users as $user) {\n            $cleanUser = [\n                'name' =\u003e TextGuard::filter($user['name'], 'safe'),\n                'email' =\u003e TextGuard::filter($user['email'], 'safe'),\n                'bio' =\u003e TextGuard::filter($user['bio'] ?? '', 'rich_text'),\n            ];\n\n            User::create($cleanUser);\n        }\n    }\n}\n```\n\n### Custom Pipeline Steps\n\n```php\n// Create custom sensitive word filtering step\nclass SensitiveWordFilter implements PipelineStep\n{\n    public function __construct(protected array $sensitiveWords = []) {}\n\n    public function __invoke(string $text): string\n    {\n        foreach ($this-\u003esensitiveWords as $word) {\n            $text = str_ireplace($word, str_repeat('*', mb_strlen($word)), $text);\n        }\n\n        return $text;\n    }\n}\n\n// Register and use\nTextGuard::registerPipelineStep('sensitive_filter', SensitiveWordFilter::class);\n\n$clean = TextGuard::filter($dirty, 'custom', [\n    'sensitive_filter' =\u003e ['sensitiveWords' =\u003e ['badword1', 'badword2']]\n]);\n```\n\n## Features\n\n- Whitespace handling (trim, collapse spaces)\n- Control character removal\n- Zero-width character removal\n- Unicode normalization\n- Full-width to half-width conversion\n- Punctuation normalization\n- HTML tag processing\n- Repeated punctuation collapsing\n- Visibility checking\n- Length truncation\n- Extensible pipeline architecture\n- Runtime custom step registration\n- Emoji support\n- Chinese punctuation support\n- Character whitelist filtering\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Code Quality\n\nThis project follows strict code quality standards:\n\n```bash\n# Format code\ncomposer fix\n\n# Run tests\ncomposer test\n```\n\n## License\n\nMIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fovertrue%2Flaravel-text-guard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fovertrue%2Flaravel-text-guard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fovertrue%2Flaravel-text-guard/lists"}