protected function analyzeScore(array $resultDocument) { $highScores = array(); $debugData = $this->search->getDebugResponse()->explain->{$resultDocument['id']}; /* TODO Provide better parsing * * parsing could be done line by line, * * recording indentation level * * replacing abbreviations * * replacing phrases like "product of" by mathematical symbols (* or x) * * ... */ // matches search term weights, ex: 0.42218783 = (MATCH) weight(content:iPod^40.0 in 43), product of: $pattern = '/(.*) = \\(MATCH\\) weight\\((.*)\\^/'; $matches = array(); preg_match_all($pattern, $debugData, $matches); foreach ($matches[0] as $key => $value) { // split field from search term list($field, $searchTerm) = explode(':', $matches[2][$key]); // keep track of highest score per search term if (!isset($highScores[$field]) || $highScores[$field]['score'] < $matches[1][$key]) { $highScores[$field] = array('score' => $matches[1][$key], 'field' => $field, 'searchTerm' => $searchTerm); } } return $highScores; }
protected function getSortingLinks() { $sortHelper = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Sorting', $this->configuration['search.']['sorting.']['options.']); $query = $this->search->getQuery(); $queryLinkBuilder = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query\\LinkBuilder', $query); $queryLinkBuilder->setLinkTargetPageId($this->parentPlugin->getLinkTargetPageId()); $sortOptions = array(); $urlParameters = GeneralUtility::_GP('tx_solr'); $urlSortParameters = GeneralUtility::trimExplode(',', $urlParameters['sort']); $configuredSortOptions = $sortHelper->getSortOptions(); foreach ($configuredSortOptions as $sortOptionName => $sortOption) { $sortDirection = $this->configuration['search.']['sorting.']['defaultOrder']; if (!empty($this->configuration['search.']['sorting.']['options.'][$sortOptionName . '.']['defaultOrder'])) { $sortDirection = $this->configuration['search.']['sorting.']['options.'][$sortOptionName . '.']['defaultOrder']; } $sortIndicator = $sortDirection; $currentSortOption = ''; $currentSortDirection = ''; foreach ($urlSortParameters as $urlSortParameter) { $explodedUrlSortParameter = explode(' ', $urlSortParameter); if ($explodedUrlSortParameter[0] == $sortOptionName) { list($currentSortOption, $currentSortDirection) = $explodedUrlSortParameter; break; } } // toggle sorting direction for the current sorting field if ($currentSortOption == $sortOptionName) { switch ($currentSortDirection) { case 'asc': $sortDirection = 'desc'; $sortIndicator = 'asc'; break; case 'desc': $sortDirection = 'asc'; $sortIndicator = 'desc'; break; } } if (!empty($this->configuration['search.']['sorting.']['options.'][$sortOptionName . '.']['fixedOrder'])) { $sortDirection = $this->configuration['search.']['sorting.']['options.'][$sortOptionName . '.']['fixedOrder']; } $sortParameter = $sortOptionName . ' ' . $sortDirection; $temp = array('link' => $queryLinkBuilder->getQueryLink($sortOption['label'], array('sort' => $sortParameter)), 'url' => $queryLinkBuilder->getQueryUrl(array('sort' => $sortParameter)), 'optionName' => $sortOptionName, 'field' => $sortOption['field'], 'label' => $sortOption['label'], 'is_current' => '0', 'direction' => $sortDirection, 'indicator' => $sortIndicator, 'current_direction' => ' '); // set sort indicator for the current sorting field if ($currentSortOption == $sortOptionName) { $temp['selected'] = 'selected="selected"'; $temp['current'] = 'current'; $temp['is_current'] = '1'; $temp['current_direction'] = $sortIndicator; } // special case relevance: just reset the search to normal behavior if ($sortOptionName == 'relevance') { $temp['link'] = $queryLinkBuilder->getQueryLink($sortOption['label'], array('sort' => NULL)); $temp['url'] = $queryLinkBuilder->getQueryUrl(array('sort' => NULL)); unset($temp['direction'], $temp['indicator']); } $sortOptions[] = $temp; } return $sortOptions; }
/** * Resolves the current iteration index (relative) of a loop to the absolute * number counting from zero of the total number of results. * * @param array $arguments * @return string */ public function execute(array $arguments = array()) { $numberOfResults = $this->search->getNumberOfResults(); $currentIterationIndex = $arguments[0]; $resultsPerPage = $this->search->getResultsPerPage(); $currentPage = 0; $getParameters = GeneralUtility::_GET('tx_solr'); if (isset($getParameters['page'])) { $currentPage = intval($getParameters['page']); } return $currentPage * $resultsPerPage + $currentIterationIndex; }
/** * Modifies the given query and returns the modified query as result * * @param ResultsCommand $resultCommand The search result command * @param array $resultDocument Result document * @return array The document with fields as array */ public function modifyResultDocument(ResultsCommand $resultCommand, array $resultDocument) { $this->search = $resultCommand->getParentPlugin()->getSearchResultSetService()->getSearch(); // only check whether a BE user is logged in, don't need to check // for enabled score analysis as we wouldn't be here if it was disabled if ($GLOBALS['TSFE']->beUserLogin) { $configuration = Util::getSolrConfiguration(); $queryFields = $configuration->getSearchQueryQueryFields(); $debugData = $this->search->getDebugResponse()->explain->{$resultDocument['id']}; $scoreCalculationService = GeneralUtility::makeInstance(ScoreCalculationService::class); $resultDocument['score_analysis'] = $scoreCalculationService->getRenderedScores($debugData, $queryFields); } return $resultDocument; }
/** * Creates the HTML for the relevance output * * @param array $arguments Array of arguments, [0] is expected to contain the result document. * @return string The score as percent value. */ public function execute(array $arguments = array()) { $content = ''; $document = $arguments[0]; if (count($arguments) > 1) { // a pipe character caused the serialized document to be split up $document = implode('|', $arguments); } if ($this->search->hasSearched() && $this->search->getNumberOfResults()) { $score = $this->getScore($document); $maximumScore = $this->getMaximumScore($document); $content = $this->render($score, $maximumScore); } return $content; }
/** * Modifies the given document and returns the modified document as result. * * @param ResultsCommand $resultCommand The search result command * @param array $resultDocument Result document as array * @return array The document with fields as array */ public function modifyResultDocument(ResultsCommand $resultCommand, array $resultDocument) { $this->search = $resultCommand->getParentPlugin()->getSearchResultSetService()->getSearch(); $highlightedContent = $this->search->getHighlightedContent(); foreach ($this->highlightFields as $highlightField) { if (!empty($highlightedContent->{$resultDocument['id']}->{$highlightField}[0])) { $fragments = array(); foreach ($highlightedContent->{$resultDocument['id']}->{$highlightField} as $fragment) { $fragments[] = Template::escapeMarkers($fragment); } $resultDocument[$highlightField] = implode(' ' . $this->fragmentSeparator . ' ', $fragments); } } return $resultDocument; }
/** * Creates a link to a given page with a given link text with the current * tx_solr parameters appended to the URL * * @param array Array of arguments, [0] is the link text, [1] is the (optional) page Id to link to (otherwise TSFE->id), [2] are additional URL parameters, [3] use cache, defaults to FALSE * @return string complete anchor tag with URL and link text */ public function execute(array $arguments = array()) { $linkText = $arguments[0]; $pageId = $this->determinePageId(trim($arguments[1])); $additionalUrlParameters = $arguments[2] ? $arguments[2] : ''; $useCache = $arguments[3] ? true : false; $returnOnlyUrl = $arguments[4] ? true : false; // FIXME pass anything not prefixed with tx_solr in $additionalParameters as 4th parameter $additionalUrlParameters = GeneralUtility::explodeUrl2Array($additionalUrlParameters, true); $solrUrlParameters = array(); if (!empty($additionalUrlParameters['tx_solr'])) { $solrUrlParameters = $additionalUrlParameters['tx_solr']; } $linkConfiguration = array('useCacheHash' => $useCache); if ($returnOnlyUrl) { $linkConfiguration['returnLast'] = 'url'; } if ($this->search->hasSearched()) { $query = $this->search->getQuery(); } else { $query = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query', ''); } $linkBuilder = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query\\LinkBuilder', $query); $linkBuilder->setLinkTargetPageId($pageId); $queryLink = $linkBuilder->getQueryLink($linkText, $solrUrlParameters, $linkConfiguration); return $queryLink; }
/** * Modifies the given document and returns the modified document as result. * * @param ResultsCommand $resultCommand The search result command * @param array $resultDocument Result document as array * @return array The document with fields as array */ public function modifyResultDocument(ResultsCommand $resultCommand, array $resultDocument) { $this->search = $resultCommand->getParentPlugin()->getSearch(); $configuration = Util::getSolrConfiguration(); $highlightedContent = $this->search->getHighlightedContent(); $highlightFields = GeneralUtility::trimExplode(',', $configuration['search.']['results.']['resultsHighlighting.']['highlightFields'], true); foreach ($highlightFields as $highlightField) { if (!empty($highlightedContent->{$resultDocument['id']}->{$highlightField}[0])) { $fragments = array(); foreach ($highlightedContent->{$resultDocument['id']}->{$highlightField} as $fragment) { $fragments[] = Template::escapeMarkers($fragment); } $resultDocument[$highlightField] = implode(' ' . $configuration['search.']['results.']['resultsHighlighting.']['fragmentSeparator'] . ' ', $fragments); } } return $resultDocument; }
/** * Query link with a suggested/corrected query * * @return string Suggestion/spell checked query link */ public function getSuggestionQueryLink() { $suggestions = $this->getSuggestions(); $query = clone $this->search->getQuery(); $query->setKeywords($suggestions['collation']); $queryLinkBuilder = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query\\LinkBuilder', $query); $queryLinkBuilder->setLinkTargetPageId($GLOBALS['TSFE']->id); return $queryLinkBuilder->getQueryLink(htmlspecialchars($query->getKeywordsRaw())); }
/** * Retrieves a single document from solr by document id. * * @param string $documentId * @return \ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResult */ public function getDocumentById($documentId) { /* @var $query Query */ $query = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query', $documentId); $query->setQueryFieldsFromString('id'); $response = $this->search->search($query, 0, 1); $this->processResponse($documentId, $query, $response); $resultDocument = isset($response->response->docs[0]) ? $response->response->docs[0] : null; return $resultDocument; }
/** * Constructor. * * @param Facet $facet The facet to render. */ public function __construct(Facet $facet) { $this->search = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Search'); $this->facet = $facet; $this->facetName = $facet->getName(); $this->solrConfiguration = Util::getSolrConfiguration(); $this->facetConfiguration = $this->solrConfiguration['search.']['faceting.']['facets.'][$this->facetName . '.']; $this->linkTargetPageId = $GLOBALS['TSFE']->id; $this->queryLinkBuilder = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query\\LinkBuilder', $this->search->getQuery()); }
/** * Generates a link with spell checking suggestions if it is activated and * spell checking suggestions are returned by Solr. * * @return string A link to start over with a new search using the suggested keywords. */ public function getSpellCheckingSuggestions() { $suggestionsLink = ''; if ($this->configuration['search.']['spellchecking'] && $this->search->hasSearched()) { $suggestions = $this->getSuggestions(); if ($suggestions && !$suggestions['correctlySpelled'] && !empty($suggestions['collation'])) { $suggestionsLink = $GLOBALS['TSFE']->cObj->noTrimWrap($this->getSuggestionQueryLink(), $this->configuration['search.']['spellchecking.']['wrap']); } } return $suggestionsLink; }
/** * Queries Solr for the current page's documents. * * @return array An array of Apache_Solr_Document objects */ protected function getIndexDocuments() { /* @var Query $query */ $query = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query', ''); $query->setQueryType('standard'); $query->useRawQueryString(true); $query->setQueryString('*:*'); $query->addFilter('(type:pages AND uid:' . $this->pageId . ') OR (*:* AND pid:' . $this->pageId . ' NOT type:pages)'); $query->addFilter('siteHash:' . Site::getSiteByPageId($this->pageId)->getSiteHash()); $query->setFieldList('*'); $query->setSorting('type asc, title asc'); $this->search->search($query, 0, 10000); return $this->search->getResultDocumentsEscaped(); }
/** * Creates an Apache_Solr_Document from a raw stdClass object as parsed by * SolrPhpClient. * * For compatibility reasons taken from Apache_Solr_Response->_parseData() * * @param \stdClass $rawDocument The raw document as initially returned by SolrPhpClient * @return \Apache_Solr_Document Apache Solr Document */ private function createApacheSolrDocument(\stdClass $rawDocument) { $collapseSingleValueArrays = $this->search->getSolrConnection()->getCollapseSingleValueArrays(); $document = new \Apache_Solr_Document(); foreach ($rawDocument as $key => $value) { // If a result is an array with only a single value // then its nice to be able to access it // as if it were always a single value if ($collapseSingleValueArrays && is_array($value) && count($value) <= 1) { $value = array_shift($value); } $document->{$key} = $value; } return $document; }
/** * Gets the facet's options * * @return array An array with facet options. */ public function getOptionsRaw() { $facetOptions = array(); switch ($this->type) { case self::TYPE_FIELD: $facetOptions = $this->search->getFacetFieldOptions($this->field); break; case self::TYPE_QUERY: $facetOptions = $this->search->getFacetQueryOptions($this->field); break; case self::TYPE_RANGE: $facetOptions = $this->search->getFacetRangeOptions($this->field); break; } return $facetOptions; }
/** * Renders facets selected by the user. * * @return string rendered selected facets subpart */ protected function renderUsedFacets() { $template = clone $this->parentPlugin->getTemplate(); $template->workOnSubpart('used_facets'); $query = $this->search->getQuery(); $queryLinkBuilder = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query\\LinkBuilder', $this->search->getQuery()); /* @var $queryLinkBuilder LinkBuilder */ $queryLinkBuilder->setLinkTargetPageId($this->parentPlugin->getLinkTargetPageId()); // URL parameters added to facet URLs may not need to be added to the facets reset URL $facetLinkUrlParameters = $this->configuration->getSearchFacetingFacetLinkUrlParametersAsArray(); $useForFacetResetLink = $this->configuration->getSearchFacetingFacetLinkUrlParametersUseForFacetResetLinkUrl(); if (count($facetLinkUrlParameters) > 0 && $useForFacetResetLink) { $addedUrlParameterKeys = array_keys($facetLinkUrlParameters); foreach ($addedUrlParameterKeys as $addedUrlParameterKey) { if (GeneralUtility::isFirstPartOfStr($addedUrlParameterKey, 'tx_solr')) { $addedUrlParameterKey = substr($addedUrlParameterKey, 8, -1); $queryLinkBuilder->addUnwantedUrlParameter($addedUrlParameterKey); } } } $resultParameters = GeneralUtility::_GET('tx_solr'); $filterParameters = array(); if (isset($resultParameters['filter'])) { $filterParameters = (array) array_map('urldecode', $resultParameters['filter']); } $facetsInUse = array(); foreach ($filterParameters as $filter) { // only split by the first ":" to allow the use of colons in the filter value list($filterName, $filterValue) = explode(':', $filter, 2); $facetConfiguration = $this->configuration->getSearchFacetingFacetByName($filterName); // don't render facets that should not be included in used facets if (isset($facetConfiguration['includeInUsedFacets']) && $facetConfiguration['includeInUsedFacets'] == '0') { continue; } $usedFacetRenderer = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Facet\\UsedFacetRenderer', $filterName, $filterValue, $filter, $this->parentPlugin->getTemplate(), $query); $usedFacetRenderer->setLinkTargetPageId($this->parentPlugin->getLinkTargetPageId()); $facetToRemove = $usedFacetRenderer->render(); $facetsInUse[] = $facetToRemove; } $template->addLoop('facets_in_use', 'remove_facet', $facetsInUse); $template->addVariable('remove_all_facets', array('url' => $queryLinkBuilder->getQueryUrl(array('filter' => array())), 'text' => '###LLL:faceting_removeAllFilters###')); $content = ''; if (count($facetsInUse)) { $content = $template->render(); } return $content; }
/** * Performs a search and returns a SearchResultSet. * * @param SearchRequest $searchRequest * @return SearchResultSet */ public function search(SearchRequest $searchRequest) { /** @var $resultSet SearchResultSet */ $resultSetClass = $this->getResultSetClassName(); $resultSet = GeneralUtility::makeInstance($resultSetClass); $resultSet->setUsedSearchRequest($searchRequest); $this->lastResultSet = $resultSet; $resultSet = $this->handleSearchHook('beforeSearch', $resultSet); if ($searchRequest->getRawUserQueryIsNull() && !$this->getInitialSearchIsConfigured()) { // when no rawQuery was passed or no initialSearch is configured, we pass an empty result set return $resultSet; } if ($searchRequest->getRawUserQueryIsEmptyString() && !$this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) { // the user entered an empty query string "" or " " and empty querystring is not allowed return $resultSet; } $rawQuery = $searchRequest->getRawUserQuery(); $resultsPerPage = $this->getNumberOfResultsPerPage($rawQuery, $searchRequest->getResultsPerPage()); $query = $this->getPreparedQuery($rawQuery, $resultsPerPage); $resultSet->setUsedQuery($query); $currentPage = max(0, $searchRequest->getPage()); // if the number of results per page has been changed by the current request, reset the pagebrowser if ($this->resultsPerPageChanged) { $currentPage = 0; } $offSet = $currentPage * $resultsPerPage; // performing the actual search, sending the query to the Solr server $response = $this->search->search($query, $offSet, null); $this->processResponse($rawQuery, $query, $response); $resultSet->setResponse($response); $resultSet->setUsedPage($currentPage); $resultSet->setUsedResultsPerPage($resultsPerPage); $resultSet->setUsedAdditionalFilters($this->getAdditionalFilters()); $resultSet->setUsedSearch($this->search); return $this->handleSearchHook('afterSearch', $resultSet); }
/** * Constructor * * @param array $arguments */ public function __construct(array $arguments = array()) { $this->search = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Search'); $this->configuration = Util::getSolrConfiguration(); $this->queryLinkBuilder = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Query\\LinkBuilder', $this->search->getQuery()); }
/** * Initializes the Solr connection and tests the connection through a ping. * */ protected function initializeSearch() { $solrConnection = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\ConnectionManager')->getConnectionByPageId($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sys_language_uid, $GLOBALS['TSFE']->MP); $this->search = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Search', $solrConnection); $this->solrAvailable = $this->search->ping(); }
/** * @param string $expextedQueryString * @param int $expectedOffset * @param \Apache_Solr_Response $fakeResponse */ public function assertOneSearchWillBeTriggeredWithQueryAndShouldReturnFakeResponse($expextedQueryString, $expectedOffset, \Apache_Solr_Response $fakeResponse) { $this->searchMock->expects($this->once())->method('search')->with($expextedQueryString, $expectedOffset, null)->will($this->returnValue($fakeResponse)); }
/** * Determines whether filters have been applied to the query or not. * * @return string 1 if filters are applied, 0 if not (for use in templates) */ protected function isFiltered() { $filters = $this->search->getQuery()->getFilters(); $filtered = !empty($filters); return $filtered ? '1' : '0'; }