SlugRedirectChangeItemCreatedEvent¶
New in version 12.2.
The PSR-14 event \TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent
is fired in the \TYPO3\CMS\Redirects\RedirectUpdate\SlugRedirectChangeItemFactory
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
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
AddPlainSlugReplacementSource
when their listeners are registered and
executed afterwards. See the example below.
The implementation of the EXT:redirects/Classes/RedirectUpdate/RedirectSourceInterface.php 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\CMS\Redirects\RedirectUpdate\PageTypeSource
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.yaml
:
MyVendor\MyExtension\Backend\MyEventListener:
tags:
- name: event.listener
identifier: 'my-extension/custom-page-type-redirect'
after: 'redirects-add-page-type-zero-source'
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.yaml
:
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 CustomSource
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\CMS\Redirects\EventListener\AddPageTypeZeroSource
creates a \TYPO3\CMS\Redirects\RedirectUpdate\PageTypeSource
for a page
before the slug has been changed. The full URI is built to fill the source_host
and source_path
, which takes configured
route enhancers and route decorators
into account, for example, the PageType route decorator.
Note
If source_host
and source_path
lead to the same outcome for page type 0
using the full URI building like the
\TYPO3\CMS\Redirects\RedirectUpdate\PlainSlugReplacementSource
, the
PlainSlugReplacementSource
is replaced with the PageTypeSource
.
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
PageTypeSource
.
In case that PageTypeSource
for page type 0
results in a different
source, the PlainSlugReplacementSource
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
SlugRedirectChangeItemCreatedEvent
:
Remove plain slug source, if page type 0 differs¶
Registration of the event in your extension's Services.yaml
:
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:
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'
API¶
- class TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent¶
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.
- getSlugRedirectChangeItem()¶
- Return type
TYPO3\CMS\Redirects\RedirectUpdate\SlugRedirectChangeItem
- setSlugRedirectChangeItem(TYPO3\\CMS\\Redirects\\RedirectUpdate\\SlugRedirectChangeItem $slugRedirectChangeItem)¶
- Parameters
$slugRedirectChangeItem (
TYPO3\CMS\Redirects\RedirectUpdate\SlugRedirectChangeItem
) -- the slugRedirectChangeItem