TYPO3 Routing: Multiple Path Segments - typo3

I am trying to generate tree URLs for some categories.
The URLs should look like this: /category1/subcategory1/subcategory2.
The amount of path segments should be flexible since I don't know how deep the categories will be.
I got the first path segment working with the following config and a custom Mapper:
ProductsPlugin:
type: Extbase
limitToPages:
- 5
extension: MyExt
plugin: Products
routes:
-
routePath: '/{category_title}'
_controller: 'Products::list'
_arguments:
category_title: id
defaultController: 'Products::list'
requirements:
category_title: '[0-9]{1..6}'
aspects:
category_title:
type: ProductsValueMapper
The Mapper looks like this:
<?php
namespace Vendor\MyExt\Routing\Aspect;
use TYPO3\CMS\Core\Routing\Aspect\PersistedMappableAspectInterface;
use TYPO3\CMS\Core\Site\SiteLanguageAwareTrait;
class ProductsValueMapper implements PersistedMappableAspectInterface
{
use SiteLanguageAwareTrait;
/**
* #param string $value
*
* #return string|null
*/
public function generate(string $value): ?string
{
/**
* returns one or more path segments
* e.g. category1 or category1/subcategory1
*/
return $slug;
}
/**
* #param string $value
*
* #return string|null
*/
public function resolve(string $value): ?string
{
/**
* returns the id of the category from the last path segment
*/
return $id;
}
}
For just a single category it works fine. But as soon as a subcategory is requested the code breaks. The link to the subcategory only contains the path segment of the subcategory, e.g. /subcategory1, but not the one of the category.
It's clear to me why that is, since I only add the id of the subcategory to LinkViewHelper: <f:link.action controller="Products" action="list" arguments="{id: 5}">Test Link</f:link.action>. But I can
Which screws do I need to adjust to make the URLs work also for any depth of subcategories?
Thanks in advance!

You have to allow / as requirements. The default requirement is [^/]+ and you specified only a number.
This should work:
ProductsPlugin:
requirements:
category_title: .+
See also Symfony-Docs: https://symfony.com/doc/4.1/routing/slash_in_parameter.html

Related

TYPO3 v9.5 Extbase Error Handling with routeEnhancers

In my Extbase TYPO3 Extension I want to show a custom fluid template when the record is not available anymore (hidden or deleted). The error handling loads a fluid template where the path is defined in the setup.typoscript.
But when I add the routEnhancers in site config.yaml file for my Extension then the Error handling doesnt work anymore and it just shows the default TYPO3 Error Page: "The requested page does not exist".
In the docs of the site config I didn't find any way to set a special error handling for my Ext.
Here is the Code that handles it so far:
Controller:
class RecordController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
/**
* Error handling if no entry is found
*
* #param string $configuration configuration what will be done
* #throws \InvalidArgumentException
* #return string
*/
protected function handleNoRecordFoundError($configuration)
{
$statusCode = HttpUtility::HTTP_STATUS_404;
HttpUtility::setResponseCode($statusCode);
$this->getTypoScriptFrontendController()->set_no_cache('Record record not found');
$standaloneTemplate = GeneralUtility::makeInstance(StandaloneView::class);
$standaloneTemplate->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($configuration));
return $standaloneTemplate->render();
}
/**
* #return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
/**
* action show
* #param \Digitalgizmo\Vehicles\Domain\Model\Vehicle $vehicle
* #return void
*
*
*/
public function showAction(\Vendor\MyExt\Domain\Model\Record $record = null)
{
if ($record !== null){
$this->view->assign('record', $record);
}
else {
$errorContent = $this->handleNoRecordFoundError($this->settings['show']['errorTemplate']);
if ($errorContent) {
return $errorContent;
}
}
}
}
config.yaml;
routeEnhancers:
MyExt:
type: Extbase
extension: MyExt
plugin: MyExt
routes:
-
routePath: '/staticName/{uid}/{slug}'
_controller: 'ControllerName::show'
_arguments:
slug: record
uid: id
defaultController: 'ControllerName::show'
aspects:
slug:
type: PersistedAliasMapper
tableName: tx_myext_domain_model_record
routeFieldName: slug
routeValuePrefix: /
uid:
type: PersistedAliasMapper
tableName: tx_myext_domain_model_record
routeFieldName: uid
The RoutEnhancer works just fine if the record is available.
How can I catch that error, so I can handle it and show my fluid template? My showAction isn't even being loaded (tested with XDebug). I assum this is because the TYPO3 core throws the error.
Everything seems fine with that code, the problem is that the RouteEnhancer is affected by the same constraints as your showAction: once the record is deleted, the resolve method in the routeEnhancer will no longer be able to find it nor its slug.
As a reference, see the resolve function in the API: https://api.typo3.org/9.5/_persisted_alias_mapper_8php_source.html . It instanciates a queryBuilder which, by default, builds a deleted=0 clause.
To get deleted redcords by their slug, what you need to do is build a custom RouteEnhancer, maybe by extending the PersistendAliasMapper class in a way that it also finds deleted records, refer https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Routing/ExtendingRouting.html , but be aware of the implications: the slug field in your model will no longer be able to find colliding slugs even with the eval=uniqueInSite option set because that, too, only sees non-deleted records.
Thanks to j4k3
I've created my own aspect type for the routeEnhancers, which removes the deleted and hidden constrains, so it won't throw an error.
I followed this documentation to create it: https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Routing/ExtendingRouting.html
Here is my CustomMapper Class.
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Routing\Aspect\PersistedAliasMapper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class CustomMapper extends PersistedAliasMapper
{
protected function createQueryBuilder(): QueryBuilder
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($this->tableName)
->from($this->tableName);
$queryBuilder->setRestrictions(
GeneralUtility::makeInstance(FrontendRestrictionContainer::class, $this->context)
);
// Frontend Groups are not available at this time (initialized via TSFE->determineId)
// So this must be excluded to allow access restricted records
$queryBuilder->getRestrictions()->removeByType(FrontendGroupRestriction::class);
$queryBuilder->getRestrictions()->removeByType(DeletedRestriction::class);
$queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
return $queryBuilder;
}
}
Basically the only thing I added was the removal of the DeletedRestriction and HiddenRestriction.
Further more I had to change the Slug field how it gets built. I added the uid of the dataset to the slug and removed the separate uid GET Parameter, so now the slug is unique in the database. Before I did that I had the problem that the query found multiple of the same slug values and it always took the first one.
And now since the slug is unique in the database table it will return the object and wont throw an error, so I can handle the "error" in the controller.

How to get fal object instead of array when using flux:field.inline.fal

In my flux content template configuration section I define an image field like this:
<flux:field.inline.fal label="Image" name="images" maxItems="1" minItems="0" showThumbs="1"/>
In my flux content template main section I debug the output:
<f:for each="{v:resource.record.fal(table: 'tt_content',field: 'images', uid: '{record.uid}')}" as="image" iteration="iterator">
<f:debug>{image}</f:debug>
</f:for>
The debuging output shows an array but what I need is the FAL object of that image I added in the backend.
I googled a lot and found some older posts from 2015. All say it is not possible to get the fal object(!) in flux. Is it still true? Do you know any way?
One solution is to create a custom ViewHelper:
<?php
namespace Cmichael\Typo3projectprovider\ViewHelpers\Content;
/* FalViewHelper
* To get fal object by image uid (respectivly in flux templates)
* Usage example: <cm:content.fal referenceUid="{image.uid}">
* */
class FalViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* #var bool
*/
protected $escapeOutput = false;
/**
* Initialize arguments.
*
*/
public function initializeArguments() {
$this->registerArgument('referenceUid', 'integer', 'File reference uid', true, 0);
}
/**
* Return file reference
*
* #return \TYPO3\CMS\Core\Resource\FileReference|null
*/
public function render() {
$referenceUid = $this->arguments['referenceUid'];
$fileReferenceData = $GLOBALS['TSFE']->sys_page->checkRecord('sys_file_reference', $referenceUid);
return $fileReferenceData ? \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($referenceUid) : $referenceUid;
}
}

Invalid text representation: 7 ERROR: invalid input syntax for uuid: "test"

I'm using Symfony 3.2 with doctrine and postgresql.
I've created an entity with a uuid as primary key.
My entity definition:
/**
* Booking
*
* #ORM\Table(name="booking")
* #ORM\Entity(repositoryClass="AppBundle\Repository\BookingRepository")
* #ORM\EntityListeners({"AppBundle\EventListener\BookingListener"})
*/
class Booking {
/**
* #var string
*
* #ORM\Column(type="guid")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
}
In my controller I have a show action like this:
/**
* #Route("booking/{id}", name="booking_show")
* #Method({"GET"})
*/
public function showAction(Request $request, Booking $booking) {
...
}
Everything seems to work fine, but when I try to load a route putting an wrong value as an ID (i.e. /booking/hello123), I receive a:
SQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for uuid: "hello123"
Instead I would expect a 404.
Is there a way to capture this exception and redirect to a 404 page?
You can make use of Route Requirements - you can specify what conditions your parameter need to match to "qualify" to a certain route. This requirement is a regex, so all you need to do is to write a regex for an UUID
/**
* #Route("booking/{id}", name="booking_show", requirements={"id": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"})
* #Method({"GET"})
*/
public function showAction(Request $request, Booking $booking) {
...
}
NOTE: Regex used above is just first result I found in Google for UUID regex, I didn't verify if it works
In the end if your id does not match regex, it does not match route and you should get 404.
you need to change the showaction
/**
* #Route("booking/{id}", name="booking_show")
* #Method({"GET"})
*/
public function showAction(Request $request,$bookingID) {
$em = $this->getDoctrine()->getManager();
$booking = $em->getRepository('AppBundle:Entity')->find(bookingID);
if(!$booking)
$this->createNotFoundException('No entity found with id :'.$bookingID);
...
}

Extend tx_news with 1 field without extension_builder

I try to extend the tx_news extension with the field imageright.
For that I found this tutorial: https://docs.typo3.org/typo3cms/extensions/news/2.2.1/Main/Tutorial/ExtendingNews/Index.html
The first step is to use extension_builder to add the field. As I already have a extension in where I want to implement the extension I do not want to use the extension_builder (also I tried it with a new extension and extend the news-model did not work - I have no clue how to do it right). However this are the steps I did:
In my extension my_template I added the folders and file: Classes/Domain/Model/News.php:
class MyTemplate_Domain_Model_News extends Tx_News_Domain_Model_News {
/**
* #var bool
*/
protected $imageright;
/**
* Returns the imageright
*
* #return bool $imageright
*/
public function getImageright() {
return $this->imageright;
}
/**
* Sets the sort
*
* #param bool $imageright
* #return void
*/
public function setImageright($imageright)
{
$this->imageright = $imageright;
}
}
?>
/Ressources/Private/extend-news.txt:
Domain/Model/News
Created the field imageright as tinyint in the table tx_news_domain_model_news (and added it to the SQL file)
I knew I have to create a TCA file in /Configuration/TCA/, but I have no clue how this should look like or what name it needs to have. I think this is the last step I need to make this working.
Also note the extension my_template was just a template, so before my changes there where no Classes and no TCA files.
Solution is to use this tutorial: http://www.lukasjakob.com/extend-a-typo3-extbase-model-with-custom-field/

template for list view with system categories

I have an extbase extension (TYPO3 7) with a simple model of a contact person.
The person has a name and a picture.
So far this is clear.
But every Person has a category (e.g. where he works. Office, Marketing etc.)
Therefor i use the system categories, as described here:
https://wiki.typo3.org/TYPO3_6.0#Adding_categories_to_own_models_without_using_Extension_Builder
When creating a person via web > list, i can assign a category.
Now the question for templating:
If i debug my contact person, i get the output like screen below.
I want to have a list where every category (headline) is shown with it's contact persons.
How to do this?
Is the logic for this only in the template or also in the controller?
Has anybody an example for this?
Best regards
Markus
I guess the required logic you need is possible with Fluid with using the GroupedFor ViewHelper and many others. Because a person can have multiple categories this would become a huge nesting of Viewhelpers so I can not recommend to use Fluid for this even if its possible. This kind of logics belong to the controllers, models and repositories.
There are multiple ways to solve this logic. Here is an example how to realize this in the controller...
Controller:
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\CategoryRepository
* #inject
*/
protected $categoryRespoitory = NULL;
/**
* action list
* #return void
*/
public function listAction()
{
$allCategories = $this->categoryRespoitory->findAll();
$categoriesWithContacts = [];
/** #var \TYPO3\CMS\Extbase\Domain\Model\Category $category */
foreach($allCategories as $category) {
$contactsInCategory= $this->contactRepository->findByCategory($category);
if($contactsInCategory->count()>0) {
$categoriesWithContacts[] = [
'category' => $category,
'contacts' => $contactsInCategory
];
}
}
$this->view->assignMultiple([
'categoriesWithContacts' => $categoriesWithContacts
]);
}
Injecting the CategoryRespository will required clearing cache in install tool or reinstalling the extension.
Maybe you need this function in your ContactRepository:
/**
* #param \TYPO3\CMS\Extbase\Domain\Model\Category $category
* #return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
*/
public function findByCategory(\TYPO3\CMS\Extbase\Domain\Model\Category $category) {
$query = $this->createQuery();
return $query->matching($query->contains('categories', $category))->execute();
}
Then in Fluid you can do something like this:
<f:for each="{categoriesWithContacts}" as="categoryWithContact">
{categoryWithContact.category.title}
<f:for each="{categoryWithContact.contacts}" as="contact">
{contact.name}
</f:for>
</f:for>