Skip to main navigation Skip to main content Skip to page footer

Speaking URLs for TYPO3 9 and tx_news

Here we show you how to set up speaking URLs with multiple parameters for the "News System" extension under TYPO3 9.5.X.

Hello everyone,

Recently, the agency Einmahl WebSolution set out to integrate the tx_news extension for TYPO3 and configure it for the news section on our website. In version 9.5.X, extensions such as realurl are no longer needed, since this kind of functionality is now built into the TYPO3 core. However, this does not change the URLs of certain extensions such as tx_news, which meant action was required. Some research was necessary, and we found that there are many tutorials and explanations covering exactly this topic, but that only allow a single parameter in the URL.

For example, this URL:

einmahl.de/ueber-uns/news/detail?tx_news_pi1[action]=detail&tx_news_pi1[controller]=News&tx_news_pi1[news]=1&cHash=...

generates this URL:

einmahl.de/ueber-uns/news/detail/ein-neuer-blog-entsteht

Here, the title of the news post was appended to the URL as a parameter, which already looks quite good on its own. The only problem now is that these titles can be assigned twice or three times over, which leads to the error that each of these posts would generate the exact same URL. We solved this problem by appending the ID of the post to the name, resulting in the following URL:

einmahl.de/ueber-uns/news/detail/ein-neuer-blog-entsteht-1
 

How does this work?

We'll now explain exactly how this works (a full overview of the code follows again at the end of the post).

The only file that needs to be changed for this is config.yaml. You'll find it in the directory "typo3conf/sites/your-site/config.yaml". Insert the following code here:

routeEnhancers:
  News:
    type: Extbase
    limitToPages: [55]
    extension: News
    plugin: Pi1

With routeEnhancers you can influence how TYPO3 generates URLs. Next, a unique identifier is specified. In our case that would be "News". This is followed by the general settings:

  • The type specifies what kind of enhancer this is. Here you have "Simple", "Plugin" and "Extbase", as well as the option to define your own enhancers, which must be registered in the file "typo3conf/AdditionalConfiguration.php". The tx_news extension requires the type "Extbase" here, so we won't go into the other options any further.
  • limitToPages specifies which pages should be affected by the URL generation. In our example this is the page with ID 55 (found in the page tree of the TYPO3 backend). It's also possible to specify multiple pages here. That would look like this: "limitToPages: [55, 66, 77]". This value is optional; if limitToPages is not specified, the enhancer is applied to every page, which is why, for performance reasons, it makes sense to limit routing to specific extension pages.
  • extension refers to the extension key of the relevant extension (in our case "News"). Note that the extension key must be specified in UpperCamelCase. That means the extension key "meine_neue_extension" would become "MeineNeueExtension".
  • Under plugin the name of the extension's plugin is specified. For the news extension this is "Pi1".
     

routes and aspects

Now we've prepared the routeEnhancer. Next we need to tell it exactly what to do. For this you need routes and aspects. routes extend existing URLs with arguments, while aspects are used to modify these arguments. For a simple title of the news posts in the URL, the following code would suffice:

    routes:
      - routePath: '/{news-title}'
        _controller: 'News::detail'
        _arguments: 
          news-title: 'news'
    aspects:
      news-title:
        type: PersistedAliasMapper
        tableName: 'tx_news_domain_model_news'
        routeFieldName: 'path_segment'

With that, our speaking URL would look like this:

einmahl.de/ueber-uns/news/detail/ein-neuer-blog-entsteht

Now we also want the ID of the post in the URL, to avoid errors caused by posts sharing the same name. Under routePath it's specified that the title of the post should simply be appended to our URL. It would also be possible here to state that the routePath should be '/detail/{news-title}', which would result in the URL you saw above this paragraph. We solved this by creating a page "Detail" underneath "News" in the page tree of the TYPO3 backend, which is why this specification is no longer necessary in our code here.

Under aspects you'll find the unique identifier "news-title", which matches the _arguments from routes and tells it where to find the title of the post. It's specified that the name of the table in the database (tableName) should be "tx_news_domain_model_news" and the relevant field in the table (routeFieldName) "path_segment", which always contains the URL-compatible title of the news item.

To bring the ID into play as well, however, we need a different type. This would look like this:

    aspects:
      news-title: 
        type: PersistedPatternMapper
        tableName: 'tx_news_domain_model_news'
        routeFieldPattern: '^(?P<path_segment>.+)-(?P<uid>\d+)$'
        routeFieldResult: '{path_segment}-{uid}'

Here we used the type "PersistedPatternMapper" instead of "PersistedAliasMapper". The unique identifier (news-title) stays the same here, since we want to address the same argument. The name of the table doesn't change either, since we can find both the title and the ID of the posts in this very table. In the next line a regular expression is used, which contains the corresponding names of the table fields, followed by the final result of how our URL suffix should look in the end. With that, we've achieved our final desired URL:

einmahl.de/ueber-uns/news/detail/ein-neuer-blog-entsteht-1
 

Final result

Here's the full code once again:

routeEnhancers:
  News:
    type: Extbase
    limitToPages: [55]
    extension: News
    plugin: Pi1
    routes:
      - routePath: '/{news-title}'
        _controller: 'News::detail'
        _arguments: 
          news-title: 'news'
    aspects:
      news-title: 
        type: PersistedPatternMapper
        tableName: 'tx_news_domain_model_news'
        routeFieldPattern: '^(?P<path_segment>.+)-(?P<uid>\d+)$'
        routeFieldResult: '{path_segment}-{uid}'

Here you should urgently make sure to set the indentation correctly, as unpredictable results may otherwise follow.