# Filtering User Input

https://dev.wisecp.com/es/filtering-user-input

Every value that arrives from a browser is read through `Filter`. It cuts the value down to the characters the receiving code expects, before anything else sees it.

## Overview

WISECP never reads `$_POST`, `$_GET` or `$_REQUEST` directly. One class takes a path into the superglobal plus a filter name. It answers the value already reduced to what that filter allows.

Filtering is half of input safety. Validation is the other half: filtering removes characters, validation decides whether what is left is usable. A filtered but meaningless value still has to be rejected by your own check.

## Reference

### Signatures

```php
public static function init($arg = null, $mod = false, $special = false);

public static function GET($arg = '');
public static function POST($arg = '');
public static function REQUEST($arg = '');
public static function FILES($arg = '');
public static function SERVER($arg = '');

public static function html_clear($arg = null, $allow = '');
public static function phone($arg = '');
public static function route($arg = '', $special = '');
public static function permalink($str, $options = []);
```

Everything here is static. Most names in the table below are also methods of their own with the same two arguments. `Filter::rnumbers($value)` does what `init` does after resolving the path. Three are different. There is no `hclear` method, because that name calls `html_clear`. `false` is the absence of a filter, and `domain` takes the value alone.

### The Three Arguments of init

- **$arg**: A source path such as `POST/user/name`, or a plain value. A string that starts with none of the five source names is filtered as it stands, so `Filter::init($text, "hclear")` is legal. An empty first argument answers `false`.
- **$mod**: The filter name, one of the nineteen in the table. Default `false` returns the value untouched, and so does an unrecognised name.
- **$special**: Extra characters merged into the filter's allow list, as regular expression syntax. `a-z` is a range, and a literal hyphen or slash needs escaping. With `$mod` left at `false` it becomes the whole allow list, applied after markup is stripped.

### Source Paths

The first path segment names the superglobal, the rest walks into it. `POST/user/name` reads the `name` key inside the `user` array of the request body. The same five accessors are public and return the raw value, unfiltered. With no argument they return the whole superglobal; with a missing key they return `false`, never `null`.

- **Filter::GET($arg)**: Query string. Path prefix `GET/`.
- **Filter::POST($arg)**: Request body. Path prefix `POST/`.
- **Filter::REQUEST($arg)**: Either source. Path prefix `REQUEST/`, and how an operation name is read.
- **Filter::FILES($arg)**: Upload entry for a field name, as PHP built it. Path prefix `FILES/`.
- **Filter::SERVER($arg)**: Server variable such as `REMOTE_ADDR`. Path prefix `SERVER/`.

### Filter Types

The last column is what you get when the key is not in the request. That answer differs by filter.

| Filter | Keeps | Returns | Key absent |
| --- | --- | --- | --- |
| `false` | The value untouched, markup included | mixed | `false` |
| `hclear` | Tags stripped after entities are decoded | string | empty string |
| `text` | Same, then both quote characters become numeric entities | string | empty string |
| `dtext` | Same as `hclear`, quotes left alone | string | empty string |
| `letters_numbers` | Latin letters and digits | string | `false` |
| `letters` | Latin letters plus the active language's letters | string | empty string |
| `numbers` | Digits and hyphen | string | `false` |
| `rnumbers` | Digits and hyphen, then cast to a whole number | int | `0` |
| `amount` | Digits, hyphen, dot, comma; separators kept as typed | string | empty string |
| `rate` | Same, comma read as the decimal point, thousands dots dropped | float | `0.0` |
| `ip` | Letters, digits, hyphen, dot, colon, so v6 survives | string | `false` |
| `domain` | Letters, digits, dot, hyphen, then lowercased | string | empty string |
| `email` | Letters, digits and the characters `@ . + _` | string | empty string |
| `route` | Letters, digits, hyphen, underscore, dot; `../` removed first | string | empty string |
| `folder` | Letters, digits, slash, hyphen, underscore, dot | string | empty string |
| `file` | Letters, language letters, digits, hyphen, underscore, dot | string | empty string |
| `noun` | Letters, digits, comma, dot, space | string | empty string |
| `identity` | Digits and hyphen, same rule as `numbers` | string | `false` |
| `password` | Everything, unchanged | mixed | `false` |

`password` is deliberately a pass-through. A strong password is made of exactly the characters the other filters remove. Stripping them silently changes what the visitor typed. `hclear`, `text`, `dtext`, `email`, `domain`, `identity` and `password` ignore the third argument.

### Helpers You Call Directly

- **html_clear($arg, $allow)**: Decodes entities, then strips tags. The second argument is the allow list in PHP's own format, for example `'<b><i>'`. This is the only way to keep tags: `hclear` never passes one.
- **phone($arg)**: Digits only: a country prefix, spaces and brackets disappear. One argument, no allow list.
- **route($arg, $special)**: Removes `../`, then keeps letters, digits, hyphen, underscore and dot. Add the escaped slash through the second argument when a route segment carries one.
- **permalink($str, $options)**: The slug maker. Options, with their defaults: `delimiter` (a hyphen), `limit` (none), `lowercase` (true), `replacements` (patterns applied first, empty) and `transliterate` (true).

## Example

Field names are the contract between the two sides. What the form posts is what the path spells.

```html
<input type="hidden" name="operation" value="update_client">
<input type="hidden" name="id" value="42">
<input type="text"     name="user[name]">
<input type="email"    name="email">
<input type="password" name="password">
<input type="text"     name="slug">
```

```php
public function update_client(Operation $operation): bool
{
    $operation->demo();

    $id    = (int) Filter::init("POST/id", "rnumbers");
    $name  = Filter::init("POST/user/name", "hclear");
    $email = Filter::init("POST/email", "email");
    $pass  = Filter::init("POST/password", "password");
    $slug  = Filter::init("POST/slug", "route", "\/");

    // Filtering is not validation: these are clean, not necessarily usable.
    if (!$id)                                          throw new Exception(Language::gc("error/id-required"));
    if ($name === '')                                  throw new Exception(Language::gc("error/name-required"));
    if (!filter_var($email, FILTER_VALIDATE_EMAIL))    throw new Exception(Language::gc("error/email-invalid"));

    $bio = Filter::html_clear(Filter::POST("bio"), '<b><i><a>');

    return $operation->output(['status' => "successful"]);
}
```

## Pitfalls

> **A misspelled filter name returns the raw value**
> 
> The name is matched against a list and anything unmatched falls through to the untouched value, markup and all. `Filter::init("POST/v", "number")` is not a numeric read, it is no read at all. Writing the allow list into the second slot instead of the third does the same: `Filter::init("POST/v", "a-z")` filters nothing.

> **An absent key does not answer the same way twice**
> 
> The five accessors always answer `false`, so a presence test is written `!== false`. Through `init` the answer belongs to the filter, as the table's last column shows. A test written as `!== ''` or `!== null` is true on every request for some of them.

> **The third argument is regular expression syntax**
> 
> It is pasted into a character class exactly as written. It can widen the filter far past what you meant, and a stray bracket breaks the pattern. Escape what must be literal, and never build it from user input.

> **Array values do not survive a scalar filter**
> 
> Only `password` and the unfiltered read pass an array through. The rest answer `false`, an empty string or zero depending on the filter. A grouped field is read key by key rather than as one value.

## Related Articles

- [Operations](https://dev.wisecp.com/en/operations)
- [Error Handling](https://dev.wisecp.com/en/error-handling)
- [Coding Conventions](https://dev.wisecp.com/en/coding-conventions)
