Passed
Push — sheepy/introspection ( 69e16c...c6c7ca )
by Marco
05:28
created

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