Skip to content

Commit

Permalink
Add plugin and action for header, runtime caching detail request in c…
Browse files Browse the repository at this point in the history
…ontroller, view transitions
  • Loading branch information
thomas-sc committed Feb 25, 2025
1 parent 62a8103 commit bd4ba9f
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 116 deletions.
192 changes: 116 additions & 76 deletions Classes/Controller/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,90 +14,102 @@
use Elastic\Transport\Exception\RuntimeException;
use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
use Slub\LisztCommon\Common\PageTitleProvider;
// ToDo:
// Organize the transfer of the necessary parameters (index name, fields, etc.) from the other extensions (ExtensionConfiguration?) -> see in ElasticSearchServic
// Elastic Search Index return standardized fields? Standardized search fields or own params from the respective extension?
// process search parameters from the URL query parameters to search
use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;

final class SearchController extends ClientEnabledController
{
// set resultLimit as intern variable from $this->settings['resultLimit'];
protected int $resultLimit;
private FrontendInterface $runtimeCache;


// Dependency Injection of Repository
// https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html#Dependency-Injection

public function __construct(
private readonly ElasticSearchServiceInterface $elasticSearchService,
protected ExtensionConfiguration $extConf,
private readonly PageTitleProvider $titleProvider,
)
{
private readonly AssetCollector $assetCollector,
CacheManager $cacheManager,
) {
$this->resultLimit = $this->settings['resultLimit'] ?? 25;
$this->runtimeCache = $cacheManager->getCache('runtime');
}

public function indexAction(array $searchParams = []): ResponseInterface
{
$language = $this->request->getAttribute('language');
$locale = $language->getLocale();
if (
isset($searchParams['page']) &&
(int) $searchParams['page'] > 0
) {
$currentPage = (int) $searchParams['page'];
} else {
$currentPage = 1;
}

// $totalItems = $this->elasticSearchService->count($searchParams, $this->settings);
//$totalItems = 100;
$locale = $this->request->getAttribute('language')->getLocale();
$currentPage = $this->getCurrentPage($searchParams);
$this->addViewTransitionStyle();

$elasticResponse = $this->elasticSearchService->search($searchParams, $this->settings);
$paginator = (new Paginator())->
setPage($currentPage)->
setTotalItems($elasticResponse['hits']['total']['value'])->
setExtensionConfiguration($this->extConf);
$pagination = $paginator->getPagination();
$showPagination = $paginator->getTotalPages() > 1 ? true : false;

$this->view->assign('locale', $locale);
$this->view->assign('totalItems', $elasticResponse['hits']['total']['value']);
$this->view->assign('searchParams', $searchParams);
$this->view->assign('searchResults', $elasticResponse);
$this->view->assign('pagination', $pagination);
$this->view->assign('showPagination', $showPagination);
// $this->view->assign('totalItems', $totalItems);
$this->view->assign('currentString', Paginator::CURRENT_PAGE);
$this->view->assign('dots', Paginator::DOTS);
$this->view->assign('detailPageId', $this->extConf->get('liszt_common','detailPageId'));
$totalItems = 0;
if (isset($elasticResponse['hits'], $elasticResponse['hits']['total'], $elasticResponse['hits']['total']['value'])) {
$totalItems = (int)$elasticResponse['hits']['total']['value'];
}

$paginator = (new Paginator())
->setPage($currentPage)
->setTotalItems($totalItems)
->setExtensionConfiguration($this->extConf);
$pagination = $paginator->getPagination();
$showPagination = $paginator->getTotalPages() > 1;

$this->view->assignMultiple([
'locale' => $locale,
'totalItems' => $totalItems,
'searchParams' => $searchParams,
'searchResults' => $elasticResponse,
'pagination' => $pagination,
'showPagination'=> $showPagination,
'currentString' => Paginator::CURRENT_PAGE,
'dots' => Paginator::DOTS,
'detailPageId' => $this->extConf->get('liszt_common', 'detailPageId'),
]);

return $this->htmlResponse();
}


public function searchBarAction(array $searchParams = []): ResponseInterface
{
$this->view->assign('searchParams', $searchParams);
return $this->htmlResponse();
}

public function detailsAction(array $searchParams = []): ResponseInterface

public function detailsHeaderAction(array $searchParams = []): ResponseInterface
{
$documentId = $this->getDocumentIdFromRouting();
if (!$documentId) {
return $this->redirectToNotFoundPage();
}

try {
$elasticResponse = $this->loadDetailPageFromElastic($documentId);
} catch (ClientResponseException $e) {
if ($e->getCode() === 404) {
return $this->redirectToNotFoundPage();
}
throw $e;
}

$this->view->assign('searchResult', $elasticResponse);
return $this->htmlResponse();
}

$routing = $this->request->getAttribute('routing');
$routingArgs = $routing->getArguments();

// Check if 'tx_lisztcommon_searchdetails' exists and if 'detailId' has a valid value.
$documentId = null;
if (!empty($routingArgs['tx_lisztcommon_searchdetails']['documentId'])) {
$documentId = $routingArgs['tx_lisztcommon_searchdetails']['documentId'];
} else {
public function detailsAction(array $searchParams = []): ResponseInterface
{
$documentId = $this->getDocumentIdFromRouting();
if (!$documentId) {
return $this->redirectToNotFoundPage();
}

try {
$elasticResponse = $this->elasticSearchService->getDocumentById($documentId, []);
$elasticResponse = $this->loadDetailPageFromElastic($documentId);
} catch (ClientResponseException $e) {
// Handle 404 errors
if ($e->getCode() === 404) {
Expand All @@ -107,54 +119,82 @@ public function detailsAction(array $searchParams = []): ResponseInterface

}

// Set the page title
$pageTitle = $elasticResponse['_source']['title'] ?? 'Details';
$maxTitleLength = 50;
if (strlen($pageTitle) > $maxTitleLength) {
$pageTitle = mb_substr($pageTitle, 0, $maxTitleLength - 3) . '...';
}
// set page title
$pageTitle = $this->formatPageTitle($elasticResponse['_source']['title'] ?? 'Details');
$this->titleProvider->setTitle($pageTitle);
// set metatag description
$metaTagManager = GeneralUtility::makeInstance(MetaTagManagerRegistry::class)->getManagerForProperty('description');
$metaTagManager->addProperty('description', $elasticResponse['_source']['tx_lisztcommon_searchable'] ?? '');

// set meta description
$metaDescription = '';
if (isset($elasticResponse['_source'], $elasticResponse['_source']['tx_lisztcommon_searchable'])) {
$metaDescription = (string)$elasticResponse['_source']['tx_lisztcommon_searchable'];
}
$metaTagManager = GeneralUtility::makeInstance(MetaTagManagerRegistry::class)
->getManagerForProperty('description');
$metaTagManager->addProperty('description', $metaDescription);

/* $request = $this->request;
$debugData = [
'Method' => $request->getMethod(),
'URI' => (string)$request->getUri(),
'Headers' => $request->getHeaders(),
'QueryParams' => $request->getQueryParams(),
'ParsedBody' => $request->getParsedBody(),
'Attributes' => $request->getAttributes(),
'UploadedFiles' => $request->getUploadedFiles(),
'ServerParams' => $request->getServerParams()
];
$this->addViewTransitionStyle();

// Ausgabe mit Symfony VarDumper (empfohlen)
dump($debugData);*/
$this->view->assignMultiple([
'searchParams' => $searchParams,
'routingArgs' => $this->request->getAttribute('routing')->getArguments(),
'detailId' => $documentId,
'searchResult' => $elasticResponse,
'detailPageId' => $this->extConf->get('liszt_common', 'detailPageId'),
]);

return $this->htmlResponse();
}

$this->view->assign('searchParams', $searchParams);
$this->view->assign('routingArgs', $routingArgs);
$this->view->assign('detailId', $documentId);
$this->view->assign('searchResult', $elasticResponse);
$this->view->assign('detailPageId', $this->extConf->get('liszt_common','detailPageId'));
private function getCurrentPage(array $params): int
{
return (isset($params['page']) && (int)$params['page'] > 0) ? (int)$params['page'] : 1;
}


return $this->htmlResponse();
// add view transition styles as inline style for animation
private function addViewTransitionStyle(): void
{
$this->assetCollector->addInlineStyleSheet(
'view-transitions-root',
'@media screen and (prefers-reduced-motion: no-preference) { @view-transition { navigation: auto; } }'
);
}

private function getDocumentIdFromRouting(): ?string
{
$routingArgs = $this->request->getAttribute('routing')->getArguments();
return $routingArgs['tx_lisztcommon_searchdetails']['documentId'] ?? null;
}

private function formatPageTitle(string $title): string
{
$maxTitleLength = 50;
return (mb_strlen($title) > $maxTitleLength)
? mb_substr($title, 0, $maxTitleLength - 3) . '...'
: $title;
}

private function loadDetailPageFromElastic(string $documentId): Collection
{
$cachedResult = $this->runtimeCache->get($documentId);
if (!$cachedResult instanceof Collection) {
$result = $this->elasticSearchService->getDocumentById($documentId, []);
if (!$result instanceof Collection) {
throw new \UnexpectedValueException("The return value of getDocumentById does not correspond to the expected collection type."); }
$this->runtimeCache->set($documentId, $result);
return $result;
}
return $cachedResult;
}



public function redirectToNotFoundPage(): ResponseInterface
{
// see: https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/Extbase/Reference/Controller/ActionController.html
// see: https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/Extbase/Reference/Controller/ActionController.html
// $uri could also be https://example.com/any/uri
// $this->resourceFactory is injected as part of the `ActionController` inheritance
return $this->responseFactory->createResponse(301)
->withHeader('Location', '/404/'); // the uri of the 404 page from typo installation
}

}
7 changes: 7 additions & 0 deletions Configuration/TCA/Overrides/tt_content.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
'Liszt Search Details'
);


ExtensionUtility::registerPlugin(
'liszt_common',
'SearchDetailsHeader',
'Liszt Search Details Header'
);

// Adds the content element to the "Type" dropdown
ExtensionManagementUtility::addTcaSelectItem(
'tt_content',
Expand Down
15 changes: 1 addition & 14 deletions Configuration/TypoScript/setup.typoscript
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,7 @@ config {
#lib.selectedFrontendLayout = TEXT
#lib.selectedFrontendLayout.data = levelfield:-1, layout, slide

// enable view transition animations
// only for users without reduced motion sickness
/* page {
cssInline {
10 = TEXT
10.value (
@media screen and (prefers-reduced-motion: no-preference) {
@view-transition {
navigation: auto;
}
}
)
}
}*/


// Partial Root Path to find logo and other Partials from liszt_web
/*plugin.tx_lisztcommon_searchdetails {
Expand Down
25 changes: 4 additions & 21 deletions Resources/Private/Partials/DetailPageHeader.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,13 @@
<header id="page-header" class="page-header navbar">
<div class="container-xl page-header-container">

<f:render partial="Logo" arguments="{rootPage: rootPage}" />
<f:render partial="Logo" arguments="{rootPage: rootPage}"/>

<div class="page-header-title page-header-title-detailpage">
<f:if condition="{data.subtitle}">
<f:cObject typoscriptObjectPath="tt_content.list.20.lisztcommon_searchdetailsheader" />

<p class="page-header-subtitle" style="view-transition-name: detail-sub-{detailId};">{data.subtitle}</p>
<f:render partial="NavbarToggler"/>

</f:if>

<h1 style="view-transition-name: detail-headline-{detailId};">
<f:if condition="{data.headline}">
<f:then>
<f:format.nl2br>{data.headline}</f:format.nl2br>
</f:then>
<f:else>
{data.title}
</f:else>
</f:if>
</h1>
</div>

<f:render partial="NavbarToggler" />

<f:render partial="MainNav" arguments="{_all}" />
<f:render partial="MainNav" arguments="{_all}"/>

</div>
</header>
1 change: 0 additions & 1 deletion Resources/Private/Templates/Search/Details.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
xmlns:i="http://typo3.org/ns/Quellenform/Iconpack/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:variable name="languageFilePath">LLL:EXT:{settings.entityTypes.1.extensionName}/Resources/Private/Language/{settings.entityTypes.1.languageFile}.xlf</f:variable>
<f:debug title="Details View" >{_all}</f:debug>


<f:render partial="DetailBreadcrumb" arguments="{_all}" />
Expand Down
19 changes: 19 additions & 0 deletions Resources/Private/Templates/Search/DetailsHeader.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">

<div class="page-header-title page-header-title-detailpage">
<f:if condition="{searchResult._source.tx_lisztcommon_header}">
<p class="page-header-subtitle" style="view-transition-name: detail-sub-{searchResult._id};">{searchResult._source.tx_lisztcommon_header}</p>
</f:if>
<h1 style="view-transition-name: detail-headline-{searchResult._id};">
<f:if condition="{searchResult._source.tx_lisztcommon_body}">
<f:then>
<f:format.nl2br>{searchResult._source.tx_lisztcommon_body}</f:format.nl2br>
</f:then>
<f:else>
{searchResult._source.title}
</f:else>
</f:if>
</h1>
</div>

</html>
3 changes: 1 addition & 2 deletions Resources/Private/Templates/Search/Index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,13 @@ <h5 class="offcanvas-title">{f:translate(key: 'filter_container_label', extensio
<f:for each="{searchResults.hits.hits}" as="hit">

<article class="searchresults-item ">
<f:comment>ToDo: pageUid should not hardcodet here -> from config?</f:comment>
<f:link.action
action="details"
controller="Search"
pluginName="SearchDetails"
pageUid="{detailPageId}"
arguments="{documentId: hit._id}"
class="searchresults-item-detail-link" >
class="searchresults-item-detail-link prefetch-link" >
<header>
<div class="searchresults-item-label" style="view-transition-name: detail-sub-{hit._id};">
{hit._source.tx_lisztcommon_header}
Expand Down
1 change: 1 addition & 0 deletions Resources/Private/Templates/SearchDetails.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:vac="http://typo3.org/ns/Praetorius/ViteAssetCollector/ViewHelpers"
>

<f:layout name="DetailPage"/>
<f:section name="Main">

Expand Down
4 changes: 2 additions & 2 deletions ext_conf_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ elasticCaFileFilePath = http_ca.crt
paginationRange = 2,3,4,5
# cat=Search; type=int; label=LLL:EXT:liszt_common/Resources/Private/Language/locallang.xml:config.itemsPerPage
itemsPerPage = 10
# cat=Search; type=int; label=LLL:EXT:liszt_common/Resources/Private/Language/locallang.xml:config.searchPageId
searchPageId = 18
# cat=Search; type=int; label=LLL:EXT:liszt_common/Resources/Private/Language/locallang.xml:config.detailPageId
detailPageId = 38
/*# cat=Search; type=int; label=LLL:EXT:liszt_common/Resources/Private/Language/locallang.xml:config.searchPageId
searchPageId = 18*/
Loading

0 comments on commit bd4ba9f

Please sign in to comment.