Keep filter during pagination

This commit is contained in:
Daniel Siepmann 2022-11-22 11:58:36 +00:00
parent 10df1eddc1
commit be56f0fd12
12 changed files with 156 additions and 93 deletions

View file

@ -6,6 +6,7 @@ use TYPO3\CMS\Core\EventDispatcher\EventDispatcher;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Annotation as Extbase; use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Service\ExtensionService;
use Wrm\Events\Domain\Model\Date; use Wrm\Events\Domain\Model\Date;
use Wrm\Events\Domain\Model\Dto\DateDemand; use Wrm\Events\Domain\Model\Dto\DateDemand;
use Wrm\Events\Domain\Model\Dto\DateDemandFactory; use Wrm\Events\Domain\Model\Dto\DateDemandFactory;
@ -42,11 +43,6 @@ class DateController extends AbstractController
*/ */
protected $categoryRepository; protected $categoryRepository;
/**
* @var EventDispatcher
*/
protected $eventDispatcher;
/** /**
* @var Factory * @var Factory
*/ */
@ -57,22 +53,34 @@ class DateController extends AbstractController
*/ */
protected $dataProcessing; protected $dataProcessing;
/**
* @var EventDispatcher
*/
protected $eventDispatcher;
/**
* @var ExtensionService
*/
protected $extensionService;
public function __construct( public function __construct(
DateDemandFactory $demandFactory, DateDemandFactory $demandFactory,
RegionRepository $regionRepository,
DateRepository $dateRepository, DateRepository $dateRepository,
RegionRepository $regionRepository,
CategoryRepository $categoryRepository, CategoryRepository $categoryRepository,
Factory $paginationFactory,
DataProcessingForModels $dataProcessing, DataProcessingForModels $dataProcessing,
EventDispatcher $eventDispatcher, EventDispatcher $eventDispatcher,
Factory $paginationFactory ExtensionService $extensionService
) { ) {
$this->demandFactory = $demandFactory; $this->demandFactory = $demandFactory;
$this->regionRepository = $regionRepository;
$this->dateRepository = $dateRepository; $this->dateRepository = $dateRepository;
$this->regionRepository = $regionRepository;
$this->categoryRepository = $categoryRepository; $this->categoryRepository = $categoryRepository;
$this->paginationFactory = $paginationFactory;
$this->dataProcessing = $dataProcessing; $this->dataProcessing = $dataProcessing;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->paginationFactory = $paginationFactory; $this->extensionService = $extensionService;
} }
protected function initializeAction(): void protected function initializeAction(): void
@ -82,6 +90,8 @@ class DateController extends AbstractController
$this->demandFactory->setContentObjectRenderer($contentObject); $this->demandFactory->setContentObjectRenderer($contentObject);
} }
$this->dataProcessing->setConfigurationManager($this->configurationManager); $this->dataProcessing->setConfigurationManager($this->configurationManager);
$this->handlePostRequest();
} }
/** /**
@ -95,14 +105,6 @@ class DateController extends AbstractController
$demand = $this->demandFactory->fromSettings($this->settings); $demand = $this->demandFactory->fromSettings($this->settings);
if ($search !== []) { if ($search !== []) {
$demand = DateDemand::createFromRequestValues($search, $this->settings); $demand = DateDemand::createFromRequestValues($search, $this->settings);
} elseif (
($this->request->hasArgument('searchword') && $this->request->getArgument('searchword') != '')
|| ($this->request->hasArgument('region') && $this->request->getArgument('region') != '')
|| ($this->request->hasArgument('start') && $this->request->getArgument('start') != '')
|| ($this->request->hasArgument('end') && $this->request->getArgument('end') != '')
|| ($this->request->hasArgument('events_search') && $this->request->getArgument('events_search') != [])
) {
$demand = $this->createDemandFromSearch();
} }
$dates = $this->dateRepository->findByDemand($demand); $dates = $this->dateRepository->findByDemand($demand);
@ -128,24 +130,6 @@ class DateController extends AbstractController
*/ */
public function searchAction(array $search = []): void public function searchAction(array $search = []): void
{ {
$arguments = GeneralUtility::_GET('tx_events_datelist') ?? $search;
if (is_array($arguments) === false) {
$arguments = [];
}
if (isset($arguments['events_search']) && is_array($arguments['events_search'])) {
$arguments += $arguments['events_search'];
unset($arguments['events_search']);
}
// For legacy systems.
$this->view->assignMultiple([
'searchword' => $arguments['searchword'] ?? '',
'selRegion' => $arguments['region'] ?? '',
'start' => $arguments['start'] ?? '',
'end' => $arguments['end'] ?? '',
'considerDate' => $arguments['considerDate'] ?? '',
]);
$demand = $this->demandFactory->fromSettings($this->settings); $demand = $this->demandFactory->fromSettings($this->settings);
if ($search !== []) { if ($search !== []) {
$demand = DateDemand::createFromRequestValues($search, $this->settings); $demand = DateDemand::createFromRequestValues($search, $this->settings);
@ -178,17 +162,27 @@ class DateController extends AbstractController
$this->view->assign('date', $date); $this->view->assign('date', $date);
} }
protected function createDemandFromSearch(): DateDemand /**
* Convert POST to proper GET.
*
* @see: https://en.wikipedia.org/wiki/Post/Redirect/Get
*/
private function handlePostRequest(): void
{ {
$arguments = $this->request->getArguments(); if (
if (isset($arguments['events_search'])) { $this->request->getMethod() === 'POST'
$arguments += $arguments['events_search']; && $this->request->hasArgument('search')
unset($arguments['events_search']); && is_array($this->request->getArgument('search'))
) {
$namespace = $this->extensionService->getPluginNamespace(null, null);
$this->redirectToUri($this->configurationManager->getContentObject()->typoLink_URL([
'parameter' => 't3://page?uid=current',
'additionalParams' => '&' . http_build_query([
$namespace => [
'search' => array_filter($this->request->getArgument('search'))
],
]),
]));
} }
return DateDemand::createFromRequestValues(
$arguments,
$this->settings
);
} }
} }

View file

@ -331,13 +331,18 @@ class DateDemand
return $this->startObject; return $this->startObject;
} }
public function getStart(): ?int /**
* Returns necessary format for forms.
*
* @internal Only for Extbase/Fluid.
*/
public function getStart(): string
{ {
if ($this->getStartObject() === null) { if ($this->getStartObject() === null) {
return null; return '';
} }
return (int) $this->getStartObject()->format('U'); return $this->getStartObject()->format('Y-m-d');
} }
public function setStart(?int $start): void public function setStart(?int $start): void
@ -362,13 +367,18 @@ class DateDemand
return $this->getStartObject()->format('Y-m-d') === $this->getEndObject()->format('Y-m-d'); return $this->getStartObject()->format('Y-m-d') === $this->getEndObject()->format('Y-m-d');
} }
public function getEnd(): ?int /**
* Returns necessary format for forms.
*
* @internal Only for Extbase/Fluid.
*/
public function getEnd(): string
{ {
if ($this->getEndObject() === null) { if ($this->getEndObject() === null) {
return null; return '';
} }
return (int) $this->getEndObject()->format('U'); return $this->getEndObject()->format('Y-m-d');
} }
public function setEnd(?int $end): void public function setEnd(?int $end): void
@ -387,15 +397,15 @@ class DateDemand
public function shouldShowFromNow(): bool public function shouldShowFromNow(): bool
{ {
return $this->getStart() === null return $this->getStartObject() === null
&& $this->getEnd() === null && $this->getEndObject() === null
&& $this->useMidnight === false; && $this->useMidnight === false;
} }
public function shouldShowFromMidnight(): bool public function shouldShowFromMidnight(): bool
{ {
return $this->getStart() === null return $this->getStartObject() === null
&& $this->getEnd() === null && $this->getEndObject() === null
&& $this->useMidnight === true; && $this->useMidnight === true;
} }

View file

@ -77,20 +77,20 @@ class DateRepository extends Repository
$constraints['userCategories'] = $query->in('event.categories.uid', $demand->getUserCategories()); $constraints['userCategories'] = $query->in('event.categories.uid', $demand->getUserCategories());
} }
if ($demand->getStart() !== null) { if ($demand->getStartObject() !== null) {
$constraints['starts'] = $query->greaterThanOrEqual('start', $demand->getStart()); $constraints['starts'] = $query->greaterThanOrEqual('start', $demand->getStartObject());
} }
if ($demand->getEnd() != null) { if ($demand->getEndObject() != null) {
// Dates might have end of 0 if only start exists. // Dates might have end of 0 if only start exists.
// This is respected to take start as end date. // This is respected to take start as end date.
$constraints['ends'] = $query->logicalOr([ $constraints['ends'] = $query->logicalOr([
$query->logicalAnd([ $query->logicalAnd([
$query->lessThanOrEqual('end', $demand->getEnd()), $query->lessThanOrEqual('end', $demand->getEndObject()),
$query->greaterThan('end', 0) $query->greaterThan('end', 0)
]), ]),
$query->logicalAnd([ $query->logicalAnd([
$query->equals('end', 0), $query->equals('end', 0),
$query->lessThanOrEqual('start', $demand->getEnd()) $query->lessThanOrEqual('start', $demand->getEndObject())
]), ]),
]); ]);
} }

View file

@ -23,14 +23,10 @@ plugin.tx_events {
recursive = 1 recursive = 1
} }
features { features {
#skipDefaultArguments = 1 skipDefaultArguments = 1
# if set to 1, the enable fields are ignored in BE context
ignoreAllEnableFieldsInBe = 0
# Should be on by default, but can be disabled if all action in the plugin are uncached
requireCHashArgumentForActionArguments = 0
} }
mvc { mvc {
#callDefaultActionIfActionCantBeResolved = 1 callDefaultActionIfActionCantBeResolved = 1
} }
settings { settings {
@ -74,4 +70,7 @@ plugin.tx_events {
} }
} }
plugin.tx_events_datelist.view.pluginNamespace = events_search
plugin.tx_events_datesearch.view.pluginNamespace = events_search
module.tx_events < plugin.tx_events module.tx_events < plugin.tx_events

View file

@ -0,0 +1,64 @@
3.0.0
=====
Breaking
--------
Namespace changes
^^^^^^^^^^^^^^^^^
A new namespace was defined for plugins which is "events_search".
The search parameters are now collected below namespace "search" instead of
"events_search" leading to ``events_search[search][parametername]=value`` instead of
``tx_events_signature[events_search][parametername]=value``.
The form now is submitted as post and redirects to a proper URL with GET.
The code was bloated and made it hard to fix bugs.
Necessary steps:
- Check usage of old namespace within templates and other sources.
- Check usage of old nesting of parameters.
API Changes
^^^^^^^^^^^
The methods of ``DateDemand`` have changed, ``getStart()`` and ``getEnd()`` return a
string value necessary or Fluid forms.
Those are not considered public API. Use ``getStartObject()`` and ``getEndObject()``
instead.
Features
--------
Nothing
Fixes
-----
* Keep filter during pagination
Search requests are POST by default.
We apply PRG (=Post Redirect Get) on them to create proper GET requests.
Those can be used to generate the URLs for pagination.
We follow Extbase, and do not explicitly ask for arguments from foreign namespaces.
Instead we configure a pluginNamespace that's shared between plugins.
This is all necessary as we still ship pre defined plugins.
This should belong into integration of each project.
See: https://en.wikipedia.org/wiki/Post/Redirect/Get
Relates: #10175
Tasks
-----
Nothing
Deprecation
-----------
Nothing

View file

@ -17,7 +17,7 @@
</trans-unit> </trans-unit>
<trans-unit id="tx_events.searchform.date_to"> <trans-unit id="tx_events.searchform.date_to">
<source>Date to</source> <source>Date to</source>
<target>Date bis</target> <target>Datum bis</target>
</trans-unit> </trans-unit>
<trans-unit id="tx_events.searchform.regions"> <trans-unit id="tx_events.searchform.regions">
<source>All regions</source> <source>All regions</source>

View file

@ -8,13 +8,13 @@
<f:if condition="{pagination.previousPageNumber} > 1"> <f:if condition="{pagination.previousPageNumber} > 1">
<f:then> <f:then>
<a class="page-link" <a class="page-link"
href="{f:uri.action(arguments: {currentPage: pagination.previousPageNumber})}" href="{f:uri.action(addQueryString: 1, arguments: {currentPage: pagination.previousPageNumber})}"
> >
<span aria-hidden="true">&laquo;</span> <span aria-hidden="true">&laquo;</span>
</a> </a>
</f:then> </f:then>
<f:else> <f:else>
<a class="page-link" href="{f:uri.action()}"> <a class="page-link" href="{f:uri.action(addQueryString: 1)}">
<span aria-hidden="true">&laquo;</span> <span aria-hidden="true">&laquo;</span>
</a> </a>
</f:else> </f:else>
@ -25,7 +25,7 @@
<f:if condition="{pagination.displayRangeStart} > 1"> <f:if condition="{pagination.displayRangeStart} > 1">
<li class="page-item"> <li class="page-item">
<a class="page-link" <a class="page-link"
href="{f:uri.action()}" href="{f:uri.action(addQueryString: 1)}"
aria-label="Goto Page 1" aria-label="Goto Page 1"
> >
1 1
@ -54,7 +54,7 @@
<f:if condition="{page} > 1"> <f:if condition="{page} > 1">
<f:then> <f:then>
<a class="page-link" <a class="page-link"
href="{f:uri.action(arguments: {currentPage: page})}" href="{f:uri.action(addQueryString: 1, arguments: {currentPage: page})}"
aria-label="Goto Page {page}" aria-label="Goto Page {page}"
> >
{page} {page}
@ -62,7 +62,7 @@
</f:then> </f:then>
<f:else> <f:else>
<a class="page-link" <a class="page-link"
href="{f:uri.action()}" href="{f:uri.action(addQueryString: 1)}"
aria-label="Goto Page 1" aria-label="Goto Page 1"
> >
1 1
@ -83,7 +83,7 @@
<f:if condition="{pagination.displayRangeEnd} < {pagination.lastPageNumber}"> <f:if condition="{pagination.displayRangeEnd} < {pagination.lastPageNumber}">
<li class="page-item"> <li class="page-item">
<a class="page-link" <a class="page-link"
href="{f:uri.action(arguments: {currentPage: pagination.lastPageNumber})}" href="{f:uri.action(addQueryString: 1, arguments: {currentPage: pagination.lastPageNumber})}"
aria-label="Goto Page {pagination.lastPageNumber}" aria-label="Goto Page {pagination.lastPageNumber}"
> >
{pagination.lastPageNumber} {pagination.lastPageNumber}
@ -94,7 +94,7 @@
<f:if condition="{pagination.nextPageNumber}"> <f:if condition="{pagination.nextPageNumber}">
<li class="page-item"> <li class="page-item">
<a class="page-link" <a class="page-link"
href="{f:uri.action(arguments: {currentPage: pagination.nextPageNumber})}" href="{f:uri.action(addQueryString: 1, arguments: {currentPage: pagination.nextPageNumber})}"
> >
<span aria-hidden="true">&raquo;</span> <span aria-hidden="true">&raquo;</span>
</a> </a>

View file

@ -3,14 +3,14 @@
<f:section name="content"> <f:section name="content">
<div class="row"> <div class="row">
<div class="col-12 mb-5"> <div class="col-12 mb-5">
<f:form action="list" controller="Date" pluginName="DateList" method="get" id="events_search" name="events_search" object="{demand}"> <f:form action="list" controller="Date" pluginName="DateList" method="post" id="events_search" name="search" object="{demand}">
<div class="row"> <div class="row">
<div class="col-12 col-md-12 col-lg-6"> <div class="col-12 col-md-12 col-lg-6">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="form-group"> <div class="form-group">
<label for="searchword"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.searchword" /></label> <label for="searchword"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.searchword" /></label>
<f:form.textfield type="text" class="form-control" id="searchword" name="searchword" value="{searchword}" /> <f:form.textfield type="text" class="form-control" id="searchword" property="searchword" value="{searchword}" />
</div> </div>
</div> </div>
</div> </div>
@ -18,23 +18,13 @@
<div class="col col-md-6"> <div class="col col-md-6">
<div class="form-group"> <div class="form-group">
<label for="start"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.date_from" /></label> <label for="start"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.date_from" /></label>
<div class="input-group date" id="date_start" data-target-input="nearest"> <f:form.textfield type="date" class="form-control" id="start" property="start" />
<f:form.textfield type="text" class="form-control datetimepicker-input" id="start" name="start" value="{start}" additionalAttributes="{data-target: '#date_start'}" />
<div class="input-group-append" data-target="#date_start" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
</div>
</div>
</div> </div>
</div> </div>
<div class="col col-md-6"> <div class="col col-md-6">
<div class="form-group"> <div class="form-group">
<label for="end"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.date_to" /></label> <label for="end"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.date_to" /></label>
<div class="input-group date" id="date_end" data-target-input="nearest"> <f:form.textfield type="date" class="form-control" id="end" property="end" />
<f:form.textfield type="text" class="form-control datetimepicker-input" id="start" name="end" value="{end}" additionalAttributes="{data-target: '#date_end'}" />
<div class="input-group-append" data-target="#date_end" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -44,7 +34,7 @@
<div class="row mt-3"> <div class="row mt-3">
<div class="col-4 col-md-4 col-lg-4"> <div class="col-4 col-md-4 col-lg-4">
<div class="form-check"> <div class="form-check">
<f:form.radio class="form-check-input" name="region" value="{region.uid}" checked="{selRegion}==0" id="radio_0"/> <f:form.radio class="form-check-input" property="region" id="radio_0" value="" />
<label class="form-check-label" for="radio_0"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.regions" /></label> <label class="form-check-label" for="radio_0"><f:translate key="LLL:EXT:events/Resources/Private/Language/locallang.xlf:tx_events.searchform.regions" /></label>
</div> </div>
</div> </div>
@ -52,7 +42,7 @@
<f:for each="{regions}" as="region"> <f:for each="{regions}" as="region">
<div class="col-4"> <div class="col-4">
<div class="form-check"> <div class="form-check">
<f:form.radio class="form-check-input" name="region" value="{region.uid}" checked="{selRegion}=={region.uid}" id="radio_{region.uid}"/> <f:form.radio class="form-check-input" property="region" value="{region.uid}" id="radio_{region.uid}"/>
<label class="form-check-label" for="radio_{region.uid}">{region.title}</label> <label class="form-check-label" for="radio_{region.uid}">{region.title}</label>
</div> </div>
</div> </div>

View file

@ -201,7 +201,7 @@ class DateDemandTest extends TestCase
$result->getStartObject()->format('Y-m-d') $result->getStartObject()->format('Y-m-d')
); );
self::assertSame( self::assertSame(
1657576800, '2022-07-12',
$result->getStart() $result->getStart()
); );
} }
@ -228,7 +228,7 @@ class DateDemandTest extends TestCase
$result->getEndObject()->format('Y-m-d') $result->getEndObject()->format('Y-m-d')
); );
self::assertSame( self::assertSame(
1657663140, '2022-07-12',
$result->getEnd() $result->getEnd()
); );
} }

View file

@ -9,7 +9,7 @@ $EM_CONF['events'] = [
'state' => 'alpha', 'state' => 'alpha',
'createDirs' => '', 'createDirs' => '',
'clearCacheOnLoad' => 0, 'clearCacheOnLoad' => 0,
'version' => '2.6.3', 'version' => '3.0.0',
'constraints' => [ 'constraints' => [
'depends' => [ 'depends' => [
'typo3' => '10.4.00-11.5.99', 'typo3' => '10.4.00-11.5.99',

View file

@ -37,6 +37,8 @@ call_user_func(function () {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['events_category'] = []; $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['events_category'] = [];
} }
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = '^events_search';
$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class); $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
$iconRegistry->registerIcon( $iconRegistry->registerIcon(
'events-plugin', 'events-plugin',

View file

@ -1,5 +1,9 @@
parameters: parameters:
ignoreErrors: ignoreErrors:
-
message: "#^Cannot call method typoLink_URL\\(\\) on TYPO3\\\\CMS\\\\Frontend\\\\ContentObject\\\\ContentObjectRenderer\\|null\\.$#"
count: 1
path: Classes/Controller/DateController.php
- -
message: "#^Parameter \\#1 \\$categories of method Wrm\\\\Events\\\\Domain\\\\Model\\\\Event\\:\\:setCategories\\(\\) expects TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<Wrm\\\\Events\\\\Domain\\\\Model\\\\Category\\>, TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<mixed\\> given\\.$#" message: "#^Parameter \\#1 \\$categories of method Wrm\\\\Events\\\\Domain\\\\Model\\\\Event\\:\\:setCategories\\(\\) expects TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<Wrm\\\\Events\\\\Domain\\\\Model\\\\Category\\>, TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<mixed\\> given\\.$#"
count: 1 count: 1