Public Forms
Public forms, like contact or newsletter subscription forms, are a common requirement for websites, but often a hassle for developers. Typemill makes your life much easier, because all you need is a plugin with some form definitions in YAML, and business logic in PHP. All the complicated stuff like generating the front end forms, validating, form-data, and adding spam security or CSRF-protection is done by Typemill automatically.
Form Definitions
Public forms are defined in a separate block of the YAML-configuration-file of your plugin. Public forms can use the exact same field definitions as theme- and plugin-forms. Look at this example:
forms:
fields:
myfield:
type: text
label: 'I am a textfield'
required: true
public:
fields:
mypublicfield:
type: text
label: 'I am a textfield'
required: true
If you want to provide an option for the admin to change the button-label for the form, then add the input field button_label to your plugin like this:
forms:
fields:
button_label:
type: text
label: Label for Register Button
placeholder: Register now
required: true
You probably want to customize the labels and text hints of your public forms, so that the admin can localize them for example. This can be done by a simple reference like this:
forms:
fields:
mail_label:
type: text
label: 'Label of Mail-Input-Field'
placeholder: 'Localize label here'
required: true
public:
fields:
mail:
type: text
label: mail_label
required: true
The label of the public mail field has a reference to the mail_label field of the admin forms. This way, the admin can overwrite the label for the mail-field of the public form in the plugin-settings. You can add references like this for three fieldtypes:
- label
- help
- description
Public Form Definitions
As of version 1.5.1 you can also provide a textarea with the name publicformdefinitions. In this textarea, the admin can define his own forms with the YAML syntax.
forms:
fields:
publicformdefinitions:
type: textarea
label: configure your public form with yaml
This is pretty useful, for example if you create a contactform plugin and if you want to provide the option to define an individual contact form with all kind of fields. But this also requires some more skills from the admin: He should understand YAML and he should understand at least a bit of HTML so he knows which fields exist and which attributes they can have.
The yaml-definition from the textarea publicformdefinitions will overwrite all public form definitions that you made in the yaml file of your plugin. This can be a bit risky, for example if your plugin logic expects a field with the name "username" or "password". If the admin deletes those fields, the plugin will not work anymore.
It is a good idea to provide some standard-definitions for the publicformdefinitions in the settings of your plugin. This way, the user can see the standard definitions and even delete his own definitions later to switch back to the standard-definitions.
settings:
publicformdefinitions: "username:\r\n type: text\r\n label: username\r\n placeholder: Username\r\n required: true\r\n\r\nemail:\r\n type: text\r\n label: E-Mail\r\n placeholder: Email\r\n required: true\r\n\r\npassword:\r\n type: password\r\n label: Password\r\n required: true\r\n\r\ngumroad:\r\n type: password\r\n label: Gumroad Licence Key\r\n required: true"
Business Logic
Typemill provides some handy methods to make form-handling as easy as possible. You will only need two new routes:
- A route where you want to show your form.
- A route where you want to process the form-data.
Let us check the register-plugin and use the routes from there:
public static function addNewRoutes()
{
return [
['httpMethod' => 'get', 'route' => '/tm/register', 'class' => 'Plugins\Register\Register:showRegistrationForm', 'name' => 'register.show'],
['httpMethod' => 'post', 'route' => '/tm/register', 'class' => 'Plugins\Register\Register:createUser', 'name' => 'register.create'],
];
}
In the next step, let us create the method showRegistrationForm to display the form in the frontend. We can easily generate a form with the method $this->generateForm() like this:
public function showRegistrationForm($request, $response, $args)
{
$twig = $this->getTwig(); // get the twig-object
$loader = $twig->getLoader(); // get the twig-template-loader
$loader->addPath(__DIR__ . '/templates');
$settings = $this->getSettings();
# get the public forms for the plugin
$registerform = $this->generateForm('register.create');
return $twig->render($response, '/register.twig', ['settings' => $settings, 'registerform' => $registerform ]);
}
The first parameter of generateForm() is the name of the route where you want to send the data, in this case it is our post-route with the name register.create. The plugin name is detected automatically from the plugin class, but you can pass it as an optional second parameter if needed.
Now let us create that method to handle the form-data.
# create a new user
public function createUser($request, $response, $args)
{
$params = $this->validateParams($request->getParams());
if(!$params)
{
# validation failed, redirect back to the form (errors are stored in the session automatically)
$url = $this->container->get('routeParser')->urlFor('register.show');
return $response->withRedirect($url);
}
# validation passed, do something with the params (e.g. create the user) and redirect
$url = $this->container->get('routeParser')->urlFor('home');
return $response->withRedirect($url);
}
This is super simplified but it is more or less all you have to do. The method $this->validateParams($request->getParams()) will validate all incoming params from the user against the original form-definitions and it will return only the params that are defined in the YAML form definition, everything else is stripped out. If the validation fails, the method returns false and stores the error messages in the session, so the form can display them after the redirect. You can, of course, do more validations if you want. And you probably want to do something with the params like store them somewhere. All this depends on the logic of your plugin.
Security
Public forms have a lot of security implications. To make your forms as secure as possible, Typemill provides some build-in security features. At the same time a plugin developer and a website administrator should do everything on their own to protect their page.
Use HTTPS
It is generally recommended to use HTTPS connections for websites. If you use public forms, then it is also often required by law (e.g. contact forms and dsgvo).
Input Validation
The developer is responsible to validate all incoming params from the form. To make this easy as possible, Typemill provides the method $this->validateParams($request->getParams()). The method will validate all incoming data against the original form definition, so no additional data can be posted. Incoming data is validated with common rules; no HTML or other code characters will pass the validation. All error messages are added automatically, so you do not have to worry about it.
You can also add a regular expression to each field. The input data will be validated against this regular expression before the data is stored.
Bot Protection with Honeypot
Typemill will automatically add a honeypot field to all public forms. A honeypot field is a input field (text) that is visually hidden in frontend. If a bot fills it out (because it does not notice that it is hidden), then the bot will automatically be redirected to the startpage after submitting the form.
Additionally, all web forms require a Referer header, which filters out simple bots and scripts that do not send this header.
Please note that Typemill does not add CSRF-tokens to public forms right now. If your form performs critical actions, you should consider adding your own CSRF protection in your plugin.
Captcha
Typemill ships with a traditional captcha field with a text-image (gregwar php captcha) for public forms. A user can only submit a form if he fills out the captcha correctly. If the captcha is incorrect, then the user will be redirected to the form again and the data are not stored. This is a standard measure to protect forms from spam-bots and other techniques.
The captcha is not activated automatically. To use it, you have to add the following radio-buttons to the settings of your plugin:
forms:
fields:
captchaoptions:
type: radio
label: Configure the captcha
options:
standard: Show directly (standard)
aftererror: Show after first wrong input
disabled: Disable
You can change the field label and the description of each option. But you have to keep the fieldname captchaoptions and the option-names standard, aftererror and disabled, otherwise it will be ignored. The captcha is only displayed if one of the options is stored in the plugin settings, so it is a good idea to ship a default value in the settings block of your plugin configuration file:
settings:
captchaoptions: standard
Without the captchaoptions field (or without a stored value), no captcha is displayed and no captcha is enforced.
Google Re-Captcha
The traditional captcha field that ships with typemill (gregwar php captcha) does not rely on any external service and is a perfect solution without any privacy concerns. However, it is a bit difficult to read and solve and thus might lower the conversion of your forms. Also the protection might be lower because an image captcha can be solved by KI machines today.
As an alternative you can also provide a google recaptcha where the user only has to check a checkbox in most cases. Be aware that it might have some privacy implications when you add it to a website.
Important limitation: The re-captcha widget is rendered in the frontend, but the server-side verification of the re-captcha response is not active in the current Typemill version. This means the re-captcha currently provides no real protection, so you should always combine it with the standard captcha or use the standard captcha alone.
To add an option for google re-captcha in your plugin settings, please add the following fields:
forms:
fields:
recaptcha:
type: checkbox
label: Google Recaptcha
checkboxlabel: Activate Recaptcha
recaptcha_webkey:
type: text
label: Recaptcha Website Key
help: Add the recaptcha website key here. You can get the key from the recaptcha website.
description: The website key is mandatory if you activate the recaptcha field
recaptcha_secretkey:
type: text
label: Recaptcha Secret Key
help: Add the recaptcha secret key here. You can get the key from the recaptcha website.
description: The secret key is mandatory if you activate the recaptcha field
If you add google recaptcha to your plugin, you also have to add the standard php-captcha option so the admin can disable it in the plugin settings. Otherwise you get two captchas.
Track Suspicious Actions
In the security section of the system settings you can activate a security log that tracks actions that might result from attacks or spam bots. The log contains the IP address, the time and the action (like wrong captcha, wrong password, and more). You can view and delete the log with the Kixote AI assistant in the author interface (commands "show security log" and "clear security log") or directly via the API endpoints GET /api/v1/securitylog and DELETE /api/v1/securitylog (requires the role "manager" or higher).
IP-blocking
This feature is not implemented yet, but it is planned for a future release of typemill.
Individual Security Features for Plugins
If you develop a plugin, then think about additional security features. For example, if you create a registration feature, then you can block all registration attempts with so called burner mails. This is implemented with the existing register plugin. Another security feature from that plugin is the double opt in method.