Completed
Push — master ( 07d864...5919d7 )
by Rafael
04:56
created

QueryBuilder   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 7
dl 0
loc 200
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
B buildSearchQuery() 0 29 6
B buildSuggestQuery() 0 26 5
A buildPageQuery() 0 17 1
A applyPageSectionsRootLineFilter() 0 13 3
C getAdditionalFilters() 0 36 7
A getQueryInstance() 0 5 1
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Domain\Search\Query;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2017 <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Domain\Site\SiteHashService;
28
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
29
use ApacheSolrForTypo3\Solr\Query;
30
use ApacheSolrForTypo3\Solr\SuggestQuery;
31
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
32
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
35
36
/**
37
 * The QueryBuilder is responsible to build solr queries, that are used in the extension to query the solr server.
38
 *
39
 * @package ApacheSolrForTypo3\Solr\Domain\Search\Query
40
 */
41
class QueryBuilder {
42
43
    /**
44
     * Additional filters, which will be added to the query, as well as to
45
     * suggest queries.
46
     *
47
     * @var array
48
     */
49
    protected $additionalFilters = [];
50
51
    /**
52
     * @var TypoScriptConfiguration
53
     */
54
    protected $typoScriptConfiguration;
55
56
    /**
57
     * @var SolrLogManager;
58
     */
59
    protected $logger = null;
60
61
    /**
62
     * QueryBuilder constructor.
63
     */
64
    public function __construct(TypoScriptConfiguration $configuration, SolrLogManager $solrLogManager = null)
65
    {
66
        $this->typoScriptConfiguration = $configuration;
67
        $this->logger = is_null($solrLogManager) ? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__) : $solrLogManager;
68
    }
69
70
    /**
71
     * Initializes the Query object and SearchComponents and returns
72
     * the initialized query object, when a search should be executed.
73
     *
74
     * @param string|null $rawQuery
75
     * @param int $resultsPerPage
76
     * @return Query
77
     */
78
    public function buildSearchQuery($rawQuery, $resultsPerPage) : Query
79
    {
80
        /* @var $query Query */
81
        $query = $this->getQueryInstance($rawQuery);
82
83
        $this->applyPageSectionsRootLineFilter($query);
84
85
        if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
86
            $this->logger->log(SolrLogManager::INFO, 'Received search query', [$rawQuery]);
87
        }
88
89
        $query->setResultsPerPage($resultsPerPage);
90
91
        if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
92
            // empty main query, but using a "return everything"
93
            // alternative query in q.alt
94
            $query->setAlternativeQuery('*:*');
95
        }
96
97
        if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
98
            $query->setAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
99
        }
100
101
        foreach ($this->getAdditionalFilters() as $additionalFilter) {
102
            $query->getFilters()->add($additionalFilter);
103
        }
104
105
        return $query;
106
    }
107
108
    /**
109
     * Builds a SuggestQuery with all applied filters.
110
     *
111
     * @param string $queryString
112
     * @param string $additionalFilters
113
     * @param integer $requestId
114
     * @param string $groupList
115
     * @return SuggestQuery
116
     */
117
    public function buildSuggestQuery(string $queryString, string $additionalFilters, int $requestId, string $groupList) : SuggestQuery
118
    {
119
        $suggestQuery = GeneralUtility::makeInstance(SuggestQuery::class, $queryString);
120
121
        $allowedSitesConfig = $this->typoScriptConfiguration->getObjectByPathOrDefault('plugin.tx_solr.search.query.', []);
122
        $siteService = GeneralUtility::makeInstance(SiteHashService::class);
123
        $allowedSites = $siteService->getAllowedSitesForPageIdAndAllowedSitesConfiguration($requestId, $allowedSitesConfig['allowedSites']);
124
        $suggestQuery->setUserAccessGroups(explode(',', $groupList));
125
        $suggestQuery->setSiteHashFilter($allowedSites);
126
        $suggestQuery->setOmitHeader();
127
128
        if (!empty($allowedSitesConfig['filter.'])) {
129
            foreach ($allowedSitesConfig['filter.'] as $additionalFilter) {
130
                $suggestQuery->addFilter($additionalFilter);
131
            }
132
        }
133
134
        if (!empty($additionalFilters)) {
135
            $additionalFilters = json_decode($additionalFilters);
136
            foreach ($additionalFilters as $additionalFilter) {
137
                $suggestQuery->addFilter($additionalFilter);
138
            }
139
        }
140
141
        return $suggestQuery;
142
    }
143
144
    /**
145
     * Returns Query for Search which finds document for given page.
146
     * Note: The Connection is per language as recommended in ext-solr docs.
147
     *
148
     * @return Query
149
     */
150
    public function buildPageQuery($pageId)
151
    {
152
        /** @var $siteRepository SiteRepository */
153
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
154
        $site = $siteRepository->getSiteByPageId($pageId);
155
        /* @var Query $query */
156
        $query = GeneralUtility::makeInstance(Query::class, '');
157
        $query->setQueryType('standard');
158
        $query->useRawQueryString(true);
159
        $query->setQueryString('*:*');
160
        $query->getFilters()->add('(type:pages AND uid:' . $pageId . ') OR (*:* AND pid:' . $pageId . ' NOT type:pages)');
161
        $query->getFilters()->add('siteHash:' . $site->getSiteHash());
162
        $query->getReturnFields()->add('*');
163
        $query->setSorting('type asc, title asc');
164
165
        return $query;
166
    }
167
168
    /**
169
     * Initializes additional filters configured through TypoScript and
170
     * Flexforms for use in regular queries and suggest queries.
171
     *
172
     * @param Query $query
173
     * @return void
174
     */
175
    protected function applyPageSectionsRootLineFilter(Query $query)
176
    {
177
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
178
        if (count($searchQueryFilters) <= 0) {
179
            return;
180
        }
181
182
        // special filter to limit search to specific page tree branches
183
        if (array_key_exists('__pageSections', $searchQueryFilters)) {
184
            $query->setRootlineFilter($searchQueryFilters['__pageSections']);
185
            $this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
186
        }
187
    }
188
189
    /**
190
     * Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
191
     *
192
     * @return array
193
     */
194
    public function getAdditionalFilters() : array
195
    {
196
        // when we've build the additionalFilter once, we could return them
197
        if (count($this->additionalFilters) > 0) {
198
            return $this->additionalFilters;
199
        }
200
201
        $searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
202
        if (count($searchQueryFilters) <= 0) {
203
            return [];
204
        }
205
206
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
207
208
        // all other regular filters
209
        foreach ($searchQueryFilters as $filterKey => $filter) {
210
            // the __pageSections filter should not be handled as additional filter
211
            if ($filterKey === '__pageSections') {
212
                continue;
213
            }
214
215
            $filterIsArray = is_array($searchQueryFilters[$filterKey]);
216
            if ($filterIsArray) {
217
                continue;
218
            }
219
220
            $hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
221
            if ($hasSubConfiguration) {
222
                $filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
223
            }
224
225
            $this->additionalFilters[$filterKey] = $filter;
226
        }
227
228
        return $this->additionalFilters;
229
    }
230
231
    /**
232
     * @param string $rawQuery
233
     * @return Query|object
234
     */
235
    protected function getQueryInstance($rawQuery)
236
    {
237
        $query = GeneralUtility::makeInstance(Query::class, $rawQuery, $this->typoScriptConfiguration);
238
        return $query;
239
    }
240
}