BeforeRecordIsAnalyzedEvent

Event that is fired to modify results (= add results) or modify the record before the linkanalyzer analyzes the record.

Example

In this example we are checking if there are external links containing the URL of the project itself as editors tend to set external links on internal pages at times.

The following code can be put in a custom minimal extension. You find a live example in our example extension EXT:examples.

Create a class that works as event listener. This class does not implement or extend any class. It has to provide a method that accepts an event of type TYPO3\CMS\Linkvalidator\Event\BeforeRecordIsAnalyzedEvent. By default the method is called __invoke:

Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener
use TYPO3\CMS\Linkvalidator\Event\BeforeRecordIsAnalyzedEvent;

final class CheckExternalLinksToLocalPagesEventListener
{
    private const LOCAL_DOMAIN = 'example.org';
    private const TABLE_NAME = 'tt_content';
    private const FIELD_NAME = 'bodytext';

    public function __invoke(BeforeRecordIsAnalyzedEvent $event): void
    {
        $table = $event->getTableName();
        if ($table !== self::TABLE_NAME) {
            return;
        }
        $results = $event->getResults();
        $record = $event->getRecord();
        $field = (string)$record[self::FIELD_NAME];
        if (!str_contains($field, self::LOCAL_DOMAIN)) {
            return;
        }
        $results = $this->parseField($record, $results);
        $event->setResults($results);
    }
}
Copied!

The listener must then be registered in the extensions Services.yaml:

EXT:examples/Configuration/Services.yaml
services:
   _defaults:
      autowire: true
      autoconfigure: true
      public: false

   T3docs\Examples\:
      resource: '../Classes/*'
      exclude: '../Classes/Domain/Model/*'

   T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener:
      tags:
         - name: event.listener
           identifier: 'txExampleCheckExternalLinksToLocalPages'
Copied!

For the implementation we need the BrokenLinkRepository to register additional link errors and the SoftReferenceParserFactory so we can automatically parse for links. These two classes have to be injected via dependency injection:

Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory;
use TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository;

final class CheckExternalLinksToLocalPagesEventListener
{
    private BrokenLinkRepository $brokenLinkRepository;

    private SoftReferenceParserFactory $softReferenceParserFactory;

    public function __construct(
        BrokenLinkRepository $brokenLinkRepository,
        SoftReferenceParserFactory $softReferenceParserFactory
    ) {
        $this->brokenLinkRepository = $brokenLinkRepository;
        $this->softReferenceParserFactory = $softReferenceParserFactory;
    }
}
Copied!

Now we use the SoftReferenceParserFactory to find all registered link parsers for soft reference. Then we apply each of these parsers in turn to the configured field in the current record. For each link found we can now match if it is an external link to an internal page.

Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory;

final class CheckExternalLinksToLocalPagesEventListener
{
    private const LOCAL_DOMAIN = 'example.org';
    private const TABLE_NAME = 'tt_content';
    private const FIELD_NAME = 'bodytext';

    private SoftReferenceParserFactory $softReferenceParserFactory;

    private function parseField(array $record, array $results): array
    {
        $conf = $GLOBALS['TCA'][self::TABLE_NAME]['columns'][self::FIELD_NAME]['config'];
        foreach ($this->findAllParsers($conf) as $softReferenceParser) {
            $parserResult = $softReferenceParser->parse(
                self::TABLE_NAME,
                self::FIELD_NAME,
                $record['uid'],
                (string)$record[self::FIELD_NAME]
            );
            if (!$parserResult->hasMatched()) {
                continue;
            }
            foreach ($parserResult->getMatchedElements() as $matchedElement) {
                if (!isset($matchedElement['subst'])) {
                    continue;
                }
                $this->matchUrl(
                    (string)$matchedElement['subst']['tokenValue'],
                    $record,
                    $results
                );
            }
        }
        return $results;
    }

    private function findAllParsers(array $conf): iterable
    {
        return $this->softReferenceParserFactory->getParsersBySoftRefParserList(
            $conf['softref'],
            ['subst']
        );
    }
}
Copied!

If the URL found in the matching is external and contains the local domain name we add the an entry to the BrokenLinkRepository and to the result set of BeforeRecordIsAnalyzedEvent.

Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener
use TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository;

final class CheckExternalLinksToLocalPagesEventListener
{
    private const LOCAL_DOMAIN = 'example.org';
    private const TABLE_NAME = 'tt_content';
    private const FIELD_NAME = 'bodytext';

    private BrokenLinkRepository $brokenLinkRepository;

    private function matchUrl(string $foundUrl, array $record, array &$results): void
    {
        if (str_contains($foundUrl, self::LOCAL_DOMAIN)) {
            $this->addItemToBrokenLinkRepository($record, $foundUrl);
            $results[] = $record;
        }
    }

    private function addItemToBrokenLinkRepository(array $record, string $foundUrl): void
    {
        $link = [
            'record_uid' => $record['uid'],
            'record_pid' => $record['pid'],
            'language' => $record['sys_language_uid'],
            'field' => self::FIELD_NAME,
            'table_name' => self::TABLE_NAME,
            'url' => $foundUrl,
            'last_check' => time(),
            'link_type' => 'external',
        ];
        $this->brokenLinkRepository->addBrokenLink($link, false, [
            'errorType' => 'exception',
            'exception' => 'Do not link externally to ' . self::LOCAL_DOMAIN,
            'errno' => 1661517573,
        ]);
    }
}
Copied!

The BrokenLinkRepository is not an Extbase repository but a repository based on the Doctrine database abstraction (DBAL). It therefore expects an array with the names of the table fields as argument and not an Extbase model. The method internally uses TYPO3\CMS\Core\Database\Connection::insert. This method automatically quotes all identifiers and values, we therefore do not worry about escaping here.

See the complete class here: CheckExternalLinksToLocalPagesEventListener.

API

class \TYPO3\CMS\Linkvalidator\Event\ BeforeRecordIsAnalyzedEvent

Event that is fired to modify results (= add results) or modify the record before the linkanalyzer analyzes the record.

getTableName ( )
returntype

string

getRecord ( )
returntype

array

setRecord ( array $record)
param array $record

the record

getFields ( )
returntype

array

getResults ( )
returntype

array

setResults ( array $results)
param array $results

the results

getLinkAnalyzer ( )
returntype

TYPO3\CMS\Linkvalidator\LinkAnalyzer