The TYPO3 form framework offers many possibilities and is easy to extend. Here I present a simple way how an individual finisher can influence the following.

A custom form finisher

In the TYPO3 Form Framework (ext:form), a finisher is a class that takes care of processing the data that the user has entered in the form. By the time the finisher comes into play, simple checks on the data, such as field validations, have already been completed. Finishers are now responsible for what to do with that data.

For many use cases, the Form Framework already comes with ready-made finishers. For example, a confirmation email can be sent to the user, or the site owner can be notified, or the data can be stored away in the database. And also the confirmation message or the redirection to another page is realized by a finisher.

A nice concept in the Form Framework is that you can switch individual finishers one after the other. So you don't have to decide if the user gets an email or if the data should be written to the database. From a software architecture point of view, the finishers remain very lean and implement exactly what they are supposed to do - e.g. pass data to SAP, address an API or whatever.

 

In principle, the finishers can also build on each other. For example, one finisher can generate a PDF from the data and another finisher can then send this PDF as an attachment to an e-mail.

The problem

For a customer project I recently had to implement a finisher that is the first finisher to analyze the input. This post is not about what exactly this finisher does and does not do. But part of the problem is that the finisher should have an influence on the following finishers - and not on all of them, but only on some of them.

For simplicity, let's think of an antiSPAM finisher that should be invisible to the outside world. So to the spammer it looks like his attempts are successful, but internally no records or emails are generated. (Whether this makes much sense or not remains to be seen - for the explanation and solution of the problem, the example is appropriate).

Workflow of the finisher

For our site we assume that the following finishers are processed one after the other:

  1. AntiSpam finisher - checks if the requests are SPAM.
  2. SaveToDB-Finisher - saves the request into the database
  3. EmailFinisher - sends an email to the page owner
  4. Confirmation - displays a "Thank you for your message" message.

 

Depending on whether the AntiSpam Finisher has detected spam or not, steps 2 + 3 should be skipped (or not). Step 4 should be executed in any case.

And why is this difficult?

The finishers are independent from each other and they don't know anything about each other. For example, finishers can set variables that other finishers can access - but we don't want to extend all standard finishers just to make them compatible with our AntiSpam finisher.

Furthermore, each finisher can cancel further processing. However, this then results in no further finisher being executed. Also not what we want.

 

In media res

But let's dive into the code first. First of all, we need a finisher that implements our logic.

For that, we'll implement an AntiSpamFinisher class:

 

<?php

namespace MarcWillmann\Sitepackage\Domain\Finishers;

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;

class AntispamFinisher extends AbstractFinisher
{
    
    /**
     * @var string
     */
    protected $shortFinisherIdentifier = 'Antispam';

    /**
     * @var mixed[]
     */
    protected array $data = [];

    protected function executeInternal(): void
    {
        // do whatever the finisher has to do
    }

}

 

and make them known in the form framework:

 

TYPO3:
  CMS:
    Form:
      prototypes:
        default:
          finishersDefinition:
            Antispam:
              implementationClassName: MarcWillmann\Sitepackage\Domain\Finishers\AntispamFinisher.
              FormEngine:
                label: 'IP Block (Anti spam)'

 

Now we can already use the finisher in our forms. To do this, we simply insert the finisher into the form.form.yaml, e.g. like this:

 

prototypeName: standard
finishers:
  - identifier: Antispam
  - identifier: EmailToReceiver
    options:
      subject: 'new order
      recipientAddress: 'sales@domain.tld'
      recipientName: '1001 TYPO3 solutions GmbH & Co. KG
      senderAddress: 'no-reply@domain.tld'
      senderName: '{firstname} {lastname}'
  -
    options:
      message: 'Thank you for your message!'
      contentElementUid: ''
    identifier: Confirmation

 

 

The solution to the riddle

At this point I was stuck for some time. I tried several approaches and discarded them again. But don't worry, it's much easier than I thought:

First, we set a finisher variable in the AntiSpamFinisher class, depending on whether we detected SPAM or not:

 

protected function executeInternal(): void
    {
        // do whatever the finisher has to do

        if ($isSpam) {
            $this->finisherContext->getFinisherVariableProvider()->add(
                $this->shortFinisherIdentifier,
                'enabled',
                false
            );
        } else {
            $this->finisherContext->getFinisherVariableProvider()->add(
                $this->shortFinisherIdentifier,
                'enabled',
                true
             );
    }

 

this looks a little strange at first glance. But it becomes clearer with the little trick that completes the solution. In the form.form.yaml:

 

prototypeName: default
finishers:
  - identifier: Antispam
  - identifier: EmailToReceiver
    options:
      subject: 'new order
      recipientAddress: 'sales@domain.tld'
      recipientName: '1001 TYPO3 solutions GmbH & Co. KG
      senderAddress: 'no-reply@domain.tld'
      senderName: '{firstname} {lastname}''
      renderingOptions:
        '{Antispam.enabled}'
  -
    options:
      message: 'Thank you for your message!'
      contentElementUid: ''
    identifier: Confirmation

 

we leave the decision whether to execute each finisher or not to this very variable we set at the beginning. Quite simple really, isn't it? :)

Comments (0)

No comments found!

Write new comment reply