Passed
Push — master ( 2f8bcf...138ef2 )
by Gordon
03:57 queued 02:01
created

SearchPageController::paginateSearcher()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 13
rs 10
cc 2
nc 2
nop 1
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\Indexes;
12
13
// @phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
14
15
/**
16
 * Class SearchPageController
17
 *
18
 * @package Suilven\FreeTextSearch\Page
19
 * @property int $ID Page ID
20
 * @property int $PageSize the number of results to show on each page
21
 */
22
23
class SearchPageController extends \PageController
24
{
25
    /** @var array<string> */
26
    private static $allowed_actions = ['index', 'similar'];
27
28
29
    public function similar(): \SilverStripe\View\ViewableData_Customised
30
    {
31
         /** @var \Suilven\FreeTextSearch\Page\SearchPage $model */
32
        $model = SearchPage::get_by_id(SearchPage::class, $this->ID);
33
34
        $indexes = new Indexes();
35
        $index = $indexes->getIndex($model->IndexToSearch);
36
37
        /** @var string $clazz */
38
        $clazz = $index->getClass();
39
40
        $dataObjectID = $this->getRequest()->param('ID');
41
        $dataObjectID = \intval($dataObjectID);
42
43
44
        $objectInContext = DataObject::get_by_id($clazz, $dataObjectID);
45
46
        $factory = new SearcherFactory();
47
        $searcher = $factory->getSearcher();
48
        $searcher->setIndexName($index->getName());
49
        $this->paginateSearcher($searcher);
50
51
        $results = $searcher->searchForSimilar($objectInContext);
52
53
        // tweak results set for rendering purposes, we do not want all the OR constructs
54
        $results->setQuery('');
55
        $results->setSimilarTo($objectInContext);
56
57
        return $this->renderSearchResults($model, $results);
58
    }
59
60
61
    public function index(): \SilverStripe\View\ViewableData_Customised
62
    {
63
        // @todo search indexes addition
64
        $q = $this->getRequest()->getVar('q');
65
66
        /** @var array $selected */
67
        $selected = $this->getRequest()->getVars();
68
69
        /** @var \Suilven\FreeTextSearch\Page\SearchPage $model */
70
        $model = SearchPage::get_by_id(SearchPage::class, $this->ID);
71
72
        // @todo why?
73
       // unset($selected['start']);
74
75
        $results = new SearchResults();
76
77
78
        //unset($selected['q']);
79
80
        if (isset($q) || $model->ShowAllIfEmptyQuery || isset($selected['q'])) {
81
            $results = $this->performSearchIncludingFacets($selected, $model, $q);
82
        }
83
84
85
        /*
86
         *
87
         *         // get suggestions
88
89
        $factory = new SuggesterFactory();
90
91
        $suggester = $factory->getSuggester();
92
93
        // @todo this is returning blank
94
        $suggester->setIndex($model->IndexToSearch);
95
        $suggestions = $suggester->suggest($q);
96
97
98
99
100
        $facetted = isset($results['AllFacets']);
101
102
103
104
        $targetFacet = new ArrayList();
105
        if (isset($model->ShowTagCloudFor)) {
106
            // get the tag cloud from calculated facets, but if not calculated, ie the arrive on the page case,
107
            // calculate them
108
            if ($facetted) {
109
                $facets = $results['AllFacets'];
110
            } else {
111
                $proxyResults = $this->performSearchIncludingFacets($selected, $model, $q);
112
                $facets = $proxyResults['AllFacets'];
113
            }
114
115
            foreach ($facets as $facet) {
116
                $name = $facet->getField('Name');
117
                if ($name === $model->ShowTagCloudFor) {
118
                    $targetFacet = $facet->getField('Facets');
119
120
                    break;
121
                }
122
            }
123
124
            $facetArray = $targetFacet->toArray();
125
            $minSize = 10;
126
            $maxSize = 40;
127
            $maxCount = 0;
128
            foreach ($facetArray as $tag) {
129
                $count = $tag['Count'];
130
                $maxCount = $count > $maxCount
131
                    ? $count
132
                    : $maxCount;
133
            }
134
135
            $tagCloud = new ArrayList();
136
            foreach ($facetArray as $tag) {
137
                $size = $minSize + ($maxSize - $minSize) * $tag['Count'] / $maxCount;
138
                $size = \round($size);
139
                $row = new ArrayData([
140
                    'Name' => $tag['Value'],
141
                    'Size' => $size,
142
                    'Params' => $tag['Params'],
143
                ]);
144
                $tagCloud->push($row);
145
            }
146
147
            $results['TagCloud'] = $tagCloud;
148
        }
149
150
151
        //for($i = 3; $i < 40; $i++) {
152
           // echo "li.tag{$i} { font-size: {$i}px;};\n";
153
        //}
154
155
        */
156
157
158
        // defer showing to the template level, still get facets, as this allows optionally for likes of a tag cloud
159
        // $results['ShowAllIfEmptyQuery'] = $model->ShowAllIfEmptyQuery;
160
        // $results['CleanedLink'] = $this->Link();
161
162
        return $this->renderSearchResults($model, $results);
163
    }
164
165
166
    /** @param array<string,int|string|float|bool> $selected */
167
    public function performSearchIncludingFacets(array $selected, SearchPage $searchPage, ?string $q): SearchResults
168
    {
169
        $factory = new SearcherFactory();
170
171
        /** @var \Suilven\FreeTextSearch\Interfaces\Searcher $searcher */
172
        $searcher = $factory->getSearcher();
173
        $searcher->setFilters($selected);
174
        $searcher->setIndexName($searchPage->IndexToSearch);
175
176
        \error_log('SEARCH PAGE PAGE SIZE: ' . $searchPage->PageSize);
177
178
        $facets = $searchPage->getFacetFields();
179
        $hasManyFields = $searchPage->getHasManyFields();
180
181
        $searcher->setFacettedTokens($facets);
182
        $searcher->setHasManyTokens($hasManyFields);
183
184
        $this->paginateSearcher($searcher);
185
186
        return $searcher->search($q);
187
    }
188
189
190
    /** @throws \Exception */
191
    private function renderSearchResults(
192
        SearchPage $model,
193
        SearchResults $results
194
    ): \SilverStripe\View\ViewableData_Customised {
195
        $indexes = new Indexes();
196
        $index = $indexes->getIndex($model->IndexToSearch);
197
198
        /** @var string $clazz */
199
        $clazz = $index->getClass();
200
201
        $templateName = 'Suilven/FreeTextSearch/' . \str_replace('\\', '/', $clazz);
202
        $splits = \explode('/', $templateName);
203
        $last = \array_pop($splits);
204
        $templateName = \implode('/', $splits) . '/Includes/' . $last;
205
206
207
        $records = $results->getRecords();
208
209
        $newRecords = new ArrayList();
210
        foreach ($records as $record) {
211
            $highsList = new ArrayList();
212
            $highlightsArray = $record->Highlights;
213
214
            if (isset($highlightsArray['Title'])) {
215
                $record->ResultTitle = $highlightsArray['Title'][0];
216
                unset($highlightsArray['Title']);
217
            }
218
219
            $record->HighlightedLink = $record->Link;
220
            if (isset($highlightsArray['Link']) && \count($highlightsArray['Link']) > 0) {
221
                $record->HighlightedLink = $highlightsArray['Link'][0];
222
                unset($highlightsArray['Link']);
223
            }
224
225
            // this simply repeats the title most times
226
            unset($highlightsArray['MenuTitle']);
227
228
            $keys = \is_null($highlightsArray)
229
                ? []
230
                : \array_keys($highlightsArray);
231
            foreach ($keys as $highlightedField) {
232
                foreach ($highlightsArray[$highlightedField] as $highlightsForField) {
233
                    $do = new DataObject();
234
                    // @phpstan-ignore-next-line
235
                    $do->Snippet = '...' . $highlightsForField . '...';
236
237
                    $highsList->push($do);
238
                }
239
            }
240
241
            $record->Highlights = $highsList;
242
243
            $html = $this->renderWith(
244
                [
245
                    $templateName,
246
                    'Suilven/FreeTextSearch/SilverStripe/CMS/Model/Includes/SiteTree',
247
                ],
248
                [
249
                    'Record' => $record,
250
                    'SimilarLink' => $this->Link('similar') . '/' . $record->ID,
251
                ]
252
            );
253
            $record->HTML = $html;
254
            $newRecords->push($record);
255
        }
256
257
        $paginatedList = new PaginatedList($records);
258
        $paginatedList->setLimitItems(false);
259
        $paginatedList->setPageLength($results->getPageSize());
260
        $paginatedList->setTotalItems($results->getTotaNumberOfResults());
261
        $paginatedList->setCurrentPage($results->getPage());
262
263
        return $this->customise(new ArrayData([
264
            'NumberOfResults' => $results->getTotaNumberOfResults(),
265
            'Query' => $results->getQuery(),
266
            'Records' => $newRecords,
267
            'Page' => $results->getPage(),
268
            'PageSize' => $results->getPageSize(),
269
            'Pages' => $results->getTotalPages(),
270
            'Suggestions' => new ArrayList($results->getSuggestions()),
271
            'Time' => $results->getTime(),
272
            'Pagination' => $paginatedList,
273
            'SimilarTo' => $results->getSimilarTo(),
274
        ]));
275
    }
276
277
278
    private function paginateSearcher(\Suilven\FreeTextSearch\Interfaces\Searcher &$searcher): void
279
    {
280
        $searcher->setPageSize($this->PageSize);
281
        $start = $this->getRequest()->getVar('start');
282
283
284
        // page 1 is the first page
285
        $page = isset($start)
286
            ? \ceil($start / $this->PageSize) + 1
287
            : 1;
288
289
        $page = \intval($page);
290
        $searcher->setPage($page);
291
    }
292
}
293