Passed
Push — sheepy/introspection ( 000d40...b6b869 )
by Marco
02:43
created

SearchResult::setCustomisedMatches()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
4
namespace Firesphere\SolrSearch\Results;
5
6
use Firesphere\SolrSearch\Indexes\BaseIndex;
7
use Firesphere\SolrSearch\Queries\BaseQuery;
8
use Firesphere\SolrSearch\Services\SolrCoreService;
9
use SilverStripe\Control\HTTPRequest;
10
use SilverStripe\ORM\ArrayList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\FieldType\DBField;
13
use SilverStripe\ORM\PaginatedList;
14
use SilverStripe\View\ArrayData;
15
use Solarium\Component\Result\Facet\Field;
16
use Solarium\Component\Result\FacetSet;
17
use Solarium\Component\Result\Highlighting\Highlighting;
18
use Solarium\Component\Result\Spellcheck\Collation;
19
use Solarium\Component\Result\Spellcheck\Result as SpellcheckResult;
20
use Solarium\QueryType\Select\Result\Document;
21
use Solarium\QueryType\Select\Result\Result;
22
23
class SearchResult
24
{
25
    /**
26
     * @var BaseQuery
27
     */
28
    protected $query;
29
30
    /**
31
     * @var BaseIndex
32
     */
33
    protected $index;
34
35
    /**
36
     * @var ArrayList
37
     */
38
    protected $matches;
39
40
    /**
41
     * @var int
42
     */
43
    protected $totalItems;
44
45
    /**
46
     * @var ArrayData
47
     */
48
    protected $facets;
49
50
    /**
51
     * @var Highlighting
52
     */
53
    protected $highlight;
54
55
    /**
56
     * @var ArrayList
57
     */
58
    protected $spellcheck;
59
60
    /**
61
     * @var string
62
     */
63
    protected $collatedSpellcheck;
64
65
    /**
66
     * SearchResult constructor.
67
     * Funnily enough, the $result contains the actual results, and has methods for the other things.
68
     * See Solarium docs for this.
69
     *
70
     * @param Result $result
71
     * @param BaseQuery $query
72
     * @param BaseIndex $index
73
     */
74 3
    public function __construct(Result $result, BaseQuery $query, BaseIndex $index)
75
    {
76 3
        $this->index = $index;
77 3
        $this->query = $query;
78 3
        $this->setMatches($result->getDocuments());
79 3
        $this->setFacets($result->getFacetSet());
80 3
        $this->setHighlight($result->getHighlighting());
81 3
        $this->setTotalItems($result->getNumFound());
82 3
        if ($query->hasSpellcheck()) {
83 3
            $this->setSpellcheck($result->getSpellcheck());
84 3
            $this->setCollatedSpellcheck($result->getSpellcheck());
85
        }
86 3
    }
87
88
    /**
89
     * @param HTTPRequest $request
90
     * @return PaginatedList
91
     */
92 1
    public function getPaginatedMatches(HTTPRequest $request): PaginatedList
93
    {
94
        // Get all the items in the set and push them in to the list
95 1
        $items = $this->getMatches();
96
        /** @var PaginatedList $paginated */
97 1
        $paginated = PaginatedList::create($items, $request);
98
        // Do not limit the pagination, it's done at Solr level
99 1
        $paginated->setLimitItems(false);
100
        // Override the count that's set from the item count
101 1
        $paginated->setTotalItems($this->getTotalItems());
102
        // Set the start to the current page from start.
103 1
        $paginated->setPageStart($this->query->getStart());
104
        // The amount of items per page to display
105 1
        $paginated->setPageLength($this->query->getRows());
106
107 1
        return $paginated;
108
    }
109
110
    /**
111
     * @return ArrayList
112
     */
113 1
    public function getMatches(): ArrayList
114
    {
115 1
        $matches = $this->matches;
116 1
        $items = [];
117 1
        $idField = SolrCoreService::ID_FIELD;
118 1
        $classIDField = SolrCoreService::CLASS_ID_FIELD;
119 1
        foreach ($matches as $match) {
120 1
            $item = $match;
121 1
            if (!$match instanceof DataObject) {
122 1
                $class = $match->ClassName;
123
                /** @var DataObject $item */
124 1
                $item = $class::get()->byID($match->{$classIDField});
125
            }
126 1
            if ($item && $item->exists()) {
127 1
                $this->createExcerpt($idField, $match, $item);
128 1
                $items[] = $item;
129
            }
130 1
            unset($match);
131 1
            $item->destroy();
132
        }
133
134 1
        return ArrayList::create($items);
135
    }
136
137
    /**
138
     * @param array $result
139
     * @return $this
140
     * @todo support multiple classes
141
     */
142 3
    protected function setMatches($result): self
143
    {
144 3
        $data = [];
145
        /** @var Document $item */
146 3
        foreach ($result as $item) {
147 1
            $data[] = ArrayData::create($item->getFields());
148
        }
149
150 3
        $docs = ArrayList::create($data);
151 3
        $this->matches = $docs;
152
153 3
        return $this;
154
    }
155
156
    /**
157
     * @param string $idField
158
     * @param $match
159
     * @param DataObject $item
160
     */
161 1
    protected function createExcerpt(string $idField, $match, DataObject $item): void
162
    {
163 1
        $item->Excerpt = DBField::create_field(
164 1
            'HTMLText',
165 1
            str_replace(
166 1
                '&#65533;',
167 1
                '',
168 1
                $this->getHighlightByID($match->{$idField})
169
            )
170
        );
171 1
    }
172
173
    /**
174
     * @param $docID
175
     * @return string|null
176
     */
177 1
    public function getHighlightByID($docID): ?string
178
    {
179 1
        $highlights = [];
180 1
        if ($this->highlight && $docID) {
181 1
            $highlights = [];
182 1
            foreach ($this->highlight->getResult($docID) as $field => $highlight) {
183 1
                $highlights[] = implode(' (...) ', $highlight);
184
            }
185
        }
186
187 1
        return implode(' (...) ', $highlights);
188
    }
189
190
    /**
191
     * @return int
192
     */
193 3
    public function getTotalItems(): int
194
    {
195 3
        return $this->totalItems;
196
    }
197
198
    /**
199
     * @param int $totalItems
200
     * @return $this
201
     */
202 3
    public function setTotalItems($totalItems): self
203
    {
204 3
        $this->totalItems = $totalItems;
205
206 3
        return $this;
207
    }
208
209
    /**
210
     * Allow overriding of matches with a custom result
211
     *
212
     * @param $matches
213
     * @return mixed
214
     */
215 1
    public function setCustomisedMatches($matches)
216
    {
217 1
        $this->matches = $matches;
218
219 1
        return $matches;
220
    }
221
222
    /**
223
     * @return Highlighting|null
224
     */
225 1
    public function getHighlight(): ?Highlighting
226
    {
227 1
        return $this->highlight;
228
    }
229
230
    /**
231
     * @param Highlighting|null $result
232
     * @return $this
233
     */
234 3
    protected function setHighlight($result): self
235
    {
236 3
        $this->highlight = $result;
237
238 3
        return $this;
239
    }
240
241
    /**
242
     * @return ArrayData
243
     */
244 1
    public function getFacets(): ArrayData
245
    {
246 1
        return $this->facets;
247
    }
248
249
    /**
250
     * @param FacetSet|null $facets
251
     * @return $this
252
     */
253 3
    protected function setFacets($facets): self
254
    {
255 3
        $this->facets = $this->buildFacets($facets);
256
257 3
        return $this;
258
    }
259
260
    /**
261
     * @return ArrayList
262
     */
263 1
    public function getSpellcheck(): ArrayList
264
    {
265 1
        return $this->spellcheck;
266
    }
267
268
    /**
269
     * @param SpellcheckResult|null $spellcheck
270
     * @return SearchResult
271
     */
272 3
    public function setSpellcheck($spellcheck): self
273
    {
274 3
        $spellcheckList = [];
275
276 3
        if ($spellcheck && ($suggestions = $spellcheck->getSuggestion(0))) {
277
            foreach ($suggestions->getWords() as $suggestion) {
278
                $spellcheckList[] = ArrayData::create($suggestion);
279
            }
280
        }
281
282 3
        $this->spellcheck = ArrayList::create($spellcheckList);
283
284 3
        return $this;
285
    }
286
287
    /**
288
     * @return mixed
289
     */
290
    public function getCollatedSpellcheck()
291
    {
292
        return $this->collatedSpellcheck;
293
    }
294
295
    /**
296
     * @param mixed $collatedSpellcheck
297
     * @return $this
298
     */
299 3
    public function setCollatedSpellcheck($collatedSpellcheck): self
300
    {
301
        /** @var array|Collation[] $collated */
302 3
        if ($collatedSpellcheck && ($collated = $collatedSpellcheck->getCollations())) {
303
            $key = array_keys($collated);
304
            $this->collatedSpellcheck = $collated[$key[0]];
305
        }
306
307 3
        return $this;
308
    }
309
310
    /**
311
     * Build the given list of key-value pairs in to a SilverStripe useable array
312
     * @param FacetSet|null $facets
313
     * @return ArrayData
314
     */
315 3
    protected function buildFacets($facets): ArrayData
316
    {
317 3
        $facetArray = [];
318 3
        if ($facets) {
319 3
            $facetTypes = $this->index->getFacetFields();
320
            // Loop all available facet fields by type
321 3
            foreach ($facetTypes as $class => $options) {
322
                // Get the facets by its title
323
                /** @var Field $typeFacets */
324
                $typeFacets = $facets->getFacet('facet-' . $options['Title']);
325
                $values = $typeFacets->getValues();
326
                $results = ArrayList::create();
327
                // If there are values, get the items one by one and push them in to the list
328
                if (count($values)) {
329
                    $this->getClassFacets($class, $values, $results);
330
                }
331
                // Put the results in to the array
332
                $facetArray[$options['Title']] = $results;
333
            }
334
        }
335
336
        // Return an ArrayList of the results
337 3
        return ArrayData::create($facetArray);
338
    }
339
340
    /**
341
     * @param $class
342
     * @param array $values
343
     * @param ArrayList $results
344
     */
345
    protected function getClassFacets($class, array $values, &$results): void
346
    {
347
        $items = $class::get()->byIds(array_keys($values));
348
        foreach ($items as $item) {
349
            // Set the FacetCount value to be sorted on later
350
            $item->FacetCount = $values[$item->ID];
351
            $results->push($item);
352
        }
353
        // Sort the results by FacetCount
354
        $results = $results->sort(['FacetCount' => 'DESC', 'Title' => 'ASC',]);
355
    }
356
}
357