1

I am building an extension (Classified Ads). The FrontEnd user has the option to set his Ad to hidden or the Ad gets the hidden status after some time (Ad expired). I faced two problems:

  1. if i try to call it on the showAction() then TYPO3 gives back an error saying that the Object hasn't been found, which makes sense since there are restrictions.
  2. In the list action the URL to the show action won't render since the persistedAliasMapper aspect sents a request including the same restrictions.

How do i get these problems solved so the user can edit his Ad?

Environment:

  • TYPO3: 10
  • MODE: composer
  • PHP: 7.4
Aristeidis Karavas
  • 1,891
  • 10
  • 29

1 Answers1

0

First we have to remove the restriction so the URL in the listAction() can be rendered. It is quite easy since the aspect uses a function createQueryBuilder() so the only thing to do here is to just override the function and remove the Hidden restriction. In order to do that, we have to extend the PersistedAliasMapper class. First we have to declare the class.

your_extension/ext_localconf.php

$GLOBALS['TYPO3_CONF_VARS']['SYS']['routing']['aspects']['AdPersistedAliasMapper'] = \Vendor\YourExtension\Routing\AdPersistedAliasMapper::class;

Next step is to actually extend the class. We are not going to override the restrictions every time but we include one more field on the PersistedAliasMapper arguments so we can use it on specific configurations.

your_extension/Classes/Routing/AdPersistedAliasMapper.php

<?php
namespace Vendor\YourExtension\Routing;

use InvalidArgumentException;
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\Routing\Aspect\PersistedAliasMapper;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class AdPersistedAliasMapper extends PersistedAliasMapper
{
    /**
     * @var bool
     */
    protected $ignoreEnablefields;

    /**
     * @param array $settings
     * @throws InvalidArgumentException
     */
    public function __construct(array $settings)
    {
        $ignoreEnablefields = $settings['ignoreEnablefields'] ?? false;
        $this->ignoreEnablefields = $ignoreEnablefields;
        parent::__construct($settings);
    }

    protected function createQueryBuilder(): QueryBuilder
    {
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable($this->tableName)
            ->from($this->tableName);
        if ($this->ignoreEnablefields) {
            $queryBuilder
                ->getRestrictions()
                ->removeAll()
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
        }
        else {
            $queryBuilder->setRestrictions(
                GeneralUtility::makeInstance(FrontendRestrictionContainer::class, $this->context)
            );
        }
        $queryBuilder->getRestrictions()->removeByType(FrontendGroupRestriction::class);
        return $queryBuilder;
    }
}

What this does, is to evaluate if the field ignoreEnablefields is defined in the config.yaml. If yes, then this

$queryBuilder
      ->getRestrictions()
      ->removeAll()
      ->add(GeneralUtility::makeInstance(DeletedRestriction::class));

will remove all the restriction and add the DeletedRestriction back again. You can do the opposite if you want to get the deleted and not the hidden. Or you can just remove everything and all objects's URL will be rendered.

If the ignoreEnablefields is not set, the TYPO3 will continue with the normal behaviour.

You can now use the following configuration in your config.yaml:

config/sites/yourIdentifier/config.yaml

Ad:
    type: Extbase
    extension: YourExtension
    plugin: Yourextension
    routes:
      - routePath: '/{ad}'
         _controller: 'Ad::show'
         _arguments:
            ad: ad
    aspects:
      ad:
        type: AdPersistedAliasMapper
        tableName: tx_yourextension_domain_model_ad
        routeFieldName: path_segment
        ignoreEnablefields: true

Now we have to remove the restrictions from getting the Object and avoid the error "Object with the UID x has not been found". My detailAction looks like this:

/**
 * action show
 *
 * @param Ad $ad
 * @return void
 */
public function showAction(Ad $ad): void
{ }

What TYPO3 normally does is to get the uid and send a request to the server in order to retrieve the object. This is where the error comes from. Since the object is set to hidden, the default restrictions search for the UID and hidden=0 and deleted=0. TYPO3 does not find anything so it breaks. But we can avoid that by getting the object before it reaches the showAction. In order to do that we use the TYPO3 default initialize method prefix. So the following has to be set on your controller:

your_extension/Classes/Controller/AdController.php

protected function initializeShowAction(): void
{
    $adUid = $this->request->getArguments()['ad'];
    $ad = $this->adRepository->findByUidAndHidden($adUid);
}

What this does, is to assing the hidden object to the $ad variable so when the showAction() is called, the $ad variable already contains the object which is what the action expects as parameter.

Now the findByUidAndHidden() method is not a TYPO3 default function so we have to create it.

your_extension/Classes/Domain/Repository/AdRepository.php

public function findByUidAndHidden($uid)
{
    $query = $this->createQuery();
    $query->getQuerySettings()->setIgnoreEnableFields(array('hidden'));
    $query->matching(
        $query->equals('uid', (int)$uid)
    );
    return $query->execute()[0];
}

What this does, is to create a query which does not respect the hidden field. Meaning that the hidden column won't be taken into consideration when the request is sent. The function could use the findByIdentifier() function instead. The [0] just brings the first entry of the array response back since there is always only 1 result (if the object really exists).

The same thing you can use for the edit or update function.

Best regards

Aristeidis Karavas
  • 1,891
  • 10
  • 29
  • How does assigning the variable $ad in the initializeShowAction carry over the value to the showAction? Isn't there a step missing in between? – Ceremony Jun 23 '21 at 10:07
  • you could just remove the parameter from the showAction and use the findByUidAndHidden inside the function. – Aristeidis Karavas Jun 23 '21 at 10:45
  • This would mean I would have to manually remap the changed values that are sent in the updateAction. Not really a good solution... – Ceremony Jun 23 '21 at 11:07
  • @Ceremony I stuck exactly at this point. Did you find a solution? – chris Jan 08 '22 at 21:27