Completed
Branch master (b9fc31)
by Timo
05:19
created

ResultsCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1.0046

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 5
cts 6
cp 0.8333
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1.0046
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Plugin\Results;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\SearchResult;
28
use ApacheSolrForTypo3\Solr\Plugin\CommandPluginBase;
29
use ApacheSolrForTypo3\Solr\Plugin\PluginCommand;
30
use ApacheSolrForTypo3\Solr\ResultDocumentModifier\ResultDocumentModifier;
31
use ApacheSolrForTypo3\Solr\ResultsetModifier\ResultSetModifier;
32
use ApacheSolrForTypo3\Solr\Search;
33
use ApacheSolrForTypo3\Solr\Template;
34
use ApacheSolrForTypo3\Solr\Util;
35
use TYPO3\CMS\Core\Utility\GeneralUtility;
36
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
37
38
/**
39
 * Results view command
40
 *
41
 * @author Ingo Renner <[email protected]>
42
 */
43
class ResultsCommand implements PluginCommand
44
{
45
46
    /**
47
     * @var Search
48
     */
49
    protected $search;
50
51
    /**
52
     * Parent plugin
53
     *
54
     * @var Results
55
     */
56
    protected $parentPlugin;
57
58
    /**
59
     * Configuration
60
     *
61
     * @var array
62
     */
63
    protected $configuration;
64
65
    /**
66
     * Constructor.
67
     *
68
     * @param CommandPluginBase $parentPlugin Parent plugin object.
69
     */
70 18
    public function __construct(CommandPluginBase $parentPlugin)
71
    {
72 18
        $this->parentPlugin = $parentPlugin;
0 ignored issues
show
Documentation Bug introduced by
$parentPlugin is of type object<ApacheSolrForTypo...ugin\CommandPluginBase>, but the property $parentPlugin was declared to be of type object<ApacheSolrForTypo...Plugin\Results\Results>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
73 18
        $this->configuration = $parentPlugin->typoScriptConfiguration;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parentPlugin->typoScriptConfiguration of type object<ApacheSolrForTypo...ypoScriptConfiguration> is incompatible with the declared type array of property $configuration.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
74 18
        $this->search = $parentPlugin->getSearchResultSetService()->getSearch();
75 18
    }
76
77
    /**
78
     * @return array
79
     */
80 18
    public function execute()
81
    {
82 18
        $numberOfResults = $this->search->getNumberOfResults();
83
84 18
        $query = $this->search->getQuery();
85 18
        $rawQueryTerms = $query->getKeywordsRaw();
86 18
        $queryTerms = $query->getKeywordsCleaned();
87
88 18
        $searchedFor = strtr(
89 18
            $this->parentPlugin->pi_getLL('results_searched_for'),
90 18
            ['@searchWord' => '<span class="tx-solr-search-word">' . $queryTerms . '</span>']
91 18
        );
92
93 18
        $foundResultsInfo = strtr(
94 18
            $this->parentPlugin->pi_getLL('results_found'),
95
            [
96 18
                '@resultsTotal' => $this->search->getNumberOfResults(),
97 18
                '@resultsTime' => $this->search->getQueryTime()
98 18
            ]
99 18
        );
100
101
        return [
102 18
            'searched_for' => $searchedFor,
103 18
            'query' => $queryTerms,
104 18
            'query_urlencoded' => rawurlencode($rawQueryTerms),
105 18
            'query_raw' => $rawQueryTerms,
106 18
            'found' => $foundResultsInfo,
107 18
            'range' => $this->getPageBrowserRange(),
108 18
            'count' => $this->search->getNumberOfResults(),
109 18
            'offset' => ($this->search->getResultOffset() + 1),
110 18
            'query_time' => $this->search->getQueryTime(),
111 18
            'pagebrowser' => $this->getPageBrowser($numberOfResults),
112 18
            'filtered' => $this->isFiltered(),
113 18
            'filtered_by_user' => $this->isFilteredByUser(),
114
            /* construction of the array key:
115
             * loop_ : tells the plugin that the content of that field should be processed in a loop
116
             * result_documents : is the loop name as in the template
117
             * result_document : is the marker name for the single items in the loop
118
             */
119 18
            'loop_result_documents|result_document' => $this->getResultDocuments()
120 18
        ];
121
    }
122
123
    /**
124
     * @return \Apache_Solr_Document[]
125
     */
126 18
    protected function getResultDocuments()
127
    {
128 18
        $responseDocuments = $this->search->getResultDocumentsEscaped();
129 18
        $resultDocuments = [];
130
131 18
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyResultSet'])) {
132 18
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyResultSet'] as $classReference) {
133 18
                $resultSetModifier = GeneralUtility::getUserObj($classReference);
134
135 18
                if ($resultSetModifier instanceof ResultSetModifier) {
136 18
                    $responseDocuments = $resultSetModifier->modifyResultSet($this,
137 18
                        $responseDocuments);
138 18
                } else {
139
                    throw new \UnexpectedValueException(
140
                        get_class($resultSetModifier) . ' must implement interface' . ResultSetModifier::class,
141
                        1310386927
142
                    );
143
                }
144 18
            }
145 18
        }
146
147 18
        foreach ($responseDocuments as $resultDocument) {
148
            /** @var  $resultDocument SearchResult */
149 18
            $temporaryResultDocument = $this->processDocumentFieldsToArray($resultDocument);
150
151 18
            if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyResultDocument'])) {
152 18
                foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifyResultDocument'] as $classReference) {
153 18
                    $resultDocumentModifier = GeneralUtility::getUserObj($classReference);
154
155 18
                    if ($resultDocumentModifier instanceof ResultDocumentModifier) {
156 18
                        $temporaryResultDocument = $resultDocumentModifier->modifyResultDocument($this,
157 18
                            $temporaryResultDocument);
158 18
                    } else {
159
                        throw new \UnexpectedValueException(
160
                            get_class($resultDocumentModifier) . ' must implement interface ' . ResultDocumentModifier::class,
161
                            1310386725
162
                        );
163
                    }
164 18
                }
165 18
            }
166
167 18
            $renderedResultDocument = $this->renderDocumentFields($temporaryResultDocument);
168 18
            $renderedResultDocument = $this->renderVariants($resultDocument, $renderedResultDocument);
169
170 18
            $resultDocuments[] = $renderedResultDocument;
171 18
            unset($temporaryResultDocument);
172 18
        }
173
174 18
        return $resultDocuments;
175
    }
176
177
    /**
178
     * Renders the collapsedDocuments/variants of a document and adds them into the virtual field "variants".
179
     *
180
     * @param SearchResult $resultDocument
181
     * @param array $renderedResultDocument
182
     * @return mixed
183
     */
184 18
    protected function renderVariants(SearchResult $resultDocument, $renderedResultDocument)
185
    {
186 18
        $availableVariants = $resultDocument->getVariants();
187
188 18
        if (!is_array($availableVariants)) {
189
            return $renderedResultDocument;
190
        }
191
192 18
        foreach ($availableVariants as $expandedResult) {
193
            $expandedDocumentArray = $this->processDocumentFieldsToArray($expandedResult);
194
            $renderedResultDocument['variants'][] = $expandedDocumentArray;
195 18
        }
196
197 18
        return $renderedResultDocument;
198
    }
199
200
    /**
201
     * takes a search result document and processes its fields according to the
202
     * instructions configured in TS. Currently available instructions are
203
     *    * timestamp - converts a date field into a unix timestamp
204
     *    * serialize - uses serialize() to encode multivalue fields which then can be put out using the MULTIVALUE view helper
205
     *    * skip - skips the whole field so that it is not available in the result, useful for the spell field f.e.
206
     * The default is to do nothing and just add the document's field to the
207
     * resulting array.
208
     *
209
     * @param \Apache_Solr_Document $document the Apache_Solr_Document result document
210
     * @return array An array with field values processed like defined in TS
211
     */
212 18
    protected function processDocumentFieldsToArray(
213
        \Apache_Solr_Document $document
214
    ) {
215 18
        $processingInstructions = $this->configuration->getSearchResultsFieldProcessingInstructionsConfiguration();
0 ignored issues
show
Bug introduced by
The method getSearchResultsFieldPro...structionsConfiguration cannot be called on $this->configuration (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
216 18
        $availableFields = $document->getFieldNames();
217 18
        $result = [];
218
219 18
        foreach ($availableFields as $fieldName) {
220 18
            $processingInstruction = $processingInstructions[$fieldName];
221
222
            // TODO switch to field processors
223
            // TODO allow to have multiple (comma-separated) instructions for each field
224
            switch ($processingInstruction) {
225 18
                case 'timestamp':
226 18
                    $processedFieldValue = Util::isoToTimestamp($document->{$fieldName});
227 18
                    break;
228 18
                case 'serialize':
229
                    if (!empty($document->{$fieldName})) {
230
                        $processedFieldValue = serialize($document->{$fieldName});
231
                    } else {
232
                        $processedFieldValue = '';
233
                    }
234
                    break;
235 18
                case 'skip':
236
                    continue 2;
237 18
                default:
238 18
                    $processedFieldValue = $document->{$fieldName};
239 18
            }
240
241
            // escape markers in document fields
242
            // TODO remove after switching to fluid templates
243 18
            $processedFieldValue = Template::escapeMarkers($processedFieldValue);
244
245 18
            $result[$fieldName] = $processedFieldValue;
246 18
        }
247
248 18
        return $result;
249
    }
250
251
    /**
252
     * @param array $document
253
     * @return array
254
     */
255 18
    protected function renderDocumentFields(array $document)
256
    {
257 18
        $renderingInstructions = $this->configuration->getSearchResultsFieldRenderingInstructionsConfiguration();
0 ignored issues
show
Bug introduced by
The method getSearchResultsFieldRen...structionsConfiguration cannot be called on $this->configuration (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
258 18
        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
259 18
        $cObj->start($document);
260
261 18
        foreach ($renderingInstructions as $renderingInstructionName => $renderingInstruction) {
262 18
            if (!is_array($renderingInstruction)) {
263 18
                $renderedField = $cObj->cObjGetSingle(
264 18
                    $renderingInstructions[$renderingInstructionName],
265 18
                    $renderingInstructions[$renderingInstructionName . '.']
266 18
                );
267
268 18
                $document[$renderingInstructionName] = $renderedField;
269 18
            }
270 18
        }
271
272 18
        return $document;
273
    }
274
275
    /**
276
     * @param $numberOfResults
277
     * @return string
278
     */
279 18
    protected function getPageBrowser($numberOfResults)
280
    {
281 18
        $pageBrowserMarkup = '';
282 18
        $solrPageBrowserConfiguration = $this->configuration->getSearchResultsPageBrowserConfiguration();
0 ignored issues
show
Bug introduced by
The method getSearchResultsPageBrowserConfiguration cannot be called on $this->configuration (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
283
284 18
        if ($solrPageBrowserConfiguration['enabled']) {
285 18
            $resultsPerPage = $this->parentPlugin->getSearchResultSetService()->getLastResultSet()->getResultsPerPage();
286 18
            $numberOfPages = intval($numberOfResults / $resultsPerPage)
287 18
                + (($numberOfResults % $resultsPerPage) == 0 ? 0 : 1);
288
289 18
            $solrGetParameters = GeneralUtility::_GET('tx_solr');
290 18
            if (!is_array($solrGetParameters)) {
291 17
                $solrGetParameters = [];
292 17
            }
293 18
            $currentPage = $solrGetParameters['page'];
294 18
            unset($solrGetParameters['page']);
295
296 18
            $pageBrowserConfiguration = array_merge(
297 18
                $solrPageBrowserConfiguration,
298
                [
299 18
                    'numberOfPages' => $numberOfPages,
300 18
                    'currentPage' => $currentPage,
301 18
                    'extraQueryString' => GeneralUtility::implodeArrayForUrl('tx_solr',
302 18
                        $solrGetParameters),
303 18
                    'templateFile' => $this->configuration->getTemplateByFileKey('pagebrowser')
0 ignored issues
show
Bug introduced by
The method getTemplateByFileKey cannot be called on $this->configuration (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
304 18
                ]
305 18
            );
306
307 18
            $pageBrowser = GeneralUtility::makeInstance(
308 18
                PageBrowser::class,
309 18
                $pageBrowserConfiguration,
310 18
                $this->getPageBrowserLabels()
311 18
            );
312
313 18
            $pageBrowserMarkup = $pageBrowser->render();
314 18
        }
315
316 18
        return $pageBrowserMarkup;
317 18
    }
318
319
    /**
320
     * Gets the labels for us in the page browser
321
     *
322
     * @return array page browser labels
323
     */
324 18
    protected function getPageBrowserLabels()
325
    {
326
        $labelKeys = [
327 18
            'pagebrowser_first',
328 18
            'pagebrowser_next',
329 18
            'pagebrowser_prev',
330
            'pagebrowser_last'
331 18
        ];
332
333 18
        $labels = [];
334 18
        foreach ($labelKeys as $labelKey) {
335 18
            $labels[$labelKey] = $this->parentPlugin->pi_getLL($labelKey);
336 18
        }
337
338 18
        return $labels;
339
    }
340
341
    /**
342
     * @return string
343
     */
344 18
    protected function getPageBrowserRange()
345
    {
346 18
        $resultsFrom = $this->search->getResponseBody()->start + 1;
347 18
        $resultsTo = $resultsFrom + count($this->search->getResultDocumentsEscaped()) - 1;
348 18
        $resultsTotal = $this->search->getNumberOfResults();
349
350 18
        $label = strtr(
351 18
            $this->parentPlugin->pi_getLL('results_range'),
352
            [
353 18
                '@resultsFrom' => $resultsFrom,
354 18
                '@resultsTo' => $resultsTo,
355
                '@resultsTotal' => $resultsTotal
356 18
            ]
357 18
        );
358
359 18
        return $label;
360
    }
361
362
    /**
363
     * Gets the parent plugin.
364
     *
365
     * @return Results
366
     */
367 18
    public function getParentPlugin()
368
    {
369 18
        return $this->parentPlugin;
370
    }
371
372
    /**
373
     * Determines whether filters have been applied to the query or not.
374
     *
375
     * @return string 1 if filters are applied, 0 if not (for use in templates)
376
     */
377 18
    protected function isFiltered()
378
    {
379 18
        $filters = $this->search->getQuery()->getFilters();
380 18
        $filtered = !empty($filters);
381
382 18
        return ($filtered ? '1' : '0');
383
    }
384
385
    /**
386
     * Determines whether filters have been applied by the user (facets for
387
     * example) to the query or not.
388
     *
389
     * @return string 1 if filters are applied, 0 if not (for use in templates)
390
     */
391 18
    protected function isFilteredByUser()
392
    {
393 18
        $userFiltered = false;
394 18
        $resultParameters = GeneralUtility::_GET('tx_solr');
395
396 18
        if (isset($resultParameters['filter'])) {
397 1
            $userFiltered = true;
398 1
        }
399
400 18
        return ($userFiltered ? '1' : '0');
401
    }
402
}
403