Passed
Push — Cleanup-attempt ( 703893...15009b )
by Simon
05:33
created

BaseIndex::getQueryFactory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
4
namespace Firesphere\SolrSearch\Indexes;
5
6
use Firesphere\SolrSearch\Factories\QueryComponentFactory;
7
use Firesphere\SolrSearch\Helpers\Synonyms;
8
use Firesphere\SolrSearch\Interfaces\ConfigStore;
9
use Firesphere\SolrSearch\Queries\BaseQuery;
10
use Firesphere\SolrSearch\Results\SearchResult;
11
use Firesphere\SolrSearch\Services\SchemaService;
12
use Firesphere\SolrSearch\Services\SolrCoreService;
13
use Firesphere\SolrSearch\Traits\BaseIndexTrait;
14
use Firesphere\SolrSearch\Traits\GetterSetterTrait;
15
use LogicException;
16
use Minimalcode\Search\Criteria;
17
use SilverStripe\Control\Director;
18
use SilverStripe\Core\Config\Config;
19
use SilverStripe\Core\Config\Configurable;
20
use SilverStripe\Core\Extensible;
21
use SilverStripe\Core\Injector\Injector;
22
use SilverStripe\Dev\Deprecation;
23
use SilverStripe\SiteConfig\SiteConfig;
24
use SilverStripe\View\ArrayData;
25
use Solarium\Core\Client\Adapter\Guzzle;
26
use Solarium\Core\Client\Client;
27
use Solarium\Core\Query\Helper;
28
use Solarium\QueryType\Select\Query\Query;
29
30
/**
31
 * Class BaseIndex
32
 * @package Firesphere\SolrSearch\Indexes
33
 */
34
abstract class BaseIndex
35
{
36
    use Extensible;
37
    use Configurable;
38
    use GetterSetterTrait;
39
    use BaseIndexTrait;
40
41
    private static $fieldTypes = [
42
        'FulltextFields',
43
        'SortFields',
44
        'FilterFields',
45
        'BoostedFields',
46
        'CopyFields',
47
        'DefaultField',
48
        'FacetFields',
49
    ];
50
    /**
51
     * @var SchemaService
52
     */
53
    protected $schemaService;
54
55
    /**
56
     * @var QueryComponentFactory
57
     */
58
    protected $queryFactory;
59
60
    /**
61
     * The query terms as an array
62
     * @var array
63
     */
64
    protected $queryTerms = [];
65
66
    /**
67
     * BaseIndex constructor.
68
     */
69
    public function __construct()
70
    {
71
        // Set up the client
72
        $config = Config::inst()->get(SolrCoreService::class, 'config');
73
        $config['endpoint'] = $this->getConfig($config['endpoint']);
74
        $this->client = new Client($config);
75
        $this->client->setAdapter(new Guzzle());
76
77
        // Set up the schema service, only used in the generation of the schema
78
        $schemaService = Injector::inst()->get(SchemaService::class, false);
79
        $schemaService->setIndex($this);
80
        $schemaService->setStore(Director::isDev());
81
        $this->schemaService = $schemaService;
82
        $this->queryFactory = Injector::inst()->get(QueryComponentFactory::class);
83
84
        $this->extend('onBeforeInit');
85
        $this->init();
86
        $this->extend('onAfterInit');
87
    }
88
89
    /**
90
     * Build a full config for all given endpoints
91
     * This is to add the current index to e.g. an index or select
92
     * @param array $endpoints
93
     * @return array
94
     */
95
    public function getConfig($endpoints): array
96
    {
97
        foreach ($endpoints as $host => $endpoint) {
98
            $endpoints[$host]['core'] = $this->getIndexName();
99
        }
100
101
        return $endpoints;
102
    }
103
104
    /**
105
     * @return string
106
     */
107
    abstract public function getIndexName();
108
109
    /**
110
     * Required to initialise the fields.
111
     * It's loaded in to the non-static properties for backward compatibility with FTS
112
     * Also, it's a tad easier to use this way, loading the other way around would be very
113
     * memory intensive, as updating the config for each item is not efficient
114
     */
115
    public function init()
116
    {
117
        if (!self::config()->get($this->getIndexName())) {
118
            Deprecation::notice('5', 'Please set an index name');
119
120
            // If the old init method is found, skip the config based init
121
            if (count($this->getClasses())) {
122
                Deprecation::notice(
123
                    '5',
124
                    'You are running init at the top of your method. The new API requires it to be at the bottom'
125
                );
126
            }
127
128
            return;
129
        }
130
131
132
        $config = self::config()->get($this->getIndexName());
133
134
        if (!array_key_exists('Classes', $config)) {
135
            throw new LogicException('No classes to index found!');
136
        }
137
138
        $this->setClasses($config['Classes']);
139
140
        // For backward compatibility, copy the config to the protected values
141
        // Saves doubling up further down the line
142
        foreach (self::$fieldTypes as $type) {
143
            if (array_key_exists($type, $config)) {
144
                $method = 'set' . $type;
145
                $this->$method($config[$type]);
146
            }
147
        }
148
    }
149
150
    /**
151
     * Default returns a SearchResult. It can return an ArrayData if FTS Compat is enabled
152
     *
153
     * @param BaseQuery $query
154
     * @return SearchResult|ArrayData|mixed
155
     */
156
    public function doSearch(BaseQuery $query)
157
    {
158
        $this->extend('onBeforeSearch', $query);
159
        // Build the actual query parameters
160
        $clientQuery = $this->buildSolrQuery($query);
161
162
        $result = $this->client->select($clientQuery);
163
164
        // Handle the after search first. This gets a raw search result
165
        $this->extend('onAfterSearch', $result);
166
        $searchResult = new SearchResult($result, $query, $this);
167
168
        // And then handle the search results, which is a useable object for SilverStripe
169
        $this->extend('updateSearchResults', $searchResult);
170
171
        return $searchResult;
172
    }
173
174
    /**
175
     * @param BaseQuery $query
176
     * @return Query
177
     */
178
    protected function buildSolrQuery(BaseQuery $query): Query
179
    {
180
        $clientQuery = $this->client->createSelect();
181
182
        $helper = $clientQuery->getHelper();
183
184
        $this->queryFactory->setQuery($query);
185
        $this->queryFactory->setClientQuery($clientQuery);
186
        $this->queryFactory->setHelper($helper);
187
        $this->queryFactory->setIndex($this);
188
189
        $clientQuery = $this->queryFactory->buildQuery();
190
        $this->queryTerms = $this->queryFactory->getQueryArray();
191
192
        $queryData = implode(' ', $this->queryTerms);
193
        $clientQuery->setQuery($queryData);
194
195
        return $clientQuery;
196
    }
197
198
    /**
199
     * @return array
200
     */
201
    public function getFieldsForIndexing(): array
202
    {
203
        // Return values to make the key reset
204
        // Only return unique values
205
        // And make it all a single array
206
        return array_values(
207
            array_unique(
208
                array_merge(
209
                    $this->getFulltextFields(),
210
                    $this->getSortFields(),
211
                    $this->getFilterFields()
212
                )
213
            )
214
        );
215
    }
216
217
    /**
218
     * Upload config for this index to the given store
219
     *
220
     * @param ConfigStore $store
221
     */
222
    public function uploadConfig(ConfigStore $store): void
223
    {
224
        // @todo use types/schema/elevate rendering
225
        // Upload the config files for this index
226
        // Create a default schema which we can manage later
227
        $schema = (string)$this->schemaService->generateSchema();
228
        $store->uploadString(
229
            $this->getIndexName(),
230
            'schema.xml',
231
            $schema
232
        );
233
234
235
        $synonyms = $this->getSynonyms();
236
237
        // Upload synonyms
238
        $store->uploadString(
239
            $this->getIndexName(),
240
            'synonyms.txt',
241
            $synonyms
242
        );
243
244
        // Upload additional files
245
        foreach (glob($this->schemaService->getExtrasPath() . '/*') as $file) {
246
            if (is_file($file)) {
247
                $store->uploadFile($this->getIndexName(), $file);
248
            }
249
        }
250
    }
251
252
    /**
253
     * Add synonyms. Public to be extendable
254
     * @param bool $eng Include UK to US synonyms
255
     * @return string
256
     */
257
    public function getSynonyms($eng = true): string
258
    {
259
        $engSynonyms = '';
260
        if ($eng) {
261
            $engSynonyms = Synonyms::getSynonymsAsString();
262
        }
263
264
        return $engSynonyms . SiteConfig::current_site_config()->getField('SearchSynonyms');
265
    }
266
267
    /**
268
     * @return array
269
     */
270
    public function getQueryTerms(): array
271
    {
272
        return $this->queryTerms;
273
    }
274
275
    /**
276
     * @return QueryComponentFactory
277
     */
278
    public function getQueryFactory(): QueryComponentFactory
279
    {
280
        return $this->queryFactory;
281
    }
282
}
283