Commit 23d130ce authored by Dainis Abols's avatar Dainis Abols
Browse files

Major search changes. Filters + ordering + solr + TYPO3 v10 compatability

parent e8c1d641
......@@ -2,22 +2,20 @@
namespace Lu\LuSearch\Controller;
use HDNET\Calendarize\ViewHelpers\Link\AbstractLinkViewHelper;
use Lu\LuApi\DataSources\SolrSearch;
use Lu\LuSearch\Domain\Model\Results;
use Lu\LuSearch\Helpers\DataHelper;
use TYPO3\CMS\Beuser\Domain\Model\BackendUser;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* Class SearchController
......@@ -67,19 +65,12 @@ class SearchController extends ActionController
*/
private $currentPage;
/**
* Filter array
*
* @var array
*/
private $filter;
/**
* Define pagination limit
*
* @var int
*/
private $limit = 25;
private $limit = 10;
/**
* Parent IDs
......@@ -122,25 +113,39 @@ class SearchController extends ActionController
// Pre-define variables
$data = [];
$request = $_REQUEST;
$currentPage = $request["tx_lusearch_pi1"]['@widget_0']['currentPage'];
if (empty($request['query'])) {
// Check queries
if (empty($request)) {
$request = $this->request->getArguments();
$currentPage = $this->request->getArguments()['@widget_0']['currentPage'];
}
// Set order
$request['orderBy'] = !empty($request['orderBy']) ? $request['orderBy'] : 'default';
// Clear filter
if (!empty($request['filter'])) {
foreach ($request['filter'] as &$filter) {
$filter = array_unique($filter);
}
}
// Find pagetree if limit set
// Find page tree if limit set
if ($this->settings['limitToDomain']) {
// Get page record for tree starting point
$this->parentIDs = $this->buildTree($this->getParentSite($GLOBALS['TSFE']->id)->getRootPageId());
$this->parentIDs = @$this->buildTree($this->getParentSite($GLOBALS['TSFE']->id)->getRootPageId());
}
// Get current page @FIXME There has to be a better way!
$this->currentPage = !empty($request['@widget_0']['currentPage']) ? (int)$request['@widget_0']['currentPage'] : 1;
$this->currentPage = !empty($currentPage) ? (int)$currentPage : 1;
// Fetch data from SOLR
$cleanQuery = strip_tags($request['query']);
$cleanQuery = strlen($cleanQuery) >= 3 ? $cleanQuery : '';
if ($cleanQuery) {
$data = $this->listResults($cleanQuery);
$data = $this->listResults($request);
$dataFacets = $this->listResults($request, true);
}
// Add highlighting
......@@ -156,7 +161,6 @@ class SearchController extends ActionController
$singleTranslate = true;
}
// Add paginate object
$data['paginate'] = [];
for ($k = 0; $k < $numRows; $k++) {
......@@ -169,13 +173,20 @@ class SearchController extends ActionController
// Assign variables
$this->view->assign('query', $cleanQuery);
$this->view->assign('orderBy', $request['orderBy']);
$this->view->assign('currentPage', $this->currentPage);
$this->view->assign('singleTranslate', $singleTranslate);
$this->view->assign('pageId', $GLOBALS['TSFE']->id);
$this->view->assign('language', $this->language);
$this->view->assign('newsPageId', $this->language);
$this->view->assign('pageLimit', $this->limit);
// Assign final data element to view
$this->view->assign('visual', $this->getVisualsFilter($request['filter']));
$this->view->assign('filters', $request['filter']);
$facets = $this->convertFacets($dataFacets['facet_fields'] ?? []);
$this->view->assign('facets', $facets);
$this->view->assign('data', $data);
}
......@@ -188,16 +199,29 @@ class SearchController extends ActionController
*
* @param $query
*/
private function listResults($query)
private function listResults($request, $withFacets = false)
{
// Pre-set models and values
$dataHelper = new DataHelper();
$query = $request['query'];
$orderBy = $request['orderBy'];
// Search in SOLR
$solr = new SolrSearch();
$solr->setLimit($this->limit);
$solr->setOffset(($this->currentPage - 1) * $this->limit);
if ($withFacets) {
$solr->useFacets(true);
} else {
$solr->setHighlight("<span class=\"searchResults__searchTerm\">|</span>");
}
// Check facet call
if (!empty($request['filter'])) {
foreach ($request['filter'] as $name => $value) {
$solr->setFacetFilter($name, $value);
}
}
// Set filters
$filters = ['title:"'.$query.'"', 'author:"'.$query.'"', 'body:"'.$query.'"'];
......@@ -217,14 +241,17 @@ class SearchController extends ActionController
// if ($this->settings['searchContacts']) {
// $types[] = 'type:contact';
// }
if ($this->settings['searchEvents']) {
$types[] = 'type:event';
}
// if ($this->settings['searchEvents']) {
// $types[] = 'type:event';
// }
if ($this->settings['searchPages']) {
$types[] = 'type:page';
}
$solr->setTypes($types);
// Set order
$solr->setOrder($orderBy);
// Fetch data
$jsonData = $solr->fetch(true);
$result = $dataHelper->APIDecode($jsonData);
......@@ -345,6 +372,8 @@ class SearchController extends ActionController
*/
public function buildTree($startingPoint, $depth = 100)
{
GLOBAL $GLOBALS;
// Get page record for tree starting point
$pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord(
'pages',
......@@ -361,12 +390,11 @@ class SearchController extends ActionController
'HTML' => '',
];
// Create the page tree, from the starting point, 2 levels deep
$tree->getTree(
$startingPoint,
$depth,
''
);
// Create the page tree, from the starting point, 2 levels deep with admin rights
$GLOBALS['BE_USER'] = new BackendUserAuthentication();
$GLOBALS['BE_USER']->user['admin'] = 1;
$tree->getTree($startingPoint, $depth, '');
$GLOBALS['BE_USER'] = null;
// Fetch only uids and retrn data
$treeIds = [];
......@@ -511,7 +539,8 @@ class SearchController extends ActionController
*
* @return string
*/
private function getPageTitle($page_id) {
private function getPageTitle($page_id)
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('page')->createQueryBuilder();
$statement = $queryBuilder->select('title', 'slug')->from('pages')->where(
$queryBuilder->expr()->eq('uid', $page_id),
......@@ -573,4 +602,117 @@ class SearchController extends ActionController
{
return 55699;
}
/**
* Convert facets to readable array
*
* @param $facets
*
* @return array
*/
private function convertFacets(array $facets): array
{
// Preset models
$myResults = new Results();
// Loop fasets
$result = [];
if (!empty($facets)) {
foreach ($facets as $field => $items) {
// Reformat items
$item = [];
for ($k = 0; $k < count($items); $k = $k + 2) {
if ($items[$k + 1] > 0) {
$item[] = [
'name' => $items[$k],
'visual' => $myResults->getTitleByFacet($field, $items[$k]),
'count' => $items[$k + 1],
'url' => $this->generateUrl($field, $items[$k]),
];
}
}
if (!empty($item)) {
$result[] = [
'name' => $field,
'items' => $item,
];
}
}
}
// Return result
return $result;
}
/**
* Genereate url for facet usage
*
* @param $field
* @param $item
*
* @return string
*/
private function generateUrl($field, $item)
{
// Fetch uri
$uri = parse_url($_SERVER['REQUEST_URI']);
// Build new param
$newParam = 'filter['.$field.'][]='.$item;
// Check if params already present
if (!empty($uri['query'])) {
$params = explode('&', $uri['query']);
$params = array_unique($params);
foreach ($params as $k => $param) {
// Remove own entry
if ($newParam == urldecode($param)) {
unset($params[$k]);
$newParam = '';
}
// Remove page switcher
if (strpos($param, 'currentPage') > 0) {
unset($params[$k]);
}
// Remove hash
if (strpos($param, 'cHash') === 0) {
unset($params[$k]);
}
}
$params[] = $newParam;
$params = array_filter($params);
$uri['query'] = implode('&', $params);
$uri['query'] = !empty($uri['query']) ? '?'.$uri['query'] : '';
}
// Return finalized url
return $uri['path'].$uri['query'];
}
/**
* Build visual filter display
*
* @param $filters
*
* @return array
*/
private function getVisualsFilter($filters)
{
// Preset
$visual = [];
// Fetch title and move value to key
foreach ($filters as $field => $items) {
foreach ($items as $item) {
$myResults = new Results();
$visual[$field][$item] = $myResults->getTitleByFacet($field, $item);
}
}
return $visual;
}
}
<?php
namespace Lu\LuSearch\Domain\Model;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
/**
* Class Results
*
* @package Lu\LuSearch\Domain\Model
*/
class Results
{
/**
* Fetch title by uid and field
*
* @param $field
* @param $uid
*
* @return mixed|string
*/
public function getTitleByFacet($field, $uid)
{
// Fetch titles
switch ($field) {
case 'category':
// Fetch category name
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_category')->createQueryBuilder();
$row = $queryBuilder
->select('title')
->from('sys_category')->where(
$queryBuilder->expr()->eq('uid', $uid)
)
->execute()->fetch();
break;
case 'tag':
// Fetch category name
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_news_domain_model_tag')->createQueryBuilder();
$row = $queryBuilder
->select('title')
->from('tx_news_domain_model_tag')->where(
$queryBuilder->expr()->eq('uid', $uid)
)
->execute()->fetch();
break;
}
// Return
return $row['title'] ?? $uid;
}
}
This diff is collapsed.
......@@ -59,6 +59,55 @@
<trans-unit id="results.page">
<source><![CDATA[Page]]></source>
</trans-unit>
<!-- Filters -->
<trans-unit id="filter.title">
<source><![CDATA[Filters]]></source>
</trans-unit>
<trans-unit id="facet.type">
<source><![CDATA[Types]]></source>
</trans-unit>
<trans-unit id="facet.tag">
<source><![CDATA[Tags]]></source>
</trans-unit>
<trans-unit id="facet.category">
<source><![CDATA[Categories]]></source>
</trans-unit>
<trans-unit id="facet.type.content">
<source><![CDATA[Content]]></source>
</trans-unit>
<trans-unit id="facet.type.news">
<source><![CDATA[News]]></source>
</trans-unit>
<trans-unit id="facet.type.page">
<source><![CDATA[Page]]></source>
</trans-unit>
<trans-unit id="filter.selectedParams">
<source><![CDATA[Active filters]]></source>
</trans-unit>
<trans-unit id="filter.clearFilter">
<source><![CDATA[Clear results and filter]]></source>
</trans-unit>
<trans-unit id="filter.select">
<source><![CDATA[Select]]></source>
</trans-unit>
<trans-unit id="filter.orderBy">
<source><![CDATA[Order By]]></source>
</trans-unit>
<trans-unit id="filter.relevance">
<source><![CDATA[Relevance]]></source>
</trans-unit>
<trans-unit id="filter.newest">
<source><![CDATA[Newest]]></source>
</trans-unit>
<trans-unit id="filter.olest">
<source><![CDATA[Oldest]]></source>
</trans-unit>
<trans-unit id="dateTimeFormat">
<source><![CDATA[d.m.Y H:i]]></source>
</trans-unit>
</body>
</file>
</xliff>
......@@ -77,6 +77,70 @@
<source><![CDATA[Page]]></source>
<target><![CDATA[Lapa]]></target>
</trans-unit>
<!-- Filters -->
<trans-unit id="filter.title">
<source><![CDATA[Filters]]></source>
<target><![CDATA[Filtri]]></target>
</trans-unit>
<trans-unit id="facet.type">
<source><![CDATA[Types]]></source>
<target><![CDATA[Tipi]]></target>
</trans-unit>
<trans-unit id="facet.tag">
<source><![CDATA[Tags]]></source>
<target><![CDATA[Tagi]]></target>
</trans-unit>
<trans-unit id="facet.category">
<source><![CDATA[Categories]]></source>
<target><![CDATA[Kategorijas]]></target>
</trans-unit>
<trans-unit id="facet.type.content">
<source><![CDATA[Content]]></source>
<target><![CDATA[Saturs]]></target>
</trans-unit>
<trans-unit id="facet.type.news">
<source><![CDATA[News]]></source>
<target><![CDATA[Ziņas]]></target>
</trans-unit>
<trans-unit id="facet.type.page">
<source><![CDATA[Page]]></source>
<target><![CDATA[Lapa]]></target>
</trans-unit>
<trans-unit id="filter.selectedParams">
<source><![CDATA[Active filters]]></source>
<target><![CDATA[Aktīvie filtri]]></target>
</trans-unit>
<trans-unit id="filter.clearFilter">
<source><![CDATA[Clear results and filter]]></source>
<target><![CDATA[Notīrīt filtrus un meklētāju]]></target>
</trans-unit>
<trans-unit id="filter.select">
<source><![CDATA[Select]]></source>
<target><![CDATA[Izvēlēties]]></target>
</trans-unit>
<trans-unit id="filter.orderBy">
<source><![CDATA[Order By]]></source>
<target><![CDATA[Kārtot pēc]]></target>
</trans-unit>
<trans-unit id="filter.relevance">
<source><![CDATA[Relevance]]></source>
<target><![CDATA[Atbilstības]]></target>
</trans-unit>
<trans-unit id="filter.newest">
<source><![CDATA[Newest]]></source>
<target><![CDATA[Jaunākā]]></target>
</trans-unit>
<trans-unit id="filter.olest">
<source><![CDATA[Oldets]]></source>
<target><![CDATA[Vecākā]]></target>
</trans-unit>
<trans-unit id="dateTimeFormat">
<source><![CDATA[d.m.Y H:i]]></source>
<target><![CDATA[d.m.Y. H.i]]></target>
</trans-unit>
</body>
</file>
</xliff>
......@@ -16,7 +16,7 @@
<div class="searchResults__resultContent">
<div class="searchResults__resultMeta">
<span class="searchResults__resultDate">
<f:format.date format="d.m.Y H:i">{article.date_published}</f:format.date>
<f:format.date format="{f:translate(key:'LLL:EXT:lu_search/Resources/Private/Language/locallang.xlf:dateTimeFormat')}">{article.date_published}</f:format.date>
</span>
<span class="searchResults__resultCategory">
<f:translate key="results.{article.type}"/>
......
......@@ -15,7 +15,7 @@
<div class="searchResults__resultContent">
<div class="searchResults__resultMeta">
<span class="searchResults__resultDate">
<f:format.date format="d.m.Y H:i">{date_published}</f:format.date>
<f:format.date format="{f:translate(key:'LLL:EXT:lu_search/Resources/Private/Language/locallang.xlf:dateTimeFormat')}">{date_published}</f:format.date>
</span>
<span class="searchResults__resultCategory">
<f:translate key="results.content"/>
......
......@@ -18,7 +18,7 @@
<div class="searchResults__resultContent">
<div class="searchResults__resultMeta">
<span class="searchResults__resultDate">
<f:format.date format="d.m.Y H:i">{date_published}</f:format.date>
<f:format.date format="{f:translate(key:'LLL:EXT:lu_search/Resources/Private/Language/locallang.xlf:dateTimeFormat')}">{date_published}</f:format.date>
</span>
<span class="searchResults__resultCategory">
<f:translate key="results.events"/>
......
......@@ -17,7 +17,7 @@
<div class="searchResults__resultContent">
<div class="searchResults__resultMeta">
<span class="searchResults__resultDate">
<f:format.date format="d.m.Y H:i">{date_published}</f:format.date>
<f:format.date format="{f:translate(key:'LLL:EXT:lu_search/Resources/Private/Language/locallang.xlf:dateTimeFormat')}">{date_published}</f:format.date>
</span>
<span class="searchResults__resultCategory">
<f:translate key="results.news"/>
......
......@@ -23,7 +23,7 @@
<div class="searchResults__resultContent">
<div class="searchResults__resultMeta">
<span class="searchResults__resultDate">
<f:format.date format="d.m.Y H:i">{date_published}</f:format.date>
<f:format.date format="{f:translate(key:'LLL:EXT:lu_search/Resources/Private/Language/locallang.xlf:dateTimeFormat')}">{date_published}</f:format.date>
</span>
<span class="searchResults__resultCategory">
<f:translate key="results.page"/>
......
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<!-- Check if selection already present -->
<f:variable name="showCategory" value="1" />
<f:for each="{filters}" key="filter" as="values">
<f:if condition="{filter} == {facet.name}">
<f:variable name="showCategory" value="2" />
</f:if>
</f:for>
<!-- Create facet block -->
<div class="searchFilters__category searchFilters__category-openable" id="{facet.name}_category">
<button class="searchFilters__categoryHead {f:if(condition: '{showCategory} == 2', then: '', else: 'collapsed')}" id="{facet.name}_categoryHead" data-toggle="collapse" data-target="#{facet.name}_categoryBody" aria-expanded="{f:if(condition: '{showCategory} == 2', then: 'true', else: 'false')}" aria-controls="{facet.name}_categoryBody">
<div class="searchFilters__categoryTitle"><f:translate key="facet.{facet.name}" default="{facet.name}" /></div>
</button>
<!-- Add filter option -->
<div class="searchFilters__categoryBody collapse {f:if(condition: '{showCategory} == 2', then: 'show', else: '')}" id="{facet.name}_categoryBody" aria-labelledby="{facet.name}_categoryHead" data-parent="#{facet.name}_category" style="">
<div class="searchFilters__categoryBodyInner">
<div class="searchFilters__controlList">
<!-- Render each item -->
<f:for each="{facet.items}" as="item" iteration="index">
<!-- Check if facet in filter -->
<f:variable name="showChecked" value="1"/>
<f:for each="{visual}" key="filter" as="values">
<f:for each="{values}" as="value">
<f:variable name="left" value="{filter}.{value}"/>
<f:variable name="right" value="{facet.name}.{item.visual}"/>
<f:if condition="{left} == {right}">
<f:variable name="showChecked" value="2"/>
</f:if>
</f:for>
</f:for>
<div class="luCheckbox luCheckbox-withIcon">
<input {f:if(condition: '{showChecked} == 2', then: 'checked', else: '')} data-url="{item.url}" type="checkbox" name="filter[{facet.name}][{item.name}]" value="{item.name}" id="filter_{facet.name}_{item.name}" class="jsSearchFiltersControl">
<label for="filter_{facet.name}_{item.name}">
<span class="labelText"><f:translate key="facet.{facet.name}.{item.name}" default="{item.visual}" /></span> <span class="searchFilters__labelCount">({item.count})</span>
<i class="icon-lu-checkbox-unchecked"></i>
<i class="icon-lu-checkbox-checked"></i>
</label>
</div>
</f:for>
</div>