* @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= * * @return void */ public function listAction() { // Pre-define variables $data = []; $request = $_REQUEST; if (empty($request['query'])) { $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: OR author:OR body:) * * 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("|"); // 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' => "".$response['numFound']."", '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", "$1", $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; } }