mirror of
https://github.com/werkraum-media/events.git
synced 2025-03-24 05:23:46 +01:00
Support pagination with search parameters
Ensure the parameters are passed on to new generated links. Cover things with tests. Relates: #11574
This commit is contained in:
parent
56d922cb3a
commit
4e1d158923
24 changed files with 757 additions and 17 deletions
Classes
Configuration
Documentation
Tests/Functional/Frontend
Fixtures
Database
Extensions
ce_filter
Configuration/TypoScript
Resources/Private/Templates/Date
composer.jsonext_emconf.phpext_localconf.phpce_list
example
Sites/default
|
@ -7,6 +7,7 @@ namespace WerkraumMedia\Events\Controller;
|
|||
use Exception;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
use TYPO3\CMS\Core\Http\PropagateResponseException;
|
||||
use TYPO3\CMS\Extbase\Annotation as Extbase;
|
||||
use TYPO3\CMS\Extbase\Service\ExtensionService;
|
||||
use WerkraumMedia\Events\Domain\Model\Date;
|
||||
|
@ -38,13 +39,13 @@ final class DateController extends AbstractController
|
|||
{
|
||||
parent::initializeAction();
|
||||
|
||||
$this->handlePostRequests();
|
||||
|
||||
$contentObject = $this->request->getAttribute('currentContentObject');
|
||||
if ($contentObject !== null) {
|
||||
$this->demandFactory->setContentObjectRenderer($contentObject);
|
||||
}
|
||||
$this->dataProcessing->setConfigurationManager($this->configurationManager);
|
||||
|
||||
$this->handlePostRequest();
|
||||
}
|
||||
|
||||
public function listAction(
|
||||
|
@ -127,22 +128,34 @@ final class DateController extends AbstractController
|
|||
*
|
||||
* @see: https://en.wikipedia.org/wiki/Post/Redirect/Get
|
||||
*/
|
||||
private function handlePostRequest(): void
|
||||
private function handlePostRequests(): void
|
||||
{
|
||||
if (
|
||||
$this->request->getMethod() === 'POST'
|
||||
&& $this->request->hasArgument('search')
|
||||
&& is_array($this->request->getArgument('search'))
|
||||
) {
|
||||
$namespace = $this->extensionService->getPluginNamespace(null, null);
|
||||
if ($this->request->getMethod() !== 'POST') {
|
||||
return;
|
||||
}
|
||||
|
||||
$searchArguments = [];
|
||||
if ($this->request->hasArgument('search')) {
|
||||
$searchArguments = $this->request->getArgument('search');
|
||||
}
|
||||
if (is_array($searchArguments) === false) {
|
||||
$searchArguments = [];
|
||||
}
|
||||
$searchArguments = array_filter($searchArguments);
|
||||
|
||||
$parameter = [];
|
||||
if ($searchArguments !== []) {
|
||||
$parameter['search'] = $searchArguments;
|
||||
}
|
||||
|
||||
$namespace = $this->extensionService->getPluginNamespace(null, null);
|
||||
|
||||
throw new PropagateResponseException(
|
||||
$this->redirectToUri($this->request->getAttribute('currentContentObject')->typoLink_URL([
|
||||
'parameter' => 't3://page?uid=current',
|
||||
'additionalParams' => '&' . http_build_query([
|
||||
$namespace => [
|
||||
'search' => array_filter($this->request->getArgument('search')),
|
||||
],
|
||||
]),
|
||||
]));
|
||||
}
|
||||
'additionalParams' => '&' . http_build_query([$namespace => $parameter]),
|
||||
])),
|
||||
303
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2025 Daniel Siepmann <daniel.siepmann@codappix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\Events\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Routing\PageArguments;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* Right now I couldn't find any way to add the search parameter to the pagination via addQueryString.
|
||||
* The issue is that these are not part of TYPO3 routing. Nested lists like categories also seem impossible to configure for routing.
|
||||
*
|
||||
* See corresponding core issue: https://forge.typo3.org/issues/105941
|
||||
*/
|
||||
final class AddSearchArgumentsToRouteArgumentsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $routingPath = 'events/search',
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): ResponseInterface {
|
||||
$previousResult = $request->getAttribute('routing', null);
|
||||
if ($previousResult instanceof PageArguments) {
|
||||
$request = $request->withAttribute('routing', $this->extendPageArguments($previousResult));
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
private function extendPageArguments(PageArguments $pageArguments): PageArguments
|
||||
{
|
||||
$dynamicArguments = $pageArguments->getDynamicArguments();
|
||||
|
||||
if (ArrayUtility::isValidPath($dynamicArguments, $this->routingPath) === false) {
|
||||
return $pageArguments;
|
||||
}
|
||||
|
||||
$routeArguments = ArrayUtility::setValueByPath(
|
||||
$pageArguments->getRouteArguments(),
|
||||
$this->routingPath,
|
||||
ArrayUtility::getValueByPath($dynamicArguments, $this->routingPath)
|
||||
);
|
||||
$remainingArguments = ArrayUtility::removeByPath($dynamicArguments, $this->routingPath);
|
||||
|
||||
return new PageArguments(
|
||||
$pageArguments->getPageId(),
|
||||
$pageArguments->getPageType(),
|
||||
$routeArguments,
|
||||
$pageArguments->getStaticArguments(),
|
||||
$remainingArguments,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@ routeEnhancers:
|
|||
EventsPagination:
|
||||
type: Plugin
|
||||
namespace: events
|
||||
routePath: '/{localizedPage}-{currentPage}'
|
||||
routePath: '/{localizedPage}-{currentPage}/{controller}'
|
||||
defaults:
|
||||
controller: 'Date'
|
||||
aspects:
|
||||
localizedPage:
|
||||
type: LocaleModifier
|
||||
|
|
19
Configuration/RequestMiddlewares.php
Normal file
19
Configuration/RequestMiddlewares.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use WerkraumMedia\Events\Middleware\AddSearchArgumentsToRouteArgumentsMiddleware;
|
||||
|
||||
return [
|
||||
'frontend' => [
|
||||
'werkraummedia/events/add-search-arguments-to-route-arguments' => [
|
||||
'target' => AddSearchArgumentsToRouteArgumentsMiddleware::class,
|
||||
'after' => [
|
||||
'typo3/cms-frontend/page-resolver',
|
||||
],
|
||||
'before' => [
|
||||
'typo3/cms-frontend/page-argument-validator',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
|
@ -44,6 +44,9 @@ Fixes
|
|||
* Ensure pagination settings are provided in expected type (int).
|
||||
TypoScript settings will always be strings, so we fix this with a proper type cast.
|
||||
|
||||
* Fix broken pagination routing in combination with active search for TYPO3 12.4 and onwards.
|
||||
See: :ref:`searchPagination`.
|
||||
|
||||
Tasks
|
||||
-----
|
||||
|
||||
|
|
25
Documentation/Features/SearchPagination.rst
Normal file
25
Documentation/Features/SearchPagination.rst
Normal file
|
@ -0,0 +1,25 @@
|
|||
.. index:: single: Search
|
||||
.. _searchPagination:
|
||||
|
||||
Search Pagination
|
||||
=================
|
||||
|
||||
The extension supports pagination of search results.
|
||||
Please properly configure the system. Add the following `TYPO3_CONF_VARS` configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
'FE' => [
|
||||
'cacheHash' => [
|
||||
'excludedParameters' => [
|
||||
'^events[search]',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
Adopt the configuration to your own setup, e.g. change the `pluginNamespace` from `events` to whatever you've configured.
|
||||
And ensure the involved plugins are excluded from caching (`USER_INT`).
|
||||
|
||||
The extension will assume `events[search]` as default namespace for search arguments.
|
||||
Please make use of Services files and Dependency Injection to configure the custom
|
||||
`AddSearchArgumentsToRouteArgumentsMiddleware` middleware with your own namespace.
|
57
Tests/Functional/Frontend/Fixtures/Database/SearchSetup.php
Normal file
57
Tests/Functional/Frontend/Fixtures/Database/SearchSetup.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'tt_content' => [
|
||||
[
|
||||
'pid' => '1',
|
||||
'uid' => '1',
|
||||
'CType' => 'cefilter_filter',
|
||||
'header' => 'Search Form',
|
||||
],
|
||||
[
|
||||
'pid' => '1',
|
||||
'uid' => '2',
|
||||
'CType' => 'celist_list',
|
||||
'header' => 'Search Results',
|
||||
],
|
||||
],
|
||||
'tx_events_domain_model_event' => [
|
||||
[
|
||||
'uid' => '1',
|
||||
'pid' => '2',
|
||||
'title' => 'Event one',
|
||||
'teaser' => 'Some Teaser',
|
||||
],
|
||||
[
|
||||
'uid' => '2',
|
||||
'pid' => '2',
|
||||
'title' => 'Event two',
|
||||
'teaser' => 'Another teaser',
|
||||
],
|
||||
],
|
||||
'tx_events_domain_model_date' => [
|
||||
[
|
||||
'uid' => '1',
|
||||
'pid' => '2',
|
||||
'event' => '1',
|
||||
'start' => '1661626800',
|
||||
'end' => '1661632200',
|
||||
],
|
||||
[
|
||||
'uid' => '2',
|
||||
'pid' => '2',
|
||||
'event' => '1',
|
||||
'start' => '1660158000',
|
||||
'end' => '1660163400',
|
||||
],
|
||||
[
|
||||
'uid' => '3',
|
||||
'pid' => '2',
|
||||
'event' => '2',
|
||||
'start' => '1661194800',
|
||||
'end' => '1661200200',
|
||||
],
|
||||
],
|
||||
];
|
|
@ -0,0 +1,9 @@
|
|||
tt_content.cefilter_filter.20 {
|
||||
view {
|
||||
pluginNamespace = events
|
||||
}
|
||||
|
||||
persistence {
|
||||
storagePid = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 offset-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<f:form method="post" name="search" object="{demand}">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="searchword">Searchword</label>
|
||||
<f:form.textfield type="text" class="form-control" id="searchword" property="searchword" value="{searchword}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="start">From:</label>
|
||||
<f:form.textfield type="date" class="form-control" id="start" property="start" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="end">To:</label>
|
||||
<f:form.textfield type="date" class="form-control" id="end" property="end" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<f:if condition="{categories}">
|
||||
<div class="row mb-3">
|
||||
<f:for each="{categories}" as="category">
|
||||
<div class="col-md-4 d-none d-lg-block">
|
||||
<div class="form-check">
|
||||
<f:form.checkbox class="form-check-input" property="userCategories" value="{category.uid}" id="check_{category.uid}"/>
|
||||
<label class="form-check-label" for="check_{category.uid}">{category.title} {category.amountOfEvents}</label>
|
||||
</div>
|
||||
</div>
|
||||
</f:for>
|
||||
</div>
|
||||
</f:if>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<f:form.submit value="Search" class="btn btn-primary" />
|
||||
</div>
|
||||
</f:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "werkraummedia/ce-events-filter",
|
||||
"type": "typo3-cms-extension",
|
||||
"description": "Content element: EXT:events filter",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"version": "v1.0.0",
|
||||
"require": {
|
||||
"typo3/cms-core": "*",
|
||||
"typo3/cms-extbase": "*"
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "ce_filter"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$EM_CONF['ce_filter'] = [
|
||||
'title' => 'Events filter',
|
||||
'description' => 'Content Element for \'Events\'',
|
||||
'category' => 'plugin',
|
||||
'version' => '1.0.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '*',
|
||||
'events' => '*',
|
||||
],
|
||||
'conflicts' => [
|
||||
],
|
||||
'suggests' => [
|
||||
],
|
||||
],
|
||||
];
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
|
||||
'@import "EXT:ce_filter/Configuration/TypoScript/Setup.typoscript"'
|
||||
);
|
||||
|
||||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||
'CeFilter',
|
||||
'filter',
|
||||
[
|
||||
\WerkraumMedia\Events\Controller\DateController::class => 'search',
|
||||
],
|
||||
[
|
||||
\WerkraumMedia\Events\Controller\DateController::class => 'search',
|
||||
],
|
||||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
|
||||
);
|
|
@ -0,0 +1,15 @@
|
|||
tt_content.celist_list.20 {
|
||||
view {
|
||||
pluginNamespace = events
|
||||
}
|
||||
|
||||
persistence {
|
||||
storagePid = 2
|
||||
}
|
||||
|
||||
settings {
|
||||
sortByDate = start
|
||||
sortOrder = ASC
|
||||
itemsPerPage = 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true">
|
||||
|
||||
<nav role="navigation" aria-label="Pagination Navigation">
|
||||
<ul class="pagination">
|
||||
<f:if condition="{pagination.previousPageNumber}">
|
||||
<li class="page-item">
|
||||
<f:if condition="{pagination.previousPageNumber} > 1">
|
||||
<f:then>
|
||||
<a class="page-link"
|
||||
href="{f:uri.action(addQueryString: 1, arguments: {currentPage: pagination.previousPageNumber})}"
|
||||
>
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<a class="page-link" href="{f:uri.action(addQueryString: 1)}">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</li>
|
||||
</f:if>
|
||||
|
||||
<f:if condition="{pagination.displayRangeStart} > 1">
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
href="{f:uri.action(addQueryString: 1)}"
|
||||
aria-label="Goto Page 1"
|
||||
>
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
</f:if>
|
||||
|
||||
<f:if condition="{pagination.hasLessPages}">
|
||||
<li class="page-item">
|
||||
<span class="page-link">…</span>
|
||||
</li>
|
||||
</f:if>
|
||||
|
||||
<f:for each="{pagination.allPageNumbers}" as="page">
|
||||
<f:if condition="{page} == {pagination.paginator.currentPageNumber}">
|
||||
<f:then>
|
||||
<li class="page-item active">
|
||||
<span class="page-link"
|
||||
aria-label="Current Page {page}"
|
||||
aria-current="true"
|
||||
>{page}</span>
|
||||
</li>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
href="{f:uri.action(addQueryString: 1, arguments: {currentPage: page})}"
|
||||
aria-label="Goto Page {page}"
|
||||
>
|
||||
{page}
|
||||
</a>
|
||||
</li>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:for>
|
||||
|
||||
<f:if condition="{pagination.hasMorePages}">
|
||||
<li class="page-item">
|
||||
<span class="page-link">…</span>
|
||||
</li>
|
||||
</f:if>
|
||||
|
||||
<f:if condition="{pagination.displayRangeEnd} < {pagination.lastPageNumber}">
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
href="{f:uri.action(addQueryString: 1, arguments: {currentPage: pagination.lastPageNumber})}"
|
||||
aria-label="Goto Page {pagination.lastPageNumber}"
|
||||
>
|
||||
{pagination.lastPageNumber}
|
||||
</a>
|
||||
</li>
|
||||
</f:if>
|
||||
|
||||
<f:if condition="{pagination.nextPageNumber}">
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
href="{f:uri.action(addQueryString: 1, arguments: {currentPage: pagination.nextPageNumber})}"
|
||||
>
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</f:if>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
|
||||
<f:if condition="{pagination.paginator.paginatedItems}">
|
||||
<section class="events-list" data-uniform="height">
|
||||
|
||||
<f:for each="{pagination.paginator.paginatedItems}" as="date" iteration="index">
|
||||
<div class="row mb-5">
|
||||
<h3>
|
||||
{date.event.title}
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<f:format.crop maxCharacters="160" respectHtml="true"
|
||||
respectWordBoundaries="true">
|
||||
{date.event.teaser}</f:format.crop>
|
||||
</p>
|
||||
</div>
|
||||
</f:for>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="events-list-pagination">
|
||||
{f:render(partial: 'Pagination', arguments: {
|
||||
pagination: pagination
|
||||
})}
|
||||
</section>
|
||||
</f:if>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "werkraummedia/ce-list",
|
||||
"type": "typo3-cms-extension",
|
||||
"description": "Content element: EXT:events list",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"version": "v1.0.0",
|
||||
"require": {
|
||||
"typo3/cms-core": "*",
|
||||
"typo3/cms-extbase": "*"
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "ce_list"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$EM_CONF['ce_list'] = [
|
||||
'title' => 'Events list view',
|
||||
'description' => 'Content Elements for \'Events\'',
|
||||
'category' => 'plugin',
|
||||
'version' => '1.0.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '*',
|
||||
'events' => '*',
|
||||
],
|
||||
'conflicts' => [
|
||||
],
|
||||
'suggests' => [
|
||||
],
|
||||
],
|
||||
];
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
|
||||
'@import "EXT:ce_list/Configuration/TypoScript/Setup.typoscript"'
|
||||
);
|
||||
|
||||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||
'CeList',
|
||||
'list',
|
||||
[
|
||||
\WerkraumMedia\Events\Controller\DateController::class => 'list',
|
||||
],
|
||||
[
|
||||
\WerkraumMedia\Events\Controller\DateController::class => 'list',
|
||||
],
|
||||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
|
||||
);
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2025 Daniel Siepmann <daniel.siepmann@codappix.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\EventsExample;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
||||
|
||||
/**
|
||||
* Allows us to set a specific DateTimeAspect for requests within tests.
|
||||
*/
|
||||
final class TestingDateTimeAspectMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Context $context,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): ResponseInterface {
|
||||
$testingDateAspect = $request->getAttribute('testingDateAspect');
|
||||
if ($testingDateAspect instanceof DateTimeAspect) {
|
||||
$this->context->setAspect('date', $testingDateAspect);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use WerkraumMedia\EventsExample\TestingDateTimeAspectMiddleware;
|
||||
|
||||
return [
|
||||
'frontend' => [
|
||||
'werkraummedia/events/testing-date-time-aspect' => [
|
||||
'target' => TestingDateTimeAspectMiddleware::class,
|
||||
'before' => [
|
||||
'typo3/cms-frontend/timetracker',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator) {
|
||||
$services = $containerConfigurator->services()
|
||||
->defaults()
|
||||
->autowire()
|
||||
->autoconfigure()
|
||||
;
|
||||
|
||||
$services->load('WerkraumMedia\\EventsExample\\', '../Classes/*');
|
||||
};
|
|
@ -14,3 +14,6 @@ languages:
|
|||
flag: us
|
||||
websiteTitle: ''
|
||||
rootPageId: 1
|
||||
imports:
|
||||
-
|
||||
resource: 'EXT:events/Configuration/CeRouting.yaml'
|
||||
|
|
141
Tests/Functional/Frontend/SearchTest.php
Normal file
141
Tests/Functional/Frontend/SearchTest.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WerkraumMedia\Events\Tests\Functional\Frontend;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
||||
use TYPO3\CMS\Core\Http\StreamFactory;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
|
||||
|
||||
final class SearchTest extends AbstractFrontendTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->testExtensionsToLoad = [
|
||||
...$this->testExtensionsToLoad,
|
||||
'typo3conf/ext/events/Tests/Functional/Frontend/Fixtures/Extensions/ce_filter/',
|
||||
'typo3conf/ext/events/Tests/Functional/Frontend/Fixtures/Extensions/ce_list/',
|
||||
];
|
||||
|
||||
ArrayUtility::mergeRecursiveWithOverrule($this->configurationToUseInTestInstance, [
|
||||
'FE' => [
|
||||
'cacheHash' => [
|
||||
'excludedParameters' => [
|
||||
'^events[search]',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->importPHPDataSet(__DIR__ . '/Fixtures/Database/SearchSetup.php');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function submittingPostWithoutSearchArgumentsRedirectsToGet(): void
|
||||
{
|
||||
$request = new InternalRequest('https://example.com/');
|
||||
$request = $request->withMethod('POST');
|
||||
$request = $request->withPageId(1);
|
||||
|
||||
$response = $this->executeFrontendSubRequest($request);
|
||||
|
||||
self::assertSame(303, $response->getStatusCode());
|
||||
self::assertSame('http://example.com/', $response->getHeaderLine('location'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function submittingPostWithSearchArgumentsRedirectsToGet(): void
|
||||
{
|
||||
$request = new InternalRequest('https://example.com/');
|
||||
$request = $request->withMethod('POST');
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withBody((new StreamFactory())->createStream(http_build_query([
|
||||
'events' => [
|
||||
'search' => [
|
||||
'searchword' => 'Event',
|
||||
],
|
||||
],
|
||||
])));
|
||||
|
||||
$response = $this->executeFrontendSubRequest($request);
|
||||
|
||||
self::assertSame(303, $response->getStatusCode());
|
||||
self::assertSame('http://example.com/?events%5Bsearch%5D%5Bsearchword%5D=Event', $response->getHeaderLine('location'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function submittedInputShownInForm(): void
|
||||
{
|
||||
$request = new InternalRequest('https://example.com/');
|
||||
$request = $request->withQueryParams([
|
||||
'events' => [
|
||||
'search' => [
|
||||
'searchword' => 'Event',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendSubRequest($request);
|
||||
$html = $response->getBody()->__toString();
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertStringContainsString('value="Event"', $html, 'Submitted value is not rendered within form');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function submittedInputIsKeptWithinPagination(): void
|
||||
{
|
||||
$request = new InternalRequest('https://example.com/');
|
||||
$request = $request->withAttribute('testingDateAspect', new DateTimeAspect(new DateTimeImmutable('2022-08-10')));
|
||||
$request = $request->withQueryParams([
|
||||
'events' => [
|
||||
'search' => [
|
||||
'searchword' => 'Event',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendSubRequest($request);
|
||||
$html = $response->getBody()->__toString();
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertStringContainsString('Event one', $html);
|
||||
self::assertStringContainsString('Current Page 1', $html);
|
||||
self::assertStringContainsString('/page-2?events%5Bsearch%5D%5Bsearchword%5D=Event&cHash=41711281293c1c3a3aa161e96bbd4e98', $html);
|
||||
self::assertStringNotContainsString('Event two', $html);
|
||||
|
||||
self::assertStringContainsString('value="Event"', $html, 'Submitted value is not rendered within form');
|
||||
|
||||
// Ensure going to 2nd page works (make sure it is available after warming up cache for first page)
|
||||
|
||||
$request = new InternalRequest('https://example.com/');
|
||||
$request = $request->withAttribute('testingDateAspect', new DateTimeAspect(new DateTimeImmutable('2022-08-10')));
|
||||
$request = $request->withQueryParams([
|
||||
'events' => [
|
||||
'search' => [
|
||||
'searchword' => 'Event',
|
||||
],
|
||||
'controller' => 'Date',
|
||||
'currentPage' => '2',
|
||||
],
|
||||
'cHash' => '41711281293c1c3a3aa161e96bbd4e98',
|
||||
]);
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendSubRequest($request);
|
||||
$html = $response->getBody()->__toString();
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertStringContainsString('Event two', $html);
|
||||
self::assertStringContainsString('Current Page 2', $html);
|
||||
self::assertStringContainsString('/page-1?events%5Bsearch%5D%5Bsearchword%5D=Event&cHash=13c33adfef09ccb19da7d399ada25c4c', $html);
|
||||
self::assertStringNotContainsString('Event one', $html);
|
||||
|
||||
self::assertStringContainsString('value="Event"', $html, 'Submitted value is not rendered within form');
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@
|
|||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"WerkraumMedia\\Events\\Tests\\": "Tests",
|
||||
"WerkraumMedia\\EventsExample\\": "Tests/Functional/Frontend/Fixtures/Extensions/example/Classes/",
|
||||
"WerkraumMedia\\CustomCategories\\": "Tests/Functional/Psr14Events/DestinationDataImport/Fixtures/Extensions/custom_categories/Classes/",
|
||||
"WerkraumMedia\\CustomEvent\\": "Tests/Functional/Psr14Events/DestinationDataImport/Fixtures/Extensions/custom_event/Classes/"
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue