joomla/filter
Joomla Filter provides input sanitization and filtering utilities for PHP apps. Use InputFilter to allow/block specific HTML tags and attributes, and OutputFilter for safe output helpers like URL-safe strings. Composer installable, lightweight, framework-ready.
Install the package via Composer:
composer require joomla/filter "~3.0"
For Laravel, register the package in config/app.php under providers (if not auto-discovered). The primary entry point is the Joomla\Filter\InputFilter class.
First use case: Sanitize user-generated HTML input (e.g., from a form or comment system) to prevent XSS attacks:
use Joomla\Filter\InputFilter;
// Basic HTML sanitization
$cleanHtml = InputFilter::clean($userInput, 'html');
// Customize allowed tags/attributes
$filter = new InputFilter(
['script', 'iframe'], // blockedTags
['onclick', 'onload'], // blockedAttributes
InputFilter::ONLY_ALLOW_DEFINED_TAGS,
InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
);
$cleanHtml = $filter->clean($userInput);
Key files to reference:
src/InputFilter.php (core filtering logic)src/OutputFilter.php (URL/string sanitization)tests/ (examples of edge cases and payloads)Integrate with Laravel’s request pipeline (e.g., middleware or form requests):
// In a middleware or FormRequest class
public function validateSanitizedInput(array $data)
{
$filter = new InputFilter(
['script', 'style'], // blockedTags
['style', 'href'], // blockedAttributes
InputFilter::ONLY_ALLOW_DEFINED_TAGS,
InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
);
foreach ($data as &$value) {
if (is_string($value)) {
$value = $filter->clean($value, 'html');
}
}
return $data;
}
Store allowed tags/attributes in the database (e.g., for user roles) and hydrate the filter dynamically:
// In a service class
public function getRoleBasedFilter($role)
{
$roleConfig = RoleConfig::where('name', $role)->first();
return new InputFilter(
$roleConfig->blocked_tags,
$roleConfig->blocked_attributes,
InputFilter::ONLY_ALLOW_DEFINED_TAGS,
InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
);
}
Create a Blade directive to auto-sanitize output:
// In AppServiceProvider::boot()
Blade::directive('sanitize', function ($expression) {
return "<?php echo (new \\Joomla\\Filter\\InputFilter())->clean({$expression}, 'html'); ?>";
});
// Usage in Blade:
@sanitize($userComment)
Extend Laravel’s validation with custom rules:
use Joomla\Filter\InputFilter;
class SanitizeRule extends Rule
{
public function passes($attribute, $value)
{
$filter = new InputFilter([], [], InputFilter::ONLY_ALLOW_DEFINED_TAGS);
return $filter->clean($value, 'html') === $value;
}
}
// Usage:
'comment' => ['required', new SanitizeRule],
Use OutputFilter for SEO-friendly slugs (requires joomla/language for multibyte support):
use Joomla\Filter\OutputFilter;
$slug = OutputFilter::stringURLSafe($title);
Global State in Static Class:
The static InputFilter::clean() method uses global configurations. Avoid it in long-running processes (e.g., queues, Swoole) or multi-tenant apps. Instantiate per-request:
// ❌ Avoid (global state)
$clean = InputFilter::clean($input);
// ✅ Prefer (per-request)
$filter = new InputFilter();
$clean = $filter->clean($input);
XSS Evasion Characters:
Versions <2.0.6, <3.0.5, and <4.0.1 failed to strip common XSS evasion techniques (e.g., javascript:, '). Always pin to a patch version (e.g., ~3.0.6) and test with payloads like:
$payloads = [
'<img src=x onerror=alert(1)>',
'<script>alert(1)</script>',
'javascript:alert(1)',
'alert(1)',
];
Multibyte String Handling:
mb_* functions inconsistently, risking encoding issues.joomla/language and use OutputFilter::stringURLSafe() with the transliterate option:
$slug = OutputFilter::stringURLSafe($title, ['transliterate' => true]);
Nested Tag Recursion Bug:
Versions <3.0.2 and <2.0.5 had issues with recursive tags (e.g., <img style="..."/>). Test with:
$html = '<div><img src="x" style="display:none"/></div>';
$filter = new InputFilter(['img'], [], InputFilter::ONLY_BLOCK_DEFINED_TAGS);
$clean = $filter->clean($html); // Should strip <img> in v3.0.2+
HTML5 Media Tags:
The stripImages/stripIframes methods may miss modern tags like <picture>, <video>, or <audio>. Extend the filter:
$filter = new InputFilter(['picture', 'video', 'audio'], [], InputFilter::ONLY_BLOCK_DEFINED_TAGS);
\Log::debug("Input:", [$input]);
\Log::debug("Output:", [$filter->clean($input)]);
Custom Filter Types:
Extend InputFilter to add new filter types (e.g., 'bbcode'):
class CustomFilter extends InputFilter
{
protected function filter($value, $type)
{
if ($type === 'bbcode') {
return $this->processBBCode($value);
}
return parent::filter($value, $type);
}
}
Pre/Post-Filter Hooks:
Override preFilter() or postFilter() for custom logic:
class LoggingFilter extends InputFilter
{
protected function preFilter($value, $type)
{
\Log::debug("Filtering {$type}:", [$value]);
return parent::preFilter($value, $type);
}
}
Laravel Service Provider: Bind the filter to the container for dependency injection:
// In AppServiceProvider::register()
$this->app->bind(InputFilter::class, function () {
return new InputFilter([], [], InputFilter::ONLY_ALLOW_DEFINED_TAGS);
});
InputFilter once per request (e.g., in middleware) and reuse it.<b>, <i>, and <p>, explicitly whitelist them instead of blacklisting everything else:
$filter = new InputFilter([], [], InputFilter::ONLY_ALLOW_DEFINED_TAGS);
$filter->setAllowedTags(['b', 'i', 'p']);
How can I help you explore Laravel packages today?