/** * @test */ public function databaseResultIsUsedWhenNoCachedResultIsPresent() { $fakeConfiguration = array('select.' => array('checkRootPageId' => true, 'checkLanguage' => true, 'SELECT' => '', 'FROM' => '', 'ADD_WHERE' => '', 'GROUP_BY' => '', 'ORDER_BY' => ''), 'limit'); $this->configurationMock->expects($this->once())->method('getSearchFrequentSearchesConfiguration')->will($this->returnValue($fakeConfiguration)); $this->databaseMock->expects($this->once())->method('exec_SELECTgetRows')->will($this->returnValue(array(array('search_term' => 'my search', 'hits' => 22)))); //we fake that we have no frequent searches in the cache and therefore expect that the database will be queried $this->fakeIdentifierNotInCache(); $frequentTerms = $this->frequentSearchesService->getFrequentSearchTerms(); $this->assertSame($frequentTerms, array('my search' => 22), 'Could not retrieve frequent search terms'); }
/** * Returns an URL that switches sorting to the given sort option * * @param array $arguments * @return string */ public function execute(array $arguments = array()) { $sortUrl = ''; $urlParameters = GeneralUtility::_GP('tx_solr'); $urlSortParameters = GeneralUtility::trimExplode(',', $urlParameters['sort']); $sortOptions = GeneralUtility::trimExplode(',', $arguments[0]); $currentSortOption = ''; $sortHelper = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\Sorting', $this->configuration->getValueByPathOrDefaultValue('plugin.tx_solr.search.sorting.options.', array())); $configuredSortOptions = $sortHelper->getSortOptions(); $sortParameters = array(); foreach ($sortOptions as $sortOption) { if (isset($configuredSortOptions[$sortOption])) { $sortDirection = $this->configuration->getValueByPathOrDefaultValue('plugin.tx_solr.search.sorting.defaultOrder', 'asc'); if (isset($configuredSortOptions[$sortOption]['fixedOrder'])) { $sortDirection = $configuredSortOptions[$sortOption]['fixedOrder']; } elseif (isset($configuredSortOptions[$sortOption]['defaultOrder'])) { $sortDirection = $configuredSortOptions[$sortOption]['defaultOrder']; } $sortParameter = $sortOption . ' ' . $sortDirection; foreach ($urlSortParameters as $urlSortParameter) { $explodedUrlSortParameter = explode(' ', $urlSortParameter); if ($explodedUrlSortParameter[0] == $sortOption) { list($currentSortOption, $currentSortDirection) = $explodedUrlSortParameter; break; } } if ($currentSortOption == $sortOption) { switch ($currentSortDirection) { case 'asc': $sortDirection = 'desc'; break; case 'desc': $sortDirection = 'asc'; break; } $fixedOrder = $this->configuration->getValueByPath('plugin.tx_solr.search.sorting.options.' . $sortOption . '.fixedOrder'); if (!is_null($fixedOrder)) { $sortDirection = $fixedOrder; } $sortParameter = $sortOption . ' ' . $sortDirection; } $sortParameters[] = $sortParameter; } } $sortUrl = $this->queryLinkBuilder->getQueryUrl(array('sort' => implode(', ', $sortParameters))); return $sortUrl; }
/** * Gets the sequence id for the next search entry. * * @return int The id to be used as the next sequence id for storing the last search keywords. */ protected function getNextSequenceId() { $nextSequenceId = 0; $numberOfLastSearchesToLog = (int) $this->configuration->getSearchLastSearchesLimit(); $row = $this->database->exec_SELECTgetRows('(sequence_id + 1) % ' . $numberOfLastSearchesToLog . ' as next_sequence_id', 'tx_solr_last_searches', '', '', 'tstamp DESC', 1); if (!empty($row)) { $nextSequenceId = $row[0]['next_sequence_id']; } return $nextSequenceId; }
/** * A factory method to get an indexer depending on an item's configuration. * * By default all items are indexed using the default indexer * (ApacheSolrForTypo3\Solr\IndexQueue\Indexer) coming with EXT:solr. Pages by default are * configured to be indexed through a dedicated indexer * (ApacheSolrForTypo3\Solr\IndexQueue\PageIndexer). In all other cases a dedicated indexer * can be specified through TypoScript if needed. * * @param string $indexingConfigurationName Indexing configuration name. * @throws \RuntimeException * @return Indexer An instance of ApacheSolrForTypo3\Solr\IndexQueue\Indexer or a sub class of it. */ protected function getIndexerByItem($indexingConfigurationName) { $indexerClass = $this->configuration->getIndexQueueIndexerByConfigurationName($indexingConfigurationName); $indexerConfiguration = $this->configuration->getIndexQueueIndexerConfigurationByConfigurationName($indexingConfigurationName); $indexer = GeneralUtility::makeInstance($indexerClass, $indexerConfiguration); if (!$indexer instanceof Indexer) { throw new \RuntimeException('The indexer class "' . $indexerClass . '" for indexing configuration "' . $indexingConfigurationName . '" is not a valid indexer. Must be a subclass of ApacheSolrForTypo3\\Solr\\IndexQueue\\Indexer.', 1260463206); } return $indexer; }
/** * Activates the collapsing on the configured field, if collapsing was enabled. * * @return bool */ protected function initializeCollapsingFromConfiguration() { // check collapsing if ($this->solrConfiguration->getSearchVariants()) { $collapseField = $this->solrConfiguration->getSearchVariantsField(); $this->setVariantField($collapseField); $this->setCollapsing(true); return true; } return false; }
/** * Enables or disables spellchecking for the query. * * @param boolean $spellchecking Enables spellchecking when set to TRUE, deactivates spellchecking when set to FALSE, defaults to TRUE. */ public function setSpellchecking($spellchecking = true) { if ($spellchecking) { $this->queryParameters['spellcheck'] = 'true'; $this->queryParameters['spellcheck.collate'] = 'true'; $maxCollationTries = $this->solrConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(); $this->addQueryParameter('spellcheck.maxCollationTries', $maxCollationTries); } else { unset($this->queryParameters['spellcheck']); unset($this->queryParameters['spellcheck.collate']); unset($this->queryParameters['spellcheck.maxCollationTries']); } }
/** * Generates an array with terms and hits * * @return array Tags as array with terms and hits */ public function getFrequentSearchTerms() { $frequentSearchConfiguration = $this->configuration->getSearchFrequentSearchesConfiguration(); $terms = array(); $identifier = $this->getCacheIdentifier($frequentSearchConfiguration); if ($this->cache->has($identifier)) { $terms = $this->cache->get($identifier); } else { $terms = $this->getFrequentSearchTermsFromStatistics($frequentSearchConfiguration); if ($frequentSearchConfiguration['sortBy'] == 'hits') { arsort($terms); } else { ksort($terms); } $lifetime = null; if (isset($frequentSearchConfiguration['cacheLifetime'])) { $lifetime = intval($frequentSearchConfiguration['cacheLifetime']); } $this->cache->set($identifier, $terms, array(), $lifetime); } return $terms; }
/** * @test */ public function testAdditionalFiltersGetPassedToTheQuery() { $this->fakeRegisteredSearchComponents(array()); $fakeResponse = $this->getDumbMock('\\Apache_Solr_Response'); $this->assertOneSearchWillBeTriggeredWithQueryAndShouldReturnFakeResponse('test', 0, $fakeResponse); $this->configurationMock->expects($this->any())->method('getSearchQueryFilterConfiguration')->will($this->returnValue(array('type:pages'))); $fakeRequest = new SearchRequest(array('q' => 'test')); $this->assertOneSearchWillBeTriggeredWithQueryAndShouldReturnFakeResponse('test', 0, $fakeResponse); $this->assertPerPageInSessionWillNotBeChanged(); $resultSet = $this->searchResultSetService->search($fakeRequest); $this->assertSame($resultSet->getResponse(), $fakeResponse, 'Did not get the expected fakeResponse'); $this->assertSame(count($resultSet->getUsedQuery()->getFilters()), 1, 'There should be one registered filter in the query'); }
/** * This method is used to apply htmlspecialchars on all document fields that * are not configured to be secure. Secure mean that we know where the content is comming from. * * @param array $documents * @return \Apache_Solr_Document[] */ protected function applyHtmlSpecialCharsOnAllFields(array $documents) { $trustedSolrFields = $this->configuration->getSearchTrustedFieldsArray(); foreach ($documents as $key => $document) { $fieldNames = $document->getFieldNames(); foreach ($fieldNames as $fieldName) { if (in_array($fieldName, $trustedSolrFields)) { // we skip this field, since it was marked as secure continue; } $document->{$fieldName} = $this->applyHtmlSpecialCharsOnSingleFieldValue($document->{$fieldName}); } $documents[$key] = $document; } return $documents; }
/** * Adds filters specified through HTTP GET as filter query parameters to * the Solr query. * */ protected function addFacetQueryFilters() { // todo refactor to use a request object $resultParameters = GeneralUtility::_GET('tx_solr'); // format for filter URL parameter: // tx_solr[filter]=$facetName0:$facetValue0,$facetName1:$facetValue1,$facetName2:$facetValue2 if (is_array($resultParameters['filter'])) { $filters = array_map('urldecode', $resultParameters['filter']); // $filters look like array('name:value1','name:value2','fieldname2:foo') $configuredFacets = $this->getConfiguredFacets(); // first group the filters by facetName - so that we can // decide later whether we need to do AND or OR for multiple // filters for a certain facet/field // $filtersByFacetName look like array('name' => array ('value1', 'value2'), 'fieldname2' => array('foo')) $filtersByFacetName = array(); foreach ($filters as $filter) { // only split by the first colon to allow using colons in the filter value itself list($filterFacetName, $filterValue) = explode(':', $filter, 2); if (in_array($filterFacetName, $configuredFacets)) { $filtersByFacetName[$filterFacetName][] = $filterValue; } } foreach ($filtersByFacetName as $facetName => $filterValues) { $facetConfiguration = $this->allConfiguredFacets[$facetName . '.']; $filterEncoder = $this->facetRendererFactory->getFacetFilterEncoderByFacetName($facetName); $tag = ''; if ($facetConfiguration['keepAllOptionsOnSelection'] == 1 || $this->configuration->getSearchFacetingKeepAllFacetsOnSelection()) { $tag = '{!tag=' . addslashes($facetConfiguration['field']) . '}'; } $filterParts = array(); foreach ($filterValues as $filterValue) { if (!is_null($filterEncoder)) { $filterOptions = $facetConfiguration[$facetConfiguration['type'] . '.']; if (empty($filterOptions)) { $filterOptions = array(); } $filterValue = $filterEncoder->decodeFilter($filterValue, $filterOptions); $filterParts[] = $facetConfiguration['field'] . ':' . $filterValue; } else { $filterParts[] = $facetConfiguration['field'] . ':"' . addslashes($filterValue) . '"'; } } $operator = $facetConfiguration['operator'] == 'OR' ? ' OR ' : ' AND '; $this->facetFilters[] = $tag . '(' . implode($operator, $filterParts) . ')'; } } }
/** * @test */ public function testExpandedDocumentsGetAddedWhenVariantsAreConfigured() { // we fake that collapsing is enabled $this->configurationMock->expects($this->atLeastOnce())->method('getSearchVariants')->will($this->returnValue(true)); // in this case we collapse on the type field $this->configurationMock->expects($this->atLeastOnce())->method('getSearchVariantsField')->will($this->returnValue('type')); $this->fakeRegisteredSearchComponents(array()); $fakedSolrResponse = $this->getFixtureContent('fakeCollapsedResponse.json'); $fakeHttpResponse = $this->getDumbMock('\\Apache_Solr_HttpTransport_Response'); $fakeHttpResponse->expects($this->once())->method('getBody')->will($this->returnValue($fakedSolrResponse)); $fakeResponse = new \Apache_Solr_Response($fakeHttpResponse); $this->assertOneSearchWillBeTriggeredWithQueryAndShouldReturnFakeResponse('variantsSearch', 0, $fakeResponse); $fakeRequest = new SearchRequest(array('q' => 'variantsSearch')); $resultSet = $this->searchResultSetService->search($fakeRequest); $this->assertSame(1, count($resultSet->getResponse()->response->docs), 'Unexpected amount of document'); /** @var $fistResult SearchResult */ $fistResult = $resultSet->getResponse()->response->docs[0]; $this->assertSame(5, count($fistResult->getVariants()), 'Unexpected amount of expanded result'); }
/** * Retrieves the name of the Indexing Queue Configuration for a record * * @param string $recordTable Table to read from * @param int $recordUid Id of the record * @return string Name of indexing configuration */ protected function getIndexingConfigurationName($recordTable, $recordUid) { $name = $recordTable; $indexingConfigurations = $this->solrConfiguration->getEnabledIndexQueueConfigurationNames(); foreach ($indexingConfigurations as $indexingConfigurationName) { if (!$this->solrConfiguration->getIndexQueueConfigurationIsEnabled($indexingConfigurationName)) { // ignore disabled indexing configurations continue; } $tableToIndex = $this->solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName); if ($tableToIndex === $recordTable) { $recordWhereClause = $this->solrConfiguration->getIndexQueueAdditionalWhereClauseByConfigurationName($indexingConfigurationName); $record = BackendUtility::getRecord($recordTable, $recordUid, '*', $recordWhereClause); if (!empty($record)) { // we found a record which matches the conditions $name = $indexingConfigurationName; // FIXME currently returns after the first configuration match break; } } } return $name; }
/** * Gets the target page Id for links. Might have been set through either * flexform or TypoScript. If none is set, TSFE->id is used. * * @return int The page Id to be used for links */ public function getLinkTargetPageId() { return $this->typoScriptConfiguration->getSearchTargetPage(); }
/** * @test */ public function canGetIndexQueuePagesExcludeContentByClassArray() { $fakeConfigurationArray['plugin.']['tx_solr.'] = array('index.' => array('queue.' => array('pages.' => array('excludeContentByClass' => 'excludeClass')))); $configuration = new TypoScriptConfiguration($fakeConfigurationArray); $excludeClasses = $configuration->getIndexQueuePagesExcludeContentByClassArray(); $this->assertEquals(array('excludeClass'), $excludeClasses, 'Can not get exclude patterns from configuration'); }
/** * @test */ public function removePageSectionFilterIsKeepingOtherFilters() { $fakeConfigurationArray['plugin.']['tx_solr.'] = array('search.' => array('query.' => array('filter.' => array('type:pages', '__pageSections' => '1,2,3')))); $configuration = new TypoScriptConfiguration($fakeConfigurationArray); $this->assertEquals(['type:pages', '__pageSections' => '1,2,3'], $configuration->getSearchQueryFilterConfiguration()); $configuration->removeSearchQueryFilterForPageSections(); $this->assertEquals(['type:pages'], $configuration->getSearchQueryFilterConfiguration()); }
/** * */ public function __construct() { $this->configuration = Util::getSolrConfiguration(); $this->highlightFields = $this->configuration->getSearchResultsHighlightingFieldsAsArray(); $this->fragmentSeparator = $this->configuration->getSearchResultsHighlightingFragmentSeparator(); }
/** * @param TypoScriptConfiguration $configuration */ public function __construct(TypoScriptConfiguration $configuration = null) { $this->configuration = $configuration == null ? Util::getSolrConfiguration() : $configuration; $this->pageIndexingConfiguration = $this->configuration->getIndexQueueFieldsConfigurationByConfigurationName('pages'); $this->mappedFieldNames = $this->configuration->getIndexQueueMappedFieldsByConfigurationName('pages'); }
public function setUp() { $this->typoScripConfigurationMock = $this->getDumbMock('ApacheSolrForTypo3\\Solr\\System\\Configuration\\TypoScriptConfiguration'); $this->typoScripConfigurationMock->expects($this->once())->method('getIndexQueuePagesExcludeContentByClassArray')->will($this->returnValue(array('typo3-search-exclude'))); }
/** * @param int $limit */ protected function fakeLastSearchLimit($limit) { $this->configurationMock->expects($this->once())->method('getSearchLastSearchesLimit')->will($this->returnValue($limit)); }