SlugRedirectChangeItemCreatedEvent
New in version 12.2
The PSR-14 event \TYPO3\
is fired in the \TYPO3\
class and allows extensions to manage the redirect sources for which redirects
should be created.
TYPO3 already implements the
EXT:redirects/Classes/EventListener/AddPlainSlugReplacementSource.php (GitHub)
listener. It is used to add the plain slug value based source type, which
provides the same behavior as before. Implementing this as a Core listener
gives extension authors the ability to remove the source added by
Add
when their listeners are registered and
executed afterwards. See the example below.
The implementation of the EXT:redirects/Classes/RedirectUpdate/RedirectSourceInterface.php (GitHub) interface is required for custom source classes. Using this interface enables automatic detection of implementations. Additionally, this allows to transport custom information and data.
Examples
Using the PageTypeSource
The source type implementation based on
\TYPO3\
provides the page type number as additional value. The main use case
for this source type is to provide additional source types where the source host
and path are taken from a full built URI before the page slug change occurred for
a specific page type. This avoids the need for extension authors to implement a
custom source type for the same task, and instead providing a custom event
listener to build sources for non-zero page types.
Registration of the event listener in the extension's Services.
:
services:
# Place here the default dependency injection configuration
MyVendor\MyExtension\Backend\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/custom-page-type-redirect'
after: 'redirects-add-page-type-zero-source'
Read how to configure dependency injection in extensions.
The corresponding event listener class:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Backend;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
use TYPO3\CMS\Core\Routing\RouterInterface;
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PageTypeSource;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceCollection;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceInterface;
final class MyEventListener
{
protected array $customPageTypes = [ 1234, 169999 ];
public function __invoke(
SlugRedirectChangeItemCreatedEvent $event,
): void {
$changeItem = $event->getSlugRedirectChangeItem();
$sources = $changeItem->getSourcesCollection()->all();
foreach ($this->customPageTypes as $pageType) {
try {
$pageTypeSource = $this->createPageTypeSource(
$changeItem->getPageId(),
$pageType,
$changeItem->getSite(),
$changeItem->getSiteLanguage(),
);
if ($pageTypeSource === null) {
continue;
}
} catch (UnableToLinkToPageException) {
// Could not properly link to page. Continue to next page type
continue;
}
if ($this->isDuplicate($pageTypeSource, ...$sources)) {
// not adding duplicate,
continue;
}
$sources[] = $pageTypeSource;
}
// update sources
$changeItem = $changeItem->withSourcesCollection(
new RedirectSourceCollection(
...array_values($sources),
),
);
// update change item with updated sources
$event->setSlugRedirectChangeItem($changeItem);
}
private function isDuplicate(
PageTypeSource $pageTypeSource,
RedirectSourceInterface ...$sources,
): bool {
foreach ($sources as $existingSource) {
if ($existingSource instanceof PageTypeSource
&& $existingSource->getHost() === $pageTypeSource->getHost()
&& $existingSource->getPath() === $pageTypeSource->getPath()
) {
// we do not check for the type, as that is irrelevant. Same
// host+path tuple would lead to duplicated redirects if
// type differs.
return true;
}
}
return false;
}
private function createPageTypeSource(
int $pageUid,
int $pageType,
Site $site,
SiteLanguage $siteLanguage,
): ?PageTypeSource {
if ($pageType === 0) {
// pageType 0 is handled by \TYPO3\CMS\Redirects\EventListener\AddPageTypeZeroSource
return null;
}
try {
$context = GeneralUtility::makeInstance(Context::class);
$uri = $site->getRouter($context)->generateUri(
$pageUid,
[
'_language' => $siteLanguage,
'type' => $pageType,
],
'',
RouterInterface::ABSOLUTE_URL,
);
return new PageTypeSource(
$uri->getHost() ?: '*',
$uri->getPath(),
$pageType,
[
'type' => $pageType,
],
);
} catch (\InvalidArgumentException | InvalidRouteArgumentsException $e) {
throw new UnableToLinkToPageException(
sprintf(
'The link to the page with ID "%d" and type "%d" could not be generated: %s',
$pageUid,
$pageType,
$e->getMessage(),
),
1675618235,
$e,
);
}
}
}
With a custom source implementation
Registration of the event listener in the extension's Services.
:
MyVendor\MyExtension\Redirects\EventListener\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/redirects/add-redirect-source'
after: 'redirects-add-plain-slug-replacement-source'
The corresponding event listener class:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Redirects\EventListener;
use MyVendor\MyExtension\Redirects\CustomSource;
use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PlainSlugReplacementRedirectSource;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceCollection;
final class MyEventListener
{
public function __invoke(SlugRedirectChangeItemCreatedEvent $event): void
{
// Retrieve change item and sources
$changeItem = $event->getSlugRedirectChangeItem();
$sources = $changeItem->getSourcesCollection()->all();
// Remove plain slug replacement redirect source from sources
$sources = array_filter(
$sources,
fn($source) => !($source instanceof PlainSlugReplacementRedirectSource),
);
// Add custom source implementation
$sources[] = new CustomSource();
// Replace sources collection
$changeItem = $changeItem->withSourcesCollection(
new RedirectSourceCollection(...array_values($sources)),
);
// Update changeItem in the event
$event->setSlugRedirectChangeItem($changeItem);
}
}
Example of a Custom
implementation:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Redirects;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceInterface;
final class CustomSource implements RedirectSourceInterface
{
public function getHost(): string
{
return '*';
}
public function getPath(): string
{
return '/some-path';
}
public function getTargetLinkParameters(): array
{
return [];
}
}
Default event listeners
The listener \TYPO3\
creates a \TYPO3\
for a page
before the slug has been changed. The full URI is built to fill the source_
and source_
, which takes configured
route enhancers and route decorators
into account, for example, the PageType route decorator.
Note
If source_
and source_
lead to the same outcome for page type 0
using the full URI building like the
\TYPO3\
, the
Plain
is replaced with the Page
.
It is not possible to configure for which page types sources should be added. If
you need to do so, see Using PageTypeSource
which contains an example how to implement a custom event listener based on
Page
.
In case that Page
for page type 0
results in a different
source, the Plain
is not removed to keep the original
behaviour, which some instances may rely on.
This behaviour can be modified by adding an event listener for
Slug
:
Remove plain slug source, if page type 0 differs
Registration of the event in your extension's Services.
:
services:
# Place here the default dependency injection configuration
MyVendor\MyExtension\Backend\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/custom-page-type-redirect'
# Registering after Core listener is important, otherwise we would
# not know if there is a PageType source for page type 0
after: 'redirects-add-page-type-zero-source'
The corresponding event listener class:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Backend;
use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PageTypeSource;
use TYPO3\CMS\Redirects\RedirectUpdate\PlainSlugReplacementRedirectSource;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceCollection;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceInterface;
final class MyEventListener
{
public function __invoke(
SlugRedirectChangeItemCreatedEvent $event,
): void {
$changeItem = $event->getSlugRedirectChangeItem();
$sources = $changeItem->getSourcesCollection()->all();
$pageTypeZeroSource = $this->getPageTypeZeroSource(
...array_values($sources),
);
if ($pageTypeZeroSource === null) {
// nothing we can do - no page type 0 source found
return;
}
// Remove plain slug replacement redirect source from sources. We
// already know, that if it is there it differs from the page type
// 0 source, therefor it is safe to simply remove it by class check.
$sources = array_filter(
$sources,
static fn($source) => !($source instanceof PlainSlugReplacementRedirectSource),
);
// update sources
$changeItem = $changeItem->withSourcesCollection(
new RedirectSourceCollection(
...array_values($sources),
),
);
// update change item with updated sources
$event->setSlugRedirectChangeItem($changeItem);
}
private function getPageTypeZeroSource(
RedirectSourceInterface ...$sources,
): ?PageTypeSource {
foreach ($sources as $source) {
if ($source instanceof PageTypeSource
&& $source->getPageType() === 0
) {
return $source;
}
}
return null;
}
}
API
- class SlugRedirectChangeItemCreatedEvent
-
- Fully qualified name
-
\TYPO3\
CMS\ Redirects\ Event\ Slug Redirect Change Item Created Event
This event is fired in the TYPO3CMSRedirectsRedirectUpdateSlugRedirectChangeItemFactory factory if a new SlugRedirectChangeItem is created.
It can be used to add additional sources, remove sources or completely remove the change item itself. A source must implement the RedirectSourceInterface, and for each source a redirect record is created later in the SlugService. If the SlugRedirectChangeItem is set to null, no further action is executed for this slug change.