kitodo-presentation/Classes/Plugin/Search.php

668 lines
30 KiB
PHP
Raw Normal View History

2011-03-09 16:36:27 +01:00
<?php
2019-10-30 15:37:44 +01:00
2011-03-09 16:36:27 +01:00
/**
2016-09-23 12:24:46 +02:00
* (c) Kitodo. Key to digital objects e.V. <contact@kitodo.org>
*
* This file is part of the Kitodo and TYPO3 projects.
*
* @license GNU General Public License version 3 or later.
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
2011-03-09 16:36:27 +01:00
*/
namespace Kitodo\Dlf\Plugin;
2019-03-13 08:29:51 +01:00
use Kitodo\Dlf\Common\Document;
use Kitodo\Dlf\Common\DocumentList;
use Kitodo\Dlf\Common\Helper;
use Kitodo\Dlf\Common\Indexer;
use Kitodo\Dlf\Common\Solr;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
2019-03-13 08:29:51 +01:00
2011-03-09 16:36:27 +01:00
/**
2019-03-14 22:41:35 +01:00
* Plugin 'Search' for the 'dlf' extension
2011-03-09 16:36:27 +01:00
*
2019-03-14 22:41:35 +01:00
* @author Sebastian Meyer <sebastian.meyer@slub-dresden.de>
* @author Henrik Lochmann <dev@mentalmotive.com>
* @author Frank Ulrich Weber <fuw@zeutschel.de>
* @package TYPO3
* @subpackage dlf
* @access public
2011-03-09 16:36:27 +01:00
*/
2019-10-30 15:37:44 +01:00
class Search extends \Kitodo\Dlf\Common\AbstractPlugin
{
2019-03-16 18:49:55 +01:00
public $scriptRelPath = 'Classes/Plugin/Search.php';
2011-03-09 16:36:27 +01:00
/**
* Adds the JS files necessary for search suggestions
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @return void
*/
2019-10-30 15:37:44 +01:00
protected function addAutocompleteJS()
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_dlf_documents');
// Check if there are any metadata to suggest.
$result = $queryBuilder
->select('tx_dlf_metadata.*')
->from('tx_dlf_metadata')
->where(
$queryBuilder->expr()->eq('tx_dlf_metadata.index_autocomplete', 1),
$queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($this->conf['pages'])),
Helper::whereExpression('tx_dlf_metadata')
)
->setMaxResults(1)
->execute();
if ($result->rowCount() == 1) {
$pageRenderer = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class);
$pageRenderer->addJsFooterFile(\TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($this->extKey)) . 'Resources/Public/Javascript/Search/Suggester.js');
} else {
2019-03-15 11:03:54 +01:00
Helper::devLog('No metadata fields configured for search suggestions', DEVLOG_SEVERITY_WARNING);
}
}
/**
* Adds the current collection's UID to the search form
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @return string HTML input fields with current document's UID and parent ID
*/
2019-10-30 15:37:44 +01:00
protected function addCurrentCollection()
{
// Load current collection.
$list = GeneralUtility::makeInstance(DocumentList::class);
2019-10-30 15:37:44 +01:00
if (
!empty($list->metadata['options']['source'])
&& $list->metadata['options']['source'] == 'collection'
) {
// Get collection's UID.
2019-10-30 15:37:44 +01:00
return '<input type="hidden" name="' . $this->prefixId . '[collection]" value="' . $list->metadata['options']['select'] . '" />';
} elseif (!empty($list->metadata['options']['params']['filterquery'])) {
// Get collection's UID from search metadata.
foreach ($list->metadata['options']['params']['filterquery'] as $facet) {
$facetKeyVal = explode(':', $facet['query'], 2);
2019-10-30 15:37:44 +01:00
if (
$facetKeyVal[0] == 'collection_faceting'
&& !strpos($facetKeyVal[1], '" OR "')
) {
2019-03-16 13:41:20 +01:00
$collectionId = Helper::getUidFromIndexName(trim($facetKeyVal[1], '(")'), 'tx_dlf_collections');
}
}
2019-10-30 15:37:44 +01:00
return '<input type="hidden" name="' . $this->prefixId . '[collection]" value="' . $collectionId . '" />';
}
return '';
}
2012-09-06 13:04:11 +02:00
/**
* Adds the current document's UID or parent ID to the search form
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2021-03-07 11:47:33 +01:00
* @return string HTML input fields with current document's UID
*/
2019-10-30 15:37:44 +01:00
protected function addCurrentDocument()
{
// Load current list object.
$list = GeneralUtility::makeInstance(DocumentList::class);
// Load current document.
2019-10-30 15:37:44 +01:00
if (
!empty($this->piVars['id'])
&& \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->piVars['id'])
) {
$this->loadDocument();
Fix search in documents for hierarchical documents like newspaper. == Current Situation The search plugin has the option to restrict the search in current document and/or in current collection. This can be used to place the plugin on a separate page to search only in one collection. Or you place the plugin on the workview page and trigger a search in document. Of course, the SearchInDocument plugin is better suited for the workview as the results are reported by via AJAX and the visitor will stay on the current page. On the opposite, using the search plugin makes it possible to search in all issues of a newspaper year or even a full newspaper title. At least, this should be possible. The current implementation does not work as expected. === Example ==== Search plugin on workview (issue) For example, you can place the plugin on level of a newspaper issue. If you launch a search, the current implementation will search not only in current issue but in all issues of the current year. This is a frequently demanded feature but works only by hasard. newspaper title (anchor) | |-> year | |-> Issue The current search plugin does two mistakes: 1. Instead of the current document uid, the parent uid is set. 2. The search is done in given uid and all children. ==== Search plugin on workview (year/anchor) If you place the search plugin on the workview with calendar view, the search fails. Same procedure as above. The search will be done in the anchor and it's children (years). All these files have no fulltexts. So the result is always empty. == Proposed Implementation The proposed implementation does change the searchIn document limitation of the search plugin to search in current document _and_ all it's children. This is done on using Solr join feature. To make this possible, the partof field must use the docValues feature as "uid" does it already.
2020-11-20 16:16:48 +01:00
// Get document's UID
if ($this->doc->ready) {
Fix search in documents for hierarchical documents like newspaper. == Current Situation The search plugin has the option to restrict the search in current document and/or in current collection. This can be used to place the plugin on a separate page to search only in one collection. Or you place the plugin on the workview page and trigger a search in document. Of course, the SearchInDocument plugin is better suited for the workview as the results are reported by via AJAX and the visitor will stay on the current page. On the opposite, using the search plugin makes it possible to search in all issues of a newspaper year or even a full newspaper title. At least, this should be possible. The current implementation does not work as expected. === Example ==== Search plugin on workview (issue) For example, you can place the plugin on level of a newspaper issue. If you launch a search, the current implementation will search not only in current issue but in all issues of the current year. This is a frequently demanded feature but works only by hasard. newspaper title (anchor) | |-> year | |-> Issue The current search plugin does two mistakes: 1. Instead of the current document uid, the parent uid is set. 2. The search is done in given uid and all children. ==== Search plugin on workview (year/anchor) If you place the search plugin on the workview with calendar view, the search fails. Same procedure as above. The search will be done in the anchor and it's children (years). All these files have no fulltexts. So the result is always empty. == Proposed Implementation The proposed implementation does change the searchIn document limitation of the search plugin to search in current document _and_ all it's children. This is done on using Solr join feature. To make this possible, the partof field must use the docValues feature as "uid" does it already.
2020-11-20 16:16:48 +01:00
return '<input type="hidden" name="' . $this->prefixId . '[id]" value="' . ($this->doc->uid) . '" />';
}
} elseif (!empty($list->metadata['options']['params']['filterquery'])) {
// Get document's UID from search metadata.
2021-03-07 11:47:33 +01:00
// The string may be e.g. "{!join from=uid to=partof}uid:{!join from=uid to=partof}uid:2" OR {!join from=uid to=partof}uid:2 OR uid:2"
// or "collection_faceting:("Some Collection Title")"
foreach ($list->metadata['options']['params']['filterquery'] as $facet) {
2021-03-07 11:47:33 +01:00
if (($lastUidPos = strrpos($facet['query'], 'uid:')) !== false) {
$facetKeyVal = explode(':', substr($facet['query'], $lastUidPos));
if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($facetKeyVal[1])) {
$documentId = (int) $facetKeyVal[1];
Fix search in documents for hierarchical documents like newspaper. == Current Situation The search plugin has the option to restrict the search in current document and/or in current collection. This can be used to place the plugin on a separate page to search only in one collection. Or you place the plugin on the workview page and trigger a search in document. Of course, the SearchInDocument plugin is better suited for the workview as the results are reported by via AJAX and the visitor will stay on the current page. On the opposite, using the search plugin makes it possible to search in all issues of a newspaper year or even a full newspaper title. At least, this should be possible. The current implementation does not work as expected. === Example ==== Search plugin on workview (issue) For example, you can place the plugin on level of a newspaper issue. If you launch a search, the current implementation will search not only in current issue but in all issues of the current year. This is a frequently demanded feature but works only by hasard. newspaper title (anchor) | |-> year | |-> Issue The current search plugin does two mistakes: 1. Instead of the current document uid, the parent uid is set. 2. The search is done in given uid and all children. ==== Search plugin on workview (year/anchor) If you place the search plugin on the workview with calendar view, the search fails. Same procedure as above. The search will be done in the anchor and it's children (years). All these files have no fulltexts. So the result is always empty. == Proposed Implementation The proposed implementation does change the searchIn document limitation of the search plugin to search in current document _and_ all it's children. This is done on using Solr join feature. To make this possible, the partof field must use the docValues feature as "uid" does it already.
2020-11-20 16:16:48 +01:00
}
}
}
Fix search in documents for hierarchical documents like newspaper. == Current Situation The search plugin has the option to restrict the search in current document and/or in current collection. This can be used to place the plugin on a separate page to search only in one collection. Or you place the plugin on the workview page and trigger a search in document. Of course, the SearchInDocument plugin is better suited for the workview as the results are reported by via AJAX and the visitor will stay on the current page. On the opposite, using the search plugin makes it possible to search in all issues of a newspaper year or even a full newspaper title. At least, this should be possible. The current implementation does not work as expected. === Example ==== Search plugin on workview (issue) For example, you can place the plugin on level of a newspaper issue. If you launch a search, the current implementation will search not only in current issue but in all issues of the current year. This is a frequently demanded feature but works only by hasard. newspaper title (anchor) | |-> year | |-> Issue The current search plugin does two mistakes: 1. Instead of the current document uid, the parent uid is set. 2. The search is done in given uid and all children. ==== Search plugin on workview (year/anchor) If you place the search plugin on the workview with calendar view, the search fails. Same procedure as above. The search will be done in the anchor and it's children (years). All these files have no fulltexts. So the result is always empty. == Proposed Implementation The proposed implementation does change the searchIn document limitation of the search plugin to search in current document _and_ all it's children. This is done on using Solr join feature. To make this possible, the partof field must use the docValues feature as "uid" does it already.
2020-11-20 16:16:48 +01:00
if (!empty($documentId)) {
return '<input type="hidden" name="' . $this->prefixId . '[id]" value="' . $documentId . '" />';
}
}
return '';
}
2012-09-03 17:32:55 +02:00
/**
* Adds the encrypted Solr core name to the search form
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @return string HTML input fields with encrypted core name and hash
*/
2019-10-30 15:37:44 +01:00
protected function addEncryptedCoreName()
{
// Get core name.
2019-03-16 13:41:20 +01:00
$name = Helper::getIndexNameFromUid($this->conf['solrcore'], 'tx_dlf_solrcores');
// Encrypt core name.
if (!empty($name)) {
2019-03-13 08:29:51 +01:00
$name = Helper::encrypt($name);
}
// Add encrypted fields to search form.
2021-01-19 14:08:09 +01:00
if ($name !== false) {
return '<input type="hidden" name="' . $this->prefixId . '[encrypted]" value="' . $name . '" />';
} else {
return '';
}
}
/**
* Returns the extended search form and adds the JS files necessary for extended search.
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @return string The extended search form or an empty string
*/
2019-10-30 15:37:44 +01:00
protected function addExtendedSearch()
{
$extendedSearch = '';
// Quit without doing anything if no fields for extended search are selected.
2019-10-30 15:37:44 +01:00
if (
empty($this->conf['extendedSlotCount'])
|| empty($this->conf['extendedFields'])
) {
return $extendedSearch;
}
// Get operator options.
$operatorOptions = '';
2019-03-14 17:39:19 +01:00
foreach (['AND', 'OR', 'NOT'] as $operator) {
2020-05-11 18:13:47 +02:00
$operatorOptions .= '<option class="tx-dlf-search-operator-option tx-dlf-search-operator-' . $operator . '" value="' . $operator . '">' . htmlspecialchars($this->pi_getLL($operator, '')) . '</option>';
}
// Get field selector options.
$fieldSelectorOptions = '';
$searchFields = GeneralUtility::trimExplode(',', $this->conf['extendedFields'], true);
foreach ($searchFields as $searchField) {
2019-10-30 15:37:44 +01:00
$fieldSelectorOptions .= '<option class="tx-dlf-search-field-option tx-dlf-search-field-' . $searchField . '" value="' . $searchField . '">' . Helper::translate($searchField, 'tx_dlf_metadata', $this->conf['pages']) . '</option>';
}
for ($i = 0; $i < $this->conf['extendedSlotCount']; $i++) {
2019-03-14 17:39:19 +01:00
$markerArray = [
2019-10-30 15:37:44 +01:00
'###EXT_SEARCH_OPERATOR###' => '<select class="tx-dlf-search-operator tx-dlf-search-operator-' . $i . '" name="' . $this->prefixId . '[extOperator][' . $i . ']">' . $operatorOptions . '</select>',
'###EXT_SEARCH_FIELDSELECTOR###' => '<select class="tx-dlf-search-field tx-dlf-search-field-' . $i . '" name="' . $this->prefixId . '[extField][' . $i . ']">' . $fieldSelectorOptions . '</select>',
'###EXT_SEARCH_FIELDQUERY###' => '<input class="tx-dlf-search-query tx-dlf-search-query-' . $i . '" type="text" name="' . $this->prefixId . '[extQuery][' . $i . ']" />'
2019-03-14 17:39:19 +01:00
];
$extendedSearch .= $this->templateService->substituteMarkerArray($this->templateService->getSubpart($this->template, '###EXT_SEARCH_ENTRY###'), $markerArray);
}
return $extendedSearch;
}
/**
* Adds the facets menu to the search form
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @return string HTML output of facets menu
*/
2019-10-30 15:37:44 +01:00
protected function addFacetsMenu()
{
// Check for typoscript configuration to prevent fatal error.
if (empty($this->conf['facetsConf.'])) {
2019-03-15 11:03:54 +01:00
Helper::devLog('Incomplete plugin configuration', DEVLOG_SEVERITY_WARNING);
return '';
}
// Quit without doing anything if no facets are selected.
if (empty($this->conf['facets']) && empty($this->conf['facetCollections'])) {
return '';
}
// Get facets from plugin configuration.
2019-03-14 17:39:19 +01:00
$facets = [];
foreach (GeneralUtility::trimExplode(',', $this->conf['facets'], true) as $facet) {
2019-10-30 15:37:44 +01:00
$facets[$facet . '_faceting'] = Helper::translate($facet, 'tx_dlf_metadata', $this->conf['pages']);
}
// Render facets menu.
2019-03-14 17:39:19 +01:00
$TSconfig = [];
$TSconfig['special'] = 'userfunction';
2019-10-30 15:37:44 +01:00
$TSconfig['special.']['userFunc'] = \Kitodo\Dlf\Plugin\Search::class . '->makeFacetsMenuArray';
$TSconfig['special.']['facets'] = $facets;
$TSconfig['special.']['limit'] = max(intval($this->conf['limitFacets']), 1);
$TSconfig = Helper::mergeRecursiveWithOverrule($this->conf['facetsConf.'], $TSconfig);
2019-03-17 13:33:52 +01:00
return $this->cObj->cObjGetSingle('HMENU', $TSconfig);
}
/**
* Adds the fulltext switch to the search form
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-11-13 12:51:19 +01:00
* @param int $isFulltextSearch: Is fulltext search activated?
*
2019-03-14 22:41:35 +01:00
* @return string HTML output of fulltext switch
*/
2019-10-30 15:37:44 +01:00
protected function addFulltextSwitch($isFulltextSearch = 0)
{
$output = '';
// Check for plugin configuration.
if (!empty($this->conf['fulltext'])) {
2019-10-30 15:37:44 +01:00
$output .= ' <input class="tx-dlf-search-fulltext" id="tx-dlf-search-fulltext-no" type="radio" name="' . $this->prefixId . '[fulltext]" value="0" ' . ($isFulltextSearch == 0 ? 'checked="checked"' : '') . ' />';
2020-05-11 18:13:47 +02:00
$output .= ' <label for="tx-dlf-search-fulltext-no">' . htmlspecialchars($this->pi_getLL('label.inMetadata', '')) . '</label>';
2019-10-30 15:37:44 +01:00
$output .= ' <input class="tx-dlf-search-fulltext" id="tx-dlf-search-fulltext-yes" type="radio" name="' . $this->prefixId . '[fulltext]" value="1" ' . ($isFulltextSearch == 1 ? 'checked="checked"' : '') . '/>';
2020-05-11 18:13:47 +02:00
$output .= ' <label for="tx-dlf-search-fulltext-yes">' . htmlspecialchars($this->pi_getLL('label.inFulltext', '')) . '</label>';
}
return $output;
}
2015-12-17 17:32:06 +01:00
/**
* Adds the logical page field to the search form
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @return string HTML output of logical page field
*/
2019-10-30 15:37:44 +01:00
protected function addLogicalPage()
{
$output = '';
// Check for plugin configuration.
if (!empty($this->conf['showLogicalPageField'])) {
2020-05-11 18:13:47 +02:00
$output .= ' <label for="tx-dlf-search-logical-page">' . htmlspecialchars($this->pi_getLL('label.logicalPage', '')) . ': </label>';
2019-10-30 15:37:44 +01:00
$output .= ' <input class="tx-dlf-search-logical-page" id="tx-dlf-search-logical-page" type="text" name="' . $this->prefixId . '[logicalPage]" />';
}
return $output;
}
/**
* Creates an array for a HMENU entry of a facet value.
*
2019-03-14 22:41:35 +01:00
* @access protected
*
2019-03-14 22:41:35 +01:00
* @param string $field: The facet's index_name
* @param string $value: The facet's value
2019-11-13 12:51:19 +01:00
* @param int $count: Number of hits for this facet
2019-03-14 22:41:35 +01:00
* @param array $search: The parameters of the current search query
* @param string &$state: The state of the parent item
*
* @return array The array for the facet's menu entry
*/
2019-10-30 15:37:44 +01:00
protected function getFacetsMenuEntry($field, $value, $count, $search, &$state)
{
2019-03-14 17:39:19 +01:00
$entryArray = [];
// Translate value.
if ($field == 'owner_faceting') {
// Translate name of holding library.
2019-03-13 08:29:51 +01:00
$entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_libraries', $this->conf['pages']));
} elseif ($field == 'type_faceting') {
// Translate document type.
2019-03-13 08:29:51 +01:00
$entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_structures', $this->conf['pages']));
} elseif ($field == 'collection_faceting') {
// Translate name of collection.
2019-03-13 08:29:51 +01:00
$entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_collections', $this->conf['pages']));
} elseif ($field == 'language_faceting') {
// Translate ISO 639 language code.
2019-03-13 08:29:51 +01:00
$entryArray['title'] = htmlspecialchars(Helper::getLanguageName($value));
} else {
$entryArray['title'] = htmlspecialchars($value);
}
$entryArray['count'] = $count;
$entryArray['doNotLinkIt'] = 0;
// Check if facet is already selected.
$queryColumn = array_column($search['params']['filterquery'], 'query');
2019-10-30 15:37:44 +01:00
$index = array_search($field . ':("' . Solr::escapeQuery($value) . '")', $queryColumn);
if ($index !== false) {
// Facet is selected, thus remove it from filter.
unset($queryColumn[$index]);
$queryColumn = array_values($queryColumn);
$entryArray['ITEM_STATE'] = 'CUR';
$state = 'ACTIFSUB';
//Reset facets
if ($this->conf['resetFacets']) {
//remove ($count) for selected facet in template
$entryArray['count'] = false;
//build link to delete selected facet
2019-03-14 17:39:19 +01:00
$entryArray['_OVERRIDE_HREF'] = $this->pi_linkTP_keepPIvars_url(['query' => $search['query'], 'fq' => $queryColumn]);
$entryArray['title'] = sprintf($this->pi_getLL('resetFacet', ''), $entryArray['title']);
}
} else {
// Facet is not selected, thus add it to filter.
2019-10-30 15:37:44 +01:00
$queryColumn[] = $field . ':("' . Solr::escapeQuery($value) . '")';
$entryArray['ITEM_STATE'] = 'NO';
}
2019-03-14 17:39:19 +01:00
$entryArray['_OVERRIDE_HREF'] = $this->pi_linkTP_keepPIvars_url(['query' => $search['query'], 'fq' => $queryColumn]);
return $entryArray;
}
/**
* The main method of the PlugIn
*
2019-03-14 22:41:35 +01:00
* @access public
*
2019-03-14 22:41:35 +01:00
* @param string $content: The PlugIn content
* @param array $conf: The PlugIn configuration
*
2019-03-14 22:41:35 +01:00
* @return string The content that is displayed on the website
*/
2019-10-30 15:37:44 +01:00
public function main($content, $conf)
{
$this->init($conf);
// Disable caching for this plugin.
$this->setCache(false);
// Quit without doing anything if required variables are not set.
if (empty($this->conf['solrcore'])) {
2019-03-15 11:03:54 +01:00
Helper::devLog('Incomplete plugin configuration', DEVLOG_SEVERITY_WARNING);
return $content;
}
2019-10-30 15:37:44 +01:00
if (
!isset($this->piVars['query'])
&& empty($this->piVars['extQuery'])
) {
// Extract query and filter from last search.
$list = GeneralUtility::makeInstance(DocumentList::class);
if (!empty($list->metadata['searchString'])) {
if ($list->metadata['options']['source'] == 'search') {
$search['query'] = $list->metadata['searchString'];
}
$search['params'] = $list->metadata['options']['params'];
}
// Add javascript for search suggestions if enabled and jQuery autocompletion is available.
if (!empty($this->conf['suggest'])) {
$this->addAutocompleteJS();
}
// Load template file.
$this->getTemplate();
// Configure @action URL for form.
2019-03-14 17:39:19 +01:00
$linkConf = [
'parameter' => $GLOBALS['TSFE']->id,
'forceAbsoluteUrl' => !empty($this->conf['forceAbsoluteUrl']) ? 1 : 0,
'forceAbsoluteUrl.' => ['scheme' => !empty($this->conf['forceAbsoluteUrl']) && !empty($this->conf['forceAbsoluteUrlHttps']) ? 'https' : 'http']
2019-03-14 17:39:19 +01:00
];
// Fill markers.
2019-03-14 17:39:19 +01:00
$markerArray = [
'###ACTION_URL###' => $this->cObj->typoLink_URL($linkConf),
2020-05-11 18:13:47 +02:00
'###LABEL_QUERY###' => (!empty($search['query']) ? htmlspecialchars($search['query']) : htmlspecialchars($this->pi_getLL('label.query'))),
'###LABEL_SUBMIT###' => htmlspecialchars($this->pi_getLL('label.submit')),
2019-10-30 15:37:44 +01:00
'###FIELD_QUERY###' => $this->prefixId . '[query]',
'###QUERY###' => (!empty($search['query']) ? htmlspecialchars($search['query']) : ''),
'###FULLTEXTSWITCH###' => $this->addFulltextSwitch($list->metadata['fulltextSearch']),
'###FIELD_DOC###' => ($this->conf['searchIn'] == 'document' || $this->conf['searchIn'] == 'all' ? $this->addCurrentDocument() : ''),
'###FIELD_COLL###' => ($this->conf['searchIn'] == 'collection' || $this->conf['searchIn'] == 'all' ? $this->addCurrentCollection() : ''),
'###ADDITIONAL_INPUTS###' => $this->addEncryptedCoreName(),
'###FACETS_MENU###' => $this->addFacetsMenu(),
'###LOGICAL_PAGE###' => $this->addLogicalPage()
2019-03-14 17:39:19 +01:00
];
// Get additional fields for extended search.
$extendedSearch = $this->addExtendedSearch();
// Display search form.
$content .= $this->templateService->substituteSubpart($this->templateService->substituteMarkerArray($this->template, $markerArray), '###EXT_SEARCH_ENTRY###', $extendedSearch);
return $this->pi_wrapInBaseClass($content);
} else {
// Build label for result list.
2020-05-11 18:13:47 +02:00
$label = htmlspecialchars($this->pi_getLL('search', ''));
if (!empty($this->piVars['query'])) {
$label .= htmlspecialchars(sprintf($this->pi_getLL('for', ''), trim($this->piVars['query'])));
}
// Prepare query parameters.
2019-03-14 17:39:19 +01:00
$params = [];
$matches = [];
// Set search query.
2019-11-13 13:51:28 +01:00
if (
(!empty($this->conf['fulltext']) && !empty($this->piVars['fulltext']))
2019-10-30 15:37:44 +01:00
|| preg_match('/fulltext:\((.*)\)/', trim($this->piVars['query']), $matches)
) {
// If the query already is a fulltext query e.g using the facets
$this->piVars['query'] = empty($matches[1]) ? $this->piVars['query'] : $matches[1];
2019-11-13 14:00:11 +01:00
// Search in fulltext field if applicable. Query must not be empty!
if (!empty($this->piVars['query'])) {
2019-10-30 15:37:44 +01:00
$query = 'fulltext:(' . Solr::escapeQuery(trim($this->piVars['query'])) . ')';
}
} else {
// Retain given search field if valid.
$query = Solr::escapeQueryKeepField(trim($this->piVars['query']), $this->conf['pages']);
}
// Add extended search query.
2019-10-30 15:37:44 +01:00
if (
!empty($this->piVars['extQuery'])
&& is_array($this->piVars['extQuery'])
) {
2019-03-14 17:39:19 +01:00
$allowedOperators = ['AND', 'OR', 'NOT'];
$allowedFields = GeneralUtility::trimExplode(',', $this->conf['extendedFields'], true);
2019-11-05 16:31:35 +01:00
$numberOfExtQueries = count($this->piVars['extQuery']);
for ($i = 0; $i < $numberOfExtQueries; $i++) {
if (!empty($this->piVars['extQuery'][$i])) {
2019-10-30 15:37:44 +01:00
if (
in_array($this->piVars['extOperator'][$i], $allowedOperators)
&& in_array($this->piVars['extField'][$i], $allowedFields)
) {
if (!empty($query)) {
2019-10-30 15:37:44 +01:00
$query .= ' ' . $this->piVars['extOperator'][$i] . ' ';
}
2019-10-30 15:37:44 +01:00
$query .= Indexer::getIndexFieldName($this->piVars['extField'][$i], $this->conf['pages']) . ':(' . Solr::escapeQuery($this->piVars['extQuery'][$i]) . ')';
}
}
}
}
// Add filter query for faceting.
if (!empty($this->piVars['fq'])) {
foreach ($this->piVars['fq'] as $filterQuery) {
$params['filterquery'][]['query'] = $filterQuery;
}
}
// Add filter query for in-document searching.
2019-10-30 15:37:44 +01:00
if (
$this->conf['searchIn'] == 'document'
|| $this->conf['searchIn'] == 'all'
) {
if (
!empty($this->piVars['id'])
&& \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->piVars['id'])
) {
2021-03-10 10:33:30 +01:00
// Search in document and all subordinates (valid for up to three levels of hierarchy).
$params['filterquery'][]['query'] = '_query_:"{!join from=uid to=partof}uid:{!join from=uid to=partof}uid:' . $this->piVars['id'] . '"' .
' OR {!join from=uid to=partof}uid:' . $this->piVars['id'] .
' OR uid:' . $this->piVars['id'];
2019-03-13 08:29:51 +01:00
$label .= htmlspecialchars(sprintf($this->pi_getLL('in', ''), Document::getTitle($this->piVars['id'])));
}
}
// Add filter query for in-collection searching.
2019-10-30 15:37:44 +01:00
if (
$this->conf['searchIn'] == 'collection'
|| $this->conf['searchIn'] == 'all'
) {
if (
!empty($this->piVars['collection'])
&& \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->piVars['collection'])
) {
2019-03-16 13:41:20 +01:00
$index_name = Helper::getIndexNameFromUid($this->piVars['collection'], 'tx_dlf_collections', $this->conf['pages']);
2019-10-30 15:37:44 +01:00
$params['filterquery'][]['query'] = 'collection_faceting:("' . Solr::escapeQuery($index_name) . '")';
$label .= sprintf($this->pi_getLL('in', '', true), Helper::translate($index_name, 'tx_dlf_collections', $this->conf['pages']));
}
}
// Add filter query for collection restrictions.
if ($this->conf['collections']) {
$collIds = explode(',', $this->conf['collections']);
2019-03-14 17:39:19 +01:00
$collIndexNames = [];
foreach ($collIds as $collId) {
2019-03-16 13:41:20 +01:00
$collIndexNames[] = Solr::escapeQuery(Helper::getIndexNameFromUid(intval($collId), 'tx_dlf_collections', $this->conf['pages']));
}
// Last value is fake and used for distinction in $this->addCurrentCollection()
2019-10-30 15:37:44 +01:00
$params['filterquery'][]['query'] = 'collection_faceting:("' . implode('" OR "', $collIndexNames) . '" OR "FakeValueForDistinction")';
}
// Set some query parameters.
2019-11-13 14:00:11 +01:00
$params['query'] = !empty($query) ? $query : '*';
$params['start'] = 0;
$params['rows'] = 0;
2019-03-14 17:39:19 +01:00
$params['sort'] = ['score' => 'desc'];
// Instantiate search object.
2019-03-13 08:29:51 +01:00
$solr = Solr::getInstance($this->conf['solrcore']);
if (!$solr->ready) {
2019-03-15 11:03:54 +01:00
Helper::devLog('Apache Solr not available', DEVLOG_SEVERITY_ERROR);
return $content;
}
// Set search parameters.
$solr->cPid = $this->conf['pages'];
$solr->params = $params;
// Perform search.
$list = $solr->search();
2019-03-14 17:39:19 +01:00
$list->metadata = [
'label' => $label,
'thumbnail' => '',
'searchString' => $this->piVars['query'],
'fulltextSearch' => (!empty($this->piVars['fulltext']) ? '1' : '0'),
'options' => $list->metadata['options']
2019-03-14 17:39:19 +01:00
];
$list->save();
// Clean output buffer.
ob_end_clean();
2019-03-14 17:39:19 +01:00
$additionalParams = [];
if (!empty($this->piVars['logicalPage'])) {
$additionalParams['logicalPage'] = $this->piVars['logicalPage'];
}
// Jump directly to the page view, if there is only one result and it is configured
if ($list->metadata['options']['numberOfHits'] == 1 && !empty($this->conf['showSingleResult'])) {
$linkConf['parameter'] = $this->conf['targetPidPageView'];
$additionalParams['id'] = $list->current()['uid'];
$additionalParams['highlight_word'] = preg_replace('/\s\s+/', ';', $list->metadata['searchString']);
$additionalParams['page'] = count($list[0]['subparts']) == 1 ? $list[0]['subparts'][0]['page'] : 1;
} else {
// Keep some plugin variables.
$linkConf['parameter'] = $this->conf['targetPid'];
if (!empty($this->piVars['order'])) {
$additionalParams['order'] = $this->piVars['order'];
$additionalParams['asc'] = !empty($this->piVars['asc']) ? '1' : '0';
}
}
$linkConf['forceAbsoluteUrl'] = !empty($this->conf['forceAbsoluteUrl']) ? 1 : 0;
$linkConf['forceAbsoluteUrl.']['scheme'] = !empty($this->conf['forceAbsoluteUrl']) && !empty($this->conf['forceAbsoluteUrlHttps']) ? 'https' : 'http';
$linkConf['additionalParams'] = GeneralUtility::implodeArrayForUrl($this->prefixId, $additionalParams, '', true, false);
// Send headers.
2019-10-30 15:37:44 +01:00
header('Location: ' . GeneralUtility::locationHeaderUrl($this->cObj->typoLink_URL($linkConf)));
exit;
}
}
/**
* This builds a menu array for HMENU
*
2019-03-14 22:41:35 +01:00
* @access public
*
2019-03-14 22:41:35 +01:00
* @param string $content: The PlugIn content
* @param array $conf: The PlugIn configuration
*
2019-03-14 22:41:35 +01:00
* @return array HMENU array
*/
2019-10-30 15:37:44 +01:00
public function makeFacetsMenuArray($content, $conf)
{
$this->init($conf);
2019-03-14 17:39:19 +01:00
$menuArray = [];
// Set default value for facet search.
2019-03-14 17:39:19 +01:00
$search = [
'query' => '*',
2019-03-14 17:39:19 +01:00
'params' => [
'component' => [
'facetset' => [
'facet' => []
]
]
]
];
// Extract query and filter from last search.
$list = GeneralUtility::makeInstance(DocumentList::class);
if (!empty($list->metadata['options']['source'])) {
if ($list->metadata['options']['source'] == 'search') {
$search['query'] = $list->metadata['options']['select'];
}
$search['params'] = $list->metadata['options']['params'];
}
// Get applicable facets.
2019-03-13 08:29:51 +01:00
$solr = Solr::getInstance($this->conf['solrcore']);
if (!$solr->ready) {
2019-03-15 11:03:54 +01:00
Helper::devLog('Apache Solr not available', DEVLOG_SEVERITY_ERROR);
2019-03-14 17:39:19 +01:00
return [];
}
// Set needed parameters for facet search.
if (empty($search['params']['filterquery'])) {
2019-03-14 17:39:19 +01:00
$search['params']['filterquery'] = [];
}
2019-03-16 15:08:43 +01:00
foreach (array_keys($this->conf['facets']) as $field) {
2019-03-14 17:39:19 +01:00
$search['params']['component']['facetset']['facet'][] = [
'type' => 'field',
'key' => $field,
'field' => $field,
'limit' => $this->conf['limitFacets'],
'sort' => isset($this->conf['sortingFacets']) ? $this->conf['sortingFacets'] : 'count'
2019-03-14 17:39:19 +01:00
];
}
// Set additional query parameters.
$search['params']['start'] = 0;
$search['params']['rows'] = 0;
// Set query.
$search['params']['query'] = $search['query'];
// Perform search.
$selectQuery = $solr->service->createSelect($search['params']);
$results = $solr->service->select($selectQuery);
$facet = $results->getFacetSet();
2019-10-30 15:56:33 +01:00
$facetCollectionArray = [];
// replace everything expect numbers and comma
$facetCollections = preg_replace('/[^0-9,]/', '', $this->conf['facetCollections']);
if (!empty($facetCollections)) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_dlf_collections');
$result = $queryBuilder
->select('tx_dlf_collections.index_name AS index_name')
->from('tx_dlf_collections')
->where(
$queryBuilder->expr()->in(
'tx_dlf_collections.uid',
$queryBuilder->createNamedParameter(GeneralUtility::intExplode(',', $facetCollections), Connection::PARAM_INT_ARRAY)
)
)
->execute();
while ($collection = $result->fetch()) {
$facetCollectionArray[] = $collection['index_name'];
}
}
// Process results.
foreach ($facet as $field => $values) {
2019-03-14 17:39:19 +01:00
$entryArray = [];
$entryArray['title'] = htmlspecialchars($this->conf['facets'][$field]);
$entryArray['count'] = 0;
$entryArray['_OVERRIDE_HREF'] = '';
$entryArray['doNotLinkIt'] = 1;
$entryArray['ITEM_STATE'] = 'NO';
// Count number of facet values.
$i = 0;
foreach ($values as $value => $count) {
if ($count > 0) {
// check if facet collection configuration exists
if (!empty($this->conf['facetCollections'])) {
if ($field == "collection_faceting" && !in_array($value, $facetCollectionArray)) {
continue;
}
}
$entryArray['count']++;
if ($entryArray['ITEM_STATE'] == 'NO') {
$entryArray['ITEM_STATE'] = 'IFSUB';
}
$entryArray['_SUB_MENU'][] = $this->getFacetsMenuEntry($field, $value, $count, $search, $entryArray['ITEM_STATE']);
if (++$i == $this->conf['limit']) {
break;
}
} else {
break;
}
}
$menuArray[] = $entryArray;
}
return $menuArray;
}
2011-03-09 16:36:27 +01:00
}