cpsit / cps-shortnr
routing alias enhancer over middleware
Installs: 847
Dependents: 0
Suggesters: 0
Security: 0
Stars: 3
Watchers: 3
Forks: 4
Open Issues: 0
Type:typo3-cms-extension
pkg:composer/cpsit/cps-shortnr
Requires
- php: ^8.2
- brannow/typed-pattern-engine: ^0.4
- typo3/minimal: ^13.4
Requires (Dev)
- phpunit/phpunit: ^12.3
- typo3/testing-framework: ^9.2
This package is auto-updated.
Last update: 2026-01-09 15:59:41 UTC
README
Routing alias enhancer via middleware for TYPO3 CMS. Generates and resolves short URLs for pages and plugin records using configurable patterns.
Requirements
- PHP ^8.2
- TYPO3 ^13.4
Dependencies
- brannow/typed-pattern-engine ^0.4 - AST-based pattern matching engine
Installation
composer require cpsit/shortnr
The extension auto-registers its middleware and cache configuration.
Configuration
Default Configuration
The extension ships with a minimal default configuration in Configuration/config.yaml:
shortNr: _default: notFound: "/" languageParentField: "l10n_parent" languageField: "sys_language_uid" identifierField: "uid" pages: type: page table: pages pattern: "p{uid:int(min=1)}(-{sys_language_uid:int(min=0,default=0)})"
Pattern Syntax
Patterns use the typed-pattern-engine syntax:
| Syntax | Description |
|---|---|
{field:int(min=1)} |
Integer with minimum value |
{field:int(min=0,default=0)} |
Integer with default |
(-{field}) |
Optional segment (parentheses) |
PREFIX{field} |
Static prefix |
Example: NEWS{uid:int(min=1)}(-{sys_language_uid:int(min=0,default=0)}) generates NEWS123 or NEWS123-1
Adding Custom Configuration
Create an event listener to register your configuration file:
<?php namespace Vendor\SitePackage\EventListener\ShortNr; use CPSIT\ShortNr\Event\ShortNrConfigPathEvent; use TYPO3\CMS\Core\Attribute\AsEventListener; #[AsEventListener( identifier: 'site-package/shortNrConfigListener', event: ShortNrConfigPathEvent::class, method: 'loadConfig' )] class ConfigLoadingEventListener { public function loadConfig(ShortNrConfigPathEvent $event): void { $event->addConfigPath('EXT:site_package/Configuration/cps_shortnr.yaml'); } }
Config Merging Behavior
Configs are merged via array_replace_recursive - higher priority configs (default: 10) override lower ones (extension default: -1000).
- Every field can be overwritten by later configs
- Arrays merge recursively (add new keys, override existing)
- Set a field to
nullto remove it - the priority can be altered at the ShortNrConfigPathEvent::addConfigPath call
Configuration Examples
Page type:
shortNr: pages: type: page table: pages pattern: "PAGE{uid:int(min=1)}(-{sys_language_uid:int(min=0,default=0)})"
Plugin type (e.g., News):
shortNr: news: type: plugin table: tx_news_domain_model_news pattern: "NEWS{uid:int(min=1)}(-{sys_language_uid:int(min=0,default=0)})" plugin: extension: News plugin: Pi1 pid: 43 # Page UID containing the plugin action: detail controller: News objectName: news # Request parameter name
With conditions:
shortNr: event: type: plugin table: tx_news_domain_model_news pattern: "EVENT{uid:int(min=1)}(-{sys_language_uid:int(min=0,default=0)})" plugin: extension: News plugin: Pi1 pid: 474 action: detail controller: News objectName: news condition: is_event: 1
Available Condition Operators
See Configuration/config.example.full.yaml for detailed examples.
| Operator | Example | Description |
|---|---|---|
| implicit | field: value |
Equals |
eq |
field: {eq: value} |
Equals (explicit) |
in |
field: [a, b, c] |
Value in array |
contains |
field: {contains: "text"} |
Substring match |
starts |
field: {starts: "prefix"} |
Starts with |
ends |
field: {ends: "suffix"} |
Ends with |
gt, gte |
field: {gte: 50} |
Greater than (or equal) |
lt, lte |
field: {lt: 100} |
Less than (or equal) |
between |
field: {between: [10, 50]} |
Range |
isset |
field: {isset: true} |
Field exists |
not |
field: {not: {eq: value}} |
Negation |
Usage
ViewHelper
Generate short URLs in Fluid templates:
<html xmlns:sn="http://typo3.org/ns/CPSIT/ShortNr/ViewHelpers" data-namespace-typo3-fluid="true"> <!-- By config name and UID --> <sn:shortUrl name="news" uid="123" output="result" absolute="1"> <f:if condition="{result.uri}"> <a href="{result.uri}">{result.uri}</a> </f:if> </sn:shortUrl> <!-- From current page context (no name/uid = environment demand) --> <sn:shortUrl output="shortNr" absolute="1"> {shortNr.uri} </sn:shortUrl> <!-- From domain object --> <sn:shortUrl object="{newsItem}" output="result" absolute="1"> {result.uri} </sn:shortUrl>
ViewHelper Arguments:
| Argument | Type | Description |
|---|---|---|
output |
string | Variable name for result (required) |
languageUid |
int | Target language |
absolute |
bool | Generate absolute URL |
The ViewHelper supports three demand modes (mutually exclusive):
| Mode | Arguments | Description |
|---|---|---|
| Config + UID | name + uid |
Explicit config name and record UID |
| Object | object |
Domain object (extracts config + UID automatically) |
| Environment | (none) | Uses current page/plugin context |
Service (PHP)
Encoding (generate short URL):
use CPSIT\ShortNr\Service\EncoderService; use CPSIT\ShortNr\Service\Url\Demand\Encode\ConfigNameEncoderDemand; use CPSIT\ShortNr\Service\Url\Demand\Encode\ObjectEncoderDemand; use CPSIT\ShortNr\Service\Url\Demand\Encode\EnvironmentEncoderDemand; // Inject EncoderService via constructor public function __construct(private readonly EncoderService $encoderService) {} // 1. Config + UID: Explicit config name and record UID $demand = (new ConfigNameEncoderDemand('news', 123)) ->setRequest($request) // Required for site context ->setLanguageId(0) // Target language (null = from request) ->setAbsolute(true); // Full URL vs relative path $uri = $this->encoderService->encode($demand); // 2. Object: From domain object (AbstractEntity) $demand = (new ObjectEncoderDemand($newsEntity)) ->setRequest($request) ->setAbsolute(true); $uri = $this->encoderService->encode($demand); // 3. Environment: From current request context $demand = (new EnvironmentEncoderDemand( $request->getQueryParams(), $request->getAttribute('frontend.page.information')?->getPageRecord() ?? [], $request->getAttribute('routing'), $request->getAttribute('extbase') )) ->setRequest($request) ->setAbsolute(true); $uri = $this->encoderService->encode($demand);
Decoding (resolve short URL):
use CPSIT\ShortNr\Service\DecoderService; use CPSIT\ShortNr\Service\Url\Demand\Decode\DecoderDemand; // Inject DecoderService via constructor public function __construct(private readonly DecoderService $decoderService) {} // Direct: From short URL string $demand = new DecoderDemand('NEWS123'); $fullUri = $this->decoderService->decode($demand); // From request: Extracts short URL from request path $demand = $this->decoderService->getDecoderDemandFromRequest($request); $fullUri = $demand ? $this->decoderService->decode($demand) : null;
How It Works
- Middleware intercepts GET requests and checks for short URL patterns
- DecoderService matches request path against configured patterns
- On match: 301 redirect to resolved full URL
- On no match: request continues to TYPO3 routing
Encoding compiles record data into short URL using configured pattern. Decoding extracts UID from short URL, queries database with conditions, generates full URL.
Caching
- Compiled patterns cached to filesystem
- Encoded/decoded URLs cached with 1 week TTL
- Cache cleared on
cache:allorcache:pagesflush - Cache tags:
all,uri,encode,decode
NotFound Handling
Configure fallback behavior when a short URL cannot be resolved:
shortNr: _default: notFound: 2 # Page UID # or notFound: "/404" # URI string # or notFound: "" # Disable (continue to TYPO3 routing)
Extension Setup
The extension registers:
- PSR-15 middleware (early in stack, before site resolution)
- TYPO3 cache configuration (
cps_shortnr) - DataHandler hook for cache invalidation