Commit 1c31b146 authored by Dainis Abols's avatar Dainis Abols
Browse files

Initial public version

1.0.0
parents
Pipeline #153 failed with stages
in 0 seconds
<?php
namespace Lu\LuSearch\Controller;
use HDNET\Calendarize\ViewHelpers\Link\AbstractLinkViewHelper;
use Lu\LuApi\DataSources\SolrSearch;
use Lu\LuSearch\Helpers\DataHelper;
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
*
* @author Dainis Abols <dainis.abols@lu.lv>
* @owner University of Latvia
* @version 1.0.0
* @since 02.09.2020
*
* @package Lu\LuSearch\Controller
*/
class SearchController extends ActionController
{
/**
* Frontend-Session
*
* @var \Lu\LuSearch\Domain\Session\FrontendSessionHandler
*/
protected $frontendSession;
/**
* Frontend- or Backend-Session
* The type of session is set automatically in initializeAction().
*
* @var \Lu\LuSearch\Domain\Session\SessionHandler
*/
protected $session;
/**
* Main language array
*
* @var array
*/
private $language;
/**
* Uri Builder object
*
* @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
*/
private $uri_builder;
/**
* Selected page number
*
* @var int
*/
private $currentPage;
/**
* Filter array
*
* @var array
*/
private $filter;
/**
* Define pagination limit
*
* @var int
*/
private $limit = 25;
/**
* Parent IDs
*
* @var array
*/
private $parentIDs = [];
/**
* MusicController constructor.
*
* Assign page ID
*/
public function __construct()
{
parent::__construct();
// Set language data
$languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
$this->language = [
'id' => $languageAspect->getId(),
'iso' => $GLOBALS['TSFE']->sys_language_isocode,
'code' => $GLOBALS['TSFE']->sys_language_isocode == 'lv' ? 'lat' : 'eng',
];
// Set uri builder
$this->uri_builder = GeneralUtility::makeInstance(UriBuilder::class)->setCreateAbsoluteUri(true)->reset();
}
/**
* List Action
* Build search field and displays results.
*
* /search?query=<query-string>
*
* @return void
*/
public function listAction()
{
// Pre-define variables
$data = [];
$request = $this->request->getArguments();
// Find pagetree if limit set
if ($this->settings['limitToDomain']) {
// Get page record for tree starting point
$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;
// Fetch data from SOLR
$cleanQuery = strip_tags($request['query']);
$cleanQuery = strlen($cleanQuery) >= 3 ? $cleanQuery : '';
if ($cleanQuery) {
$data = $this->listResults($cleanQuery);
}
// Add highlighting
$singleTranslate = false;
if ($data['numFound'] > 0 || $data['response']['numFound'] > 0) {
$numRows = $data['numFound'] > 0 ? $data['numFound'] : $data['response']['numFound'];
$results = $this->buildResult($data, $cleanQuery);
$this->view->assign('results', $results);
// Set what type of text to use (Latvian number formats only)
if (substr($numRows, -1) == 1 && substr($numRows, -2) != 11) {
$singleTranslate = true;
}
// Add paginate object
$data['paginate'] = [];
for ($k = 0; $k < $numRows; $k++) {
$data['paginate'][] = $k;
}
}
// Add custom JS
$this->renderJs();
// Assign variables
$this->view->assign('query', $cleanQuery);
$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);
// Assign final data element to view
$this->view->assign('data', $data);
}
/**
* Searches in
* (title:<query> OR author:<query>OR body:<query>)
*
* Adds type, based on settings
* (type:news OR ...)
*
* @param $query
*/
private function listResults($query)
{
// Pre-set models and values
$dataHelper = new DataHelper();
// Search in SOLR
$solr = new SolrSearch();
$solr->setLimit($this->limit);
$solr->setOffset(($this->currentPage - 1) * $this->limit);
$solr->setHighlight("<span class=\"searchResults__searchTerm\">|</span>");
// Set filters
$filters = ['title:"'.$query.'"', 'author:"'.$query.'"', 'body:"'.$query.'"'];
$solr->setFilters($filters);
// Set parent IDs
$solr->setParentIDs($this->parentIDs);
// Set types
$types = [];
if ($this->settings['searchNews']) {
$types[] = 'type:news';
}
if ($this->settings['searchContent']) {
$types[] = 'type:content';
}
// if ($this->settings['searchContacts']) {
// $types[] = 'type:contact';
// }
if ($this->settings['searchEvents']) {
$types[] = 'type:event';
}
if ($this->settings['searchPages']) {
$types[] = 'type:page';
}
$solr->setTypes($types);
// Fetch data
$jsonData = $solr->fetch(true);
$result = $dataHelper->APIDecode($jsonData);
return $result;
}
/**
* Build results array for use in multiple paginations
*
* @param $data
* @param $cleanQuery
*
* @return array
*/
private function buildResult($data, $cleanQuery)
{
// Fetch variables for easier overview
$response = $data['response'] ?? $data;
$highlight = $data['highlighting'] ?? [];
// Build response array
$result = [
'recordsTotal' => "<span class=\"searchResults__queryCountNum\">".$response['numFound']."</span>",
'recordsTotal_clean' => $response['numFound'],
'recordsFiltered' => count($response['docs']),
'pageTotal' => ceil($response['numFound'] / $this->limit),
'pageCurrent' => $this->currentPage,
'data' => [],
];
// Add data info
foreach ($response['docs'] as $item) {
// Set site data
$siteData = $this->getParentSite($item['parent_id']);
// Fetch data
$uid = str_replace($item['type'].'-', '', $item['id']);
$title = $highlight[$item['id']]['title'][0] ?? $this->clearAndHighlight($item['title'], $cleanQuery, false);
$siteUrl = !empty($siteData) ? 'https://'.$siteData->getBase()->getHost() : '';
if (empty($title)) {
// If title is not still set, try to get parent page title
$title = $this->getPageTitle($item['parent_id']);
}
$image = !empty($siteData) ? $this->addNewsImage((int)$uid) : '';
if (empty($image)) {
// If still empty, check page images recursivly
$image = $this->getParentImage($item['parent_id']);
}
// Add values to array
$result['data'][] = [
'uid' => $uid,
'parent_id' => $item['parent_id'],
'type' => $item['type'],
'date_published' => $item['date_published'],
'author' => $highlight[$item['id']]['author'][0] ?? $this->clearAndHighlight($item['author'], $cleanQuery),
'title' => $title,
'body' => $highlight[$item['id']]['body'][0] ?? $this->clearAndHighlight($item['body'], $cleanQuery),
'siteName' => !empty($siteData) ? $this->getSiteName($siteData->getRootPageId()) : '',
'siteUrl' => $siteUrl,
'newsId' => !empty($siteData) ? $this->addNewsIds($siteData->getRootPageId()) : '',
'calendarId' => !empty($siteData) ? $this->addCalendarId($uid) : '',
'imgUrl' => $image,
];
}
// Return result
return $result;
}
/**
* Removed all tags and newlines and adds highlight to search words
*
* @param $string
* @param $cleanQuery
* @param bool $crop
*
* @return string
*/
private function clearAndHighlight($string, $cleanQuery, $crop = true)
{
// Clear
$string = trim(preg_replace('/\s\s+/', ' ', strip_tags($string)));
if ($crop) {
$string = substr($string, 0, strrpos(substr($string, 0, 1000), ' '));
}
// Highlight
$string = preg_replace("/(".$cleanQuery.")/i", "<span class=\"searchResults__searchTerm\">$1</span>", $string);
// Return
return $string;
}
/**
* Adds our own custom js
*/
private function renderJs()
{
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
// Add jQuery UI
$pageRenderer->addCssFile("//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css");
$pageRenderer->addJsFooterFile("//code.jquery.com/jquery-1.12.4.js");
$pageRenderer->addJsFooterFile("//code.jquery.com/ui/1.12.1/jquery-ui.js");
// Add own JS
$pageRenderer->addJsFooterFile("/typo3conf/ext/lu_search/Resources/Public/JavaScript/scripts.js");
}
/**
* Builds tree
*
* @param $startingPoint
* @param int $depth
*/
public function buildTree($startingPoint, $depth = 100)
{
// Get page record for tree starting point
$pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord(
'pages',
$startingPoint
);
// Create and initialize the tree object
/** @var $tree \TYPO3\CMS\Backend\Tree\View\PageTreeView */
$tree = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\View\PageTreeView::class);
// Creating the icon for the current page and add it to the tree
$tree->tree[] = [
'row' => $pageRecord,
'HTML' => '',
];
// Create the page tree, from the starting point, 2 levels deep
$tree->getTree(
$startingPoint,
$depth,
''
);
// Fetch only uids and retrn data
$treeIds = [];
foreach ($tree->tree as $item) {
$treeIds[] = $item['row']['uid'];
}
// Return
return $treeIds;
}
/**
* Recursive method for finding site objects
*
* @return \array|\TYPO3\CMS\Core\Site\Entity\Site
*/
private function getParentSite($current_id = 0)
{
// Get current ID if parent ID = 0
if (!$current_id) {
$current_id = $GLOBALS['TSFE']->id;
}
$parent_id = $this->getParentId($current_id);
// Check if parent exists
if ($parent_id < 1) {
return [];
}
// Try to find site info
try {
$sites = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($parent_id);
} catch (SiteNotFoundException $e) {
return [];
}
// Go up, if no site founf
if (empty($sites)) {
return $this->getParentSite($parent_id);
}
// Return site data
return $sites;
}
/**
* Fetch current page parent id
*
* @param $current_id
*
* @return int
*/
private function getParentId($current_id): int
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages')->createQueryBuilder();
return (int)$queryBuilder->select('pid')->from('pages')->where(
$queryBuilder->expr()->eq('uid', $current_id)
)->execute()->fetchColumn(0);
}
/**
* Fetch page title
*
* @param $uid
*
* @return string
*/
private function getSiteName(int $uid): string
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages')->createQueryBuilder();
return (string)$queryBuilder->select('title')->from('pages')->where(
$queryBuilder->expr()->eq('uid', $uid)
)->execute()->fetchColumn(0);
}
/**
* Get constants entry by root page id
*
* @param $rootPageId
*/
private function addNewsIds($rootPageId)
{
// SELECT constants FROM `sys_template` WHERE pid = 468 AND deleted = 0
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template')->createQueryBuilder();
$statement = $queryBuilder->select('constants')->from('sys_template')->where(
$queryBuilder->expr()->eq('pid', $rootPageId),
$queryBuilder->expr()->eq('deleted', 0)
)->execute();
// Try finding news ID entries
while ($row = $statement->fetch()) {
foreach (preg_split("/((\r?\n)|(\r\n?))/", $row['constants']) as $line) {
if (!empty($line)) {
$contsOptions = explode("=", $line);
if (trim($contsOptions[0]) == 'news.defaultDetailPid') {
return (int)$contsOptions[1];
}
}
}
}
// Return results
return 0;
}
/**
* Fetch news image, if available
*
* @param $uid
*
* @return string
*/
private function addNewsImage($uid)
{
// Fetch image from storage
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tx_news_domain_model_news', 'fal_media', $uid);
// Build image
if (!empty($fileObjects)) {
foreach ($fileObjects as $fileRef) {
// Check if allowed list view
if ($fileRef->getProperties()['showinpreview']) {
return $this->getPublicPath($fileRef);
}
}
}
// Return empty
return '';
}
/**
* Fetch page title by id
*
* @param $page_id
*
* @return string
*/
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),
)->execute();
// Get info
$result = $statement->fetch();
return $result['title'];
}
/**
* Return public path
*
* @param $fileRef
*
* @return string
*/
private function getPublicPath($fileRef)
{
return $fileRef->getStorage()->getConfiguration()['basePath'].$fileRef->getIdentifier();
}
/**
* Recursivly retrieve title image
*
* @param $uid
*
* @return string
*/
private function getParentImage($uid)
{
// Fetch image from storage
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('pages', 'media', $uid);
// Fetch level up
if (empty($fileObjects)) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('page')->createQueryBuilder();
$statement = $queryBuilder->select('pid')->from('pages')->where(
$queryBuilder->expr()->eq('uid', $uid),
)->execute();
$result = $statement->fetch();
if (empty($result)) {
return '';
}
// Call level up
return $this->getParentImage($result['pid']);
}
// Build first element image
return $this->getPublicPath($fileObjects[0]);
}
private function addCalendarId($uid)
{
return 55699;
}
}
\ No newline at end of file
<?php
namespace Lu\LuSearch\Domain\Session;
/**
* Class BackendSessionHandler
*
* @author Dainis Abols <dainis.abols@lu.lv>
* @owner University of Latvia
* @version 1.0.0
* @since 02.09.2020
*
* @package Lu\LuSearch\Domain\Session
*/
class BackendSessionHandler extends SessionHandler
{
/**
* @var string
*/
protected $mode = "BE";
}
\ No newline at end of file
<?php
namespace Lu\LuSearch\Domain\Session;
/**
* Class BackendSessionHandler
*