Passed
Push — master ( 76d4cd...f9d9de )
by Gordon
03:35
created

SearchPageController::renderSearchResults()   F

Complexity

Conditions 16
Paths 350

Size

Total Lines 145
Code Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 85
c 0
b 0
f 0
dl 0
loc 145
rs 2.6139
cc 16
nc 350
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types = 1);
2
3
namespace Suilven\FreeTextSearch\Page;
4
5
use SilverStripe\ORM\ArrayList;
6
use SilverStripe\ORM\DataObject;
7
use SilverStripe\ORM\PaginatedList;
8
use SilverStripe\View\ArrayData;
9
use Suilven\FreeTextSearch\Container\SearchResults;
10
use Suilven\FreeTextSearch\Factory\SearcherFactory;
11
use Suilven\FreeTextSearch\Factory\SuggesterFactory;
12
use Suilven\FreeTextSearch\Helper\FacetLinkHelper;
13
use Suilven\FreeTextSearch\Indexes;
14
15
// @phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
16
17
/**
18
 * Class SearchPageController
19
 *
20
 * @package Suilven\FreeTextSearch\Page
21
 * @property int $ID Page ID
22
 * @property int $PageSize the number of results to show on each page
23
 */
24
25
class SearchPageController extends \PageController
26
{
27
    /** @var array<string,string|float|int|bool> */
28
    protected static $selected = [];
29
30
    /** @var array<string> */
31
    private static $allowed_actions = ['index', 'similar'];
32
33
34
    public function similar(): \SilverStripe\View\ViewableData_Customised
35
    {
36
         /** @var \Suilven\FreeTextSearch\Page\SearchPage $model */
37
        $model = SearchPage::get_by_id(SearchPage::class, $this->ID);
38
39
        $indexes = new Indexes();
40
        $index = $indexes->getIndex($model->IndexToSearch);
41
42
        /** @var string $clazz */
43
        $clazz = $index->getClass();
44
45
        $dataObjectID = $this->getRequest()->param('ID');
46
        $dataObjectID = \intval($dataObjectID);
47
48
49
        $objectInContext = DataObject::get_by_id($clazz, $dataObjectID);
50
51
        $factory = new SearcherFactory();
52
        $searcher = $factory->getSearcher();
53
        $searcher->setIndexName($index->getName());
54
        $this->paginateSearcher($searcher);
55
56
        $results = $searcher->searchForSimilar($objectInContext);
57
58
        // tweak results set for rendering purposes, we do not want all the OR constructs
59
        $results->setQuery('');
60
        $results->setSimilarTo($objectInContext);
61
62
        return $this->renderSearchResults($model, $results);
63
    }
64
65
66
    public function index(): \SilverStripe\View\ViewableData_Customised
67
    {
68
        // @todo search indexes addition
69
        $q = $this->getRequest()->getVar('q');
70
71
        $this->selected = $this->getRequest()->getVars();
72
73
        /** @var \Suilven\FreeTextSearch\Page\SearchPage $model */
74
        $model = SearchPage::get_by_id(SearchPage::class, $this->ID);
75
76
77
        $results = new SearchResults();
78
79
80
        //unset($selected['q']);
81
82
        if (isset($q) || $model->ShowAllIfEmptyQuery || isset($this->selected['q'])) {
83
            $results = $this->performSearchIncludingFacets($model, $q);
84
        }
85
86
        unset($this->selected['q']);
87
        unset($this->selected['start']);
88
89
        echo 'SELECTED:';
90
        \print_r($this->selected);
91
92
        $factory = new SuggesterFactory();
93
94
        $suggester = $factory->getSuggester();
95
96
        // @todo this is returning blank
97
        $suggester->setIndex($model->IndexToSearch);
98
        $suggestions = \is_null($q)
99
            ? []
100
            : $suggester->suggest($q);
101
102
        \print_r($suggestions);
103
104
/*
105
106
        $facetted = isset($results['AllFacets']);
107
108
109
110
        $targetFacet = new ArrayList();
111
        if (isset($model->ShowTagCloudFor)) {
112
            // get the tag cloud from calculated facets, but if not calculated, ie the arrive on the page case,
113
            // calculate them
114
            if ($facetted) {
115
                $facets = $results['AllFacets'];
116
            } else {
117
                $proxyResults = $this->performSearchIncludingFacets($selected, $model, $q);
118
                $facets = $proxyResults['AllFacets'];
119
            }
120
121
            foreach ($facets as $facet) {
122
                $name = $facet->getField('Name');
123
                if ($name === $model->ShowTagCloudFor) {
124
                    $targetFacet = $facet->getField('Facets');
125
126
                    break;
127
                }
128
            }
129
130
            $facetArray = $targetFacet->toArray();
131
            $minSize = 10;
132
            $maxSize = 40;
133
            $maxCount = 0;
134
            foreach ($facetArray as $tag) {
135
                $count = $tag['Count'];
136
                $maxCount = $count > $maxCount
137
                    ? $count
138
                    : $maxCount;
139
            }
140
141
            $tagCloud = new ArrayList();
142
            foreach ($facetArray as $tag) {
143
                $size = $minSize + ($maxSize - $minSize) * $tag['Count'] / $maxCount;
144
                $size = \round($size);
145
                $row = new ArrayData([
146
                    'Name' => $tag['Value'],
147
                    'Size' => $size,
148
                    'Params' => $tag['Params'],
149
                ]);
150
                $tagCloud->push($row);
151
            }
152
153
            $results['TagCloud'] = $tagCloud;
154
        }
155
156
157
        //for($i = 3; $i < 40; $i++) {
158
           // echo "li.tag{$i} { font-size: {$i}px;};\n";
159
        //}
160
161
        */
162
163
164
        // defer showing to the template level, still get facets, as this allows optionally for likes of a tag cloud
165
        // $results['ShowAllIfEmptyQuery'] = $model->ShowAllIfEmptyQuery;
166
        // $results['CleanedLink'] = $this->Link();
167
168
        return $this->renderSearchResults($model, $results);
169
    }
170
171
172
    public function performSearchIncludingFacets(SearchPage $searchPage, ?string $q): SearchResults
173
    {
174
        $factory = new SearcherFactory();
175
176
        /** @var \Suilven\FreeTextSearch\Interfaces\Searcher $searcher */
177
        $searcher = $factory->getSearcher();
178
        $searcher->setFilters($this->selected);
179
        $searcher->setIndexName($searchPage->IndexToSearch);
180
181
        \error_log('SEARCH PAGE PAGE SIZE: ' . $searchPage->PageSize);
182
183
        $facets = $searchPage->getFacetFields();
184
        $hasManyFields = $searchPage->getHasManyFields();
185
186
        // @todo ShutterSpeed breaks, no idea why
187
188
        $searcher->setFacettedTokens($facets);
189
        $searcher->setHasManyTokens($hasManyFields);
190
191
       // $searcher->setFilters(['ISO' => '400']);
192
193
        $this->paginateSearcher($searcher);
194
195
        return $searcher->search($q);
196
    }
197
198
199
    /** @throws \Exception */
200
    private function renderSearchResults(
201
        SearchPage $model,
202
        SearchResults $results
203
    ): \SilverStripe\View\ViewableData_Customised {
204
        $indexes = new Indexes();
205
        $index = $indexes->getIndex($model->IndexToSearch);
206
207
        $hasManyFieldsDetails = $index->getHasManyFields();
208
        $hasManyFieldsNames = \array_keys($hasManyFieldsDetails);
209
210
        /** @var string $clazz */
211
        $clazz = $index->getClass();
212
213
        $templateName = 'Suilven/FreeTextSearch/' . \str_replace('\\', '/', $clazz);
214
        $splits = \explode('/', $templateName);
215
        $last = \array_pop($splits);
216
        $templateName = \implode('/', $splits) . '/Includes/' . $last;
217
218
219
        $records = $results->getRecords();
220
221
        $newRecords = new ArrayList();
222
        foreach ($records as $record) {
223
            $highsList = new ArrayList();
224
            $highlightsArray = $record->Highlights;
225
226
            if (isset($highlightsArray['Title'])) {
227
                $record->ResultTitle = $highlightsArray['Title'][0];
228
                unset($highlightsArray['Title']);
229
            }
230
231
            $record->HighlightedLink = $record->Link;
232
            if (isset($highlightsArray['Link']) && \count($highlightsArray['Link']) > 0) {
233
                $record->HighlightedLink = $highlightsArray['Link'][0];
234
                unset($highlightsArray['Link']);
235
            }
236
237
            // this simply repeats the title most times
238
            unset($highlightsArray['MenuTitle']);
239
240
            $keys = \is_null($highlightsArray)
241
                ? []
242
                : \array_keys($highlightsArray);
243
            foreach ($keys as $highlightedField) {
244
                foreach ($highlightsArray[$highlightedField] as $highlightsForField) {
245
                    $do = new DataObject();
246
                    // @phpstan-ignore-next-line
247
                    $do->Snippet = '...' . $highlightsForField . '...';
248
249
                    $highsList->push($do);
250
                }
251
            }
252
253
            $record->Highlights = $highsList;
254
255
            $html = $this->renderWith(
256
                [
257
                    $templateName,
258
                    'Suilven/FreeTextSearch/SilverStripe/CMS/Model/Includes/SiteTree',
259
                ],
260
                [
261
                    'Record' => $record,
262
                    'SimilarLink' => $this->Link('similar') . '/' . $record->ID,
263
                ]
264
            );
265
            $record->HTML = $html;
266
            $newRecords->push($record);
267
        }
268
269
        $paginatedList = new PaginatedList($records);
270
        $paginatedList->setLimitItems(false);
271
        $paginatedList->setPageLength($results->getPageSize());
272
        $paginatedList->setTotalItems($results->getTotaNumberOfResults());
273
        $paginatedList->setCurrentPage($results->getPage());
274
275
276
        $facets = $results->getFacets();
277
        $selectedFacetNames = \array_keys($this->selected);
278
279
280
        $displayFacets = new ArrayList();
281
282
        $helper = new FacetLinkHelper();
283
284
        /** @var \Suilven\FreeTextSearch\Container\Facet $facet */
285
        foreach ($facets as $facet) {
286
            $displayFacet = new DataObject();
287
            $facetName= $facet->getName();
288
            /** @phpstan-ignore-next-line */
289
            $displayFacet->Name = $facetName;
290
291
            $helper->setFacetInContext($facetName);
292
            $isHasManyFacet = \in_array($facetName, $hasManyFieldsNames, true);
293
            $isSelectedFacet = \in_array($facetName, $selectedFacetNames, true);
294
295
            $counts = new ArrayList();
296
            /** @var \Suilven\FreeTextSearch\Container\FacetCount $facetCount */
297
            foreach ($facet->getFacetCounts() as $facetCount) {
298
                // @todo Make this an object
299
                $count = new DataObject();
300
                $key = $facetCount->getKey();
301
                /** @phpstan-ignore-next-line */
302
                $count->Key = $key;
303
                /** @phpstan-ignore-next-line */
304
                $count->Count = $facetCount->getCount();
305
                $link = $helper->isSelectedFacet($key) ? null: $helper->getDrillDownFacetLink(
306
                    $model->Link(),
307
                    $count->Key
308
                );
309
                $clearFacetLink = $helper->isSelectedFacet($key)
310
                    ? $helper->getClearFacetLink($model->Link(), $facet->getName())
311
                    : null;
312
313
                // @phpstan-ignore-next-line
314
                $count->Link = $link;
315
316
                // @phpstan-ignore-next-line
317
                $count->ClearFacetLink = $clearFacetLink;
318
319
                if ($isHasManyFacet && !$isSelectedFacet) {
320
                    $counts->push($count);
321
                } elseif ($isSelectedFacet && $helper->isSelectedFacet($key)) {
322
                    $counts->push($count);
323
                }
324
            }
325
326
            // @phpstan-ignore-next-line
327
            $displayFacet->FacetCounts = $counts;
328
329
330
            $displayFacets->push($displayFacet);
331
        }
332
333
        return $this->customise(new ArrayData([
334
            'NumberOfResults' => $results->getTotaNumberOfResults(),
335
            'Query' => $results->getQuery(),
336
            'Records' => $newRecords,
337
            'Page' => $results->getPage(),
338
            'PageSize' => $results->getPageSize(),
339
            'Pages' => $results->getTotalPages(),
340
            'Suggestions' => new ArrayList($results->getSuggestions()),
341
            'Time' => $results->getTime(),
342
            'Pagination' => $paginatedList,
343
            'SimilarTo' => $results->getSimilarTo(),
344
            'Facets' => $displayFacets,
345
        ]));
346
    }
347
348
349
    private function paginateSearcher(\Suilven\FreeTextSearch\Interfaces\Searcher &$searcher): void
350
    {
351
        $searcher->setPageSize($this->PageSize);
352
        $start = $this->getRequest()->getVar('start');
353
354
355
        // page 1 is the first page
356
        $page = isset($start)
357
            ? \ceil($start / $this->PageSize) + 1
358
            : 1;
359
360
        $page = \intval($page);
361
        $searcher->setPage($page);
362
    }
363
}
364