Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#715)
by Alexander
03:15
created

SearchController::makeFacetsMenuArray()   F

Complexity

Conditions 30
Paths 624

Size

Total Lines 142
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 30
eloc 80
c 4
b 0
f 0
nc 624
nop 1
dl 0
loc 142
rs 0.5222

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
2
/**
3
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
4
 *
5
 * This file is part of the Kitodo and TYPO3 projects.
6
 *
7
 * @license GNU General Public License version 3 or later.
8
 * For the full copyright and license information, please read the
9
 * LICENSE.txt file that was distributed with this source code.
10
 */
11
12
namespace Kitodo\Dlf\Controller;
13
14
use Kitodo\Dlf\Common\Helper;
15
use Kitodo\Dlf\Common\Indexer;
16
use Kitodo\Dlf\Common\Solr;
17
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
18
use TYPO3\CMS\Core\Core\Environment;
19
use TYPO3\CMS\Core\Information\Typo3Version;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use Kitodo\Dlf\Domain\Repository\CollectionRepository;
22
use Kitodo\Dlf\Domain\Repository\MetadataRepository;
23
24
/**
25
 * Controller class for the plugin 'Search'.
26
 *
27
 * @author Sebastian Meyer <[email protected]>
28
 * @author Henrik Lochmann <[email protected]>
29
 * @author Frank Ulrich Weber <[email protected]>
30
 * @author Alexander Bigga <[email protected]>
31
 * @package TYPO3
32
 * @subpackage dlf
33
 * @access public
34
 */
35
class SearchController extends AbstractController
36
{
37
    /**
38
     * @var CollectionRepository
39
     */
40
    protected $collectionRepository;
41
42
    /**
43
     * @param CollectionRepository $collectionRepository
44
     */
45
    public function injectCollectionRepository(CollectionRepository $collectionRepository)
46
    {
47
        $this->collectionRepository = $collectionRepository;
48
    }
49
50
    /**
51
     * @var MetadataRepository
52
     */
53
    protected $metadataRepository;
54
55
    /**
56
     * @param MetadataRepository $metadataRepository
57
     */
58
    public function injectMetadataRepository(MetadataRepository $metadataRepository)
59
    {
60
        $this->metadataRepository = $metadataRepository;
61
    }
62
63
    /**
64
     * @var array $this->searchParams: The current search parameter
65
     * @access protected
66
     */
67
    protected $searchParams;
68
69
    /**
70
     * Search Action
71
     *
72
     * @return void
73
     */
74
    public function searchAction()
75
    {
76
        // if search was triggered, get search parameters from POST variables
77
        $this->searchParams = $this->getParametersSafely('searchParameter');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getParametersSafely('searchParameter') can also be of type string. However, the property $searchParams is declared as type array. Maybe add an additional type 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 mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
78
79
        // output is done by main action
80
        $this->forward('main', null, null, ['searchParameter' => $this->searchParams]);
81
    }
82
83
    /**
84
     * Main action
85
     *
86
     * This shows the search form and optional the facets and extended search form.
87
     *
88
     * @return void
89
     */
90
    public function mainAction()
91
    {
92
        $listViewSearch = false;
93
        // Quit without doing anything if required variables are not set.
94
        if (empty($this->settings['solrcore'])) {
95
            $this->logger->warning('Incomplete plugin configuration');
96
            return;
97
        }
98
99
        // if search was triggered, get search parameters from POST variables
100
        $this->searchParams = $this->getParametersSafely('searchParameter');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getParametersSafely('searchParameter') can also be of type string. However, the property $searchParams is declared as type array. Maybe add an additional type 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 mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
101
        // if search was triggered by the ListView plugin, get the parameters from GET variables
102
        $listRequestData = GeneralUtility::_GPmerged('tx_dlf_listview');
103
104
        if (isset($listRequestData['searchParameter']) && is_array($listRequestData['searchParameter'])) {
105
            $this->searchParams = array_merge($this->searchParams ? : [], $listRequestData['searchParameter']);
0 ignored issues
show
Bug introduced by
It seems like $this->searchParams ?: array() can also be of type string; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
            $this->searchParams = array_merge(/** @scrutinizer ignore-type */ $this->searchParams ? : [], $listRequestData['searchParameter']);
Loading history...
106
            $listViewSearch = true;
107
        }
108
109
        // Pagination of Results: Pass the currentPage to the fluid template to calculate current index of search result.
110
        $widgetPage = $this->getParametersSafely('@widget_0');
111
        if (empty($widgetPage)) {
112
            $widgetPage = ['currentPage' => 1];
113
        }
114
115
        // If a targetPid is given, the results will be shown by ListView on the target page.
116
        if (!empty($this->settings['targetPid']) && !empty($this->searchParams) && !$listViewSearch) {
117
            $this->redirect('main', 'ListView', null,
118
                [
119
                    'searchParameter' => $this->searchParams,
120
                    'widgetPage' => $widgetPage
121
                ], $this->settings['targetPid']
122
            );
123
        }
124
125
        // If no search has been executed, no variables habe to be prepared. An empty form will be shown.
126
        if (is_array($this->searchParams) && !empty($this->searchParams)) {
127
            // get all sortable metadata records
128
            $sortableMetadata = $this->metadataRepository->findByIsSortable(true);
0 ignored issues
show
Bug introduced by
The method findByIsSortable() does not exist on Kitodo\Dlf\Domain\Repository\MetadataRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

128
            /** @scrutinizer ignore-call */ 
129
            $sortableMetadata = $this->metadataRepository->findByIsSortable(true);
Loading history...
129
130
            // get all metadata records to be shown in results
131
            $listedMetadata = $this->metadataRepository->findByIsListed(true);
0 ignored issues
show
Bug introduced by
The method findByIsListed() does not exist on Kitodo\Dlf\Domain\Repository\MetadataRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

131
            /** @scrutinizer ignore-call */ 
132
            $listedMetadata = $this->metadataRepository->findByIsListed(true);
Loading history...
132
133
            $solrResults = [];
134
            // Do not execute the Solr search if used together with ListView plugin.
135
            if (!$listViewSearch) {
136
                $solrResults = $this->documentRepository->findSolrByCollection(null, $this->settings, $this->searchParams, $listedMetadata);
137
            }
138
139
            $documents = $solrResults['documents'] ? : [];
140
            $this->view->assign('documents', $documents);
141
            $rawResults = $solrResults['solrResults']['documents'] ? : [];
142
            $this->view->assign('numResults', count($rawResults));
143
            $this->view->assign('widgetPage', $widgetPage);
144
            $this->view->assign('lastSearch', $this->searchParams);
145
            $this->view->assign('listedMetadata', $listedMetadata);
146
            $this->view->assign('sortableMetadata', $sortableMetadata);
147
148
            // Add the facets menu
149
            $this->addFacetsMenu();
150
151
        }
152
153
        // Get additional fields for extended search.
154
        $this->addExtendedSearch();
155
156
        // Add the current document if present to fluid. This way, we can limit further searches to this document.
157
        if (isset($this->requestData['id'])) {
158
            $currentDocument = $this->documentRepository->findByUid($this->requestData['id']);
159
            $this->view->assign('currentDocument', $currentDocument);
160
        }
161
162
        // Add uHash parameter to suggest parameter to make a basic protection of this form.
163
        if ($this->settings['suggest']) {
164
            $this->view->assign('uHash', GeneralUtility::hmac((string) (new Typo3Version()) . Environment::getExtensionsPath(), 'SearchSuggest'));
165
        }
166
    }
167
168
    /**
169
     * Adds the facets menu to the search form
170
     *
171
     * @access protected
172
     *
173
     * @return string HTML output of facets menu
174
     */
175
    protected function addFacetsMenu()
176
    {
177
        // Quit without doing anything if no facets are selected.
178
        if (empty($this->settings['facets']) && empty($this->settings['facetCollections'])) {
179
            return '';
180
        }
181
182
        // Get facets from plugin configuration.
183
        $facets = [];
184
        foreach (GeneralUtility::trimExplode(',', $this->settings['facets'], true) as $facet) {
185
            $facets[$facet . '_faceting'] = Helper::translate($facet, 'tx_dlf_metadata', $this->settings['storagePid']);
186
        }
187
188
        $this->view->assign('facetsMenu', $this->makeFacetsMenuArray($facets));
189
    }
190
191
    /**
192
     * This builds a menu array for HMENU
193
     *
194
     * @access public
195
     *
196
     * @param string $content: The PlugIn content
197
     * @param array $conf: The PlugIn configuration
198
     *
199
     * @return array HMENU array
200
     */
201
    public function makeFacetsMenuArray($facets)
202
    {
203
        $menuArray = [];
204
        // Set default value for facet search.
205
        $search = [
206
            'params' => [
207
                'component' => [
208
                    'facetset' => [
209
                        'facet' => []
210
                    ]
211
                ]
212
            ]
213
        ];
214
215
        // Set needed parameters for facet search.
216
        if (empty($search['params']['filterquery'])) {
217
            $search['params']['filterquery'] = [];
218
        }
219
220
        $fields = Solr::getFields();
221
222
        // Set search query.
223
        $searchParams = $this->searchParams;
224
        if (
225
            (!empty($searchParams['fulltext']))
226
            || preg_match('/' . $fields['fulltext'] . ':\((.*)\)/', trim($searchParams['query']), $matches)
227
        ) {
228
            // If the query already is a fulltext query e.g using the facets
229
            $searchParams['query'] = empty($matches[1]) ? $searchParams['query'] : $matches[1];
230
            // Search in fulltext field if applicable. Query must not be empty!
231
            if (!empty($this->searchParams['query'])) {
232
                $search['query'] = $fields['fulltext'] . ':(' . Solr::escapeQuery(trim($searchParams['query'])) . ')';
233
            }
234
        } else {
235
            // Retain given search field if valid.
236
            if (!empty($searchParams['query'])) {
237
                $search['query'] = Solr::escapeQueryKeepField(trim($searchParams['query']), $this->settings['storagePid']);
238
            }
239
        }
240
241
        // Add extended search query.
242
        if (
243
            !empty($searchParams['extQuery'])
244
            && is_array($searchParams['extQuery'])
245
        ) {
246
            $allowedOperators = ['AND', 'OR', 'NOT'];
247
            $numberOfExtQueries = count($searchParams['extQuery']);
248
            for ($i = 0; $i < $numberOfExtQueries; $i++) {
249
                if (!empty($searchParams['extQuery'][$i])) {
250
                    if (
251
                        in_array($searchParams['extOperator'][$i], $allowedOperators)
252
                    ) {
253
                        if (!empty($search['query'])) {
254
                            $search['query'] .= ' ' . $searchParams['extOperator'][$i] . ' ';
255
                        }
256
                        $search['query'] .= Indexer::getIndexFieldName($searchParams['extField'][$i], $this->settings['storagePid']) . ':(' . Solr::escapeQuery($searchParams['extQuery'][$i]) . ')';
257
                    }
258
                }
259
            }
260
        }
261
262
        if (isset($this->searchParams['fq']) && is_array($this->searchParams['fq'])) {
263
            foreach ($this->searchParams['fq'] as $fq) {
264
                $search['params']['filterquery'][]['query'] = $fq;
265
            }
266
        }
267
268
        // Get applicable facets.
269
        $solr = Solr::getInstance($this->settings['solrcore']);
270
        if (!$solr->ready) {
271
            $this->logger->error('Apache Solr not available');
272
            return [];
273
        }
274
275
        foreach (array_keys($facets) as $field) {
276
            $search['params']['component']['facetset']['facet'][] = [
277
                'type' => 'field',
278
                'key' => $field,
279
                'field' => $field,
280
                'limit' => $this->settings['limitFacets'],
281
                'sort' => isset($this->settings['sortingFacets']) ? $this->settings['sortingFacets'] : 'count'
282
            ];
283
        }
284
285
        // Set additional query parameters.
286
        $search['params']['start'] = 0;
287
        $search['params']['rows'] = 0;
288
        // Set query.
289
        $search['params']['query'] = $search['query'];
290
        // Perform search.
291
        $selectQuery = $solr->service->createSelect($search['params']);
292
        $results = $solr->service->select($selectQuery);
293
        $facet = $results->getFacetSet();
294
295
        $facetCollectionArray = [];
296
297
        // replace everything expect numbers and comma
298
        $facetCollections = preg_replace('/[^0-9,]/', '', $this->settings['facetCollections']);
299
300
        if (!empty($facetCollections)) {
301
            $collections = $this->collectionRepository->findCollectionsBySettings(['collections' => $facetCollections]);
302
303
            /** @var Collection $collection */
304
            foreach ($collections as $collection) {
305
                $facetCollectionArray[] = $collection->getIndexName();
306
            }
307
        }
308
309
        // Process results.
310
        if ($facet) {
311
            foreach ($facet as $field => $values) {
312
                $entryArray = [];
313
                $entryArray['field'] = substr($field, 0, strpos($field, '_'));
314
                $entryArray['count'] = 0;
315
                $entryArray['_OVERRIDE_HREF'] = '';
316
                $entryArray['ITEM_STATE'] = 'NO';
317
                // Count number of facet values.
318
                $i = 0;
319
                foreach ($values as $value => $count) {
320
                    if ($count > 0) {
321
                        // check if facet collection configuration exists
322
                        if (!empty($this->settings['facetCollections'])) {
323
                            if ($field == "collection_faceting" && !in_array($value, $facetCollectionArray)) {
324
                                continue;
325
                            }
326
                        }
327
                        $entryArray['count']++;
328
                        if ($entryArray['ITEM_STATE'] == 'NO') {
329
                            $entryArray['ITEM_STATE'] = 'IFSUB';
330
                        }
331
                        $entryArray['_SUB_MENU'][] = $this->getFacetsMenuEntry($field, $value, $count, $search, $entryArray['ITEM_STATE']);
332
                        if (++$i == $this->settings['limit']) {
333
                            break;
334
                        }
335
                    } else {
336
                        break;
337
                    }
338
                }
339
                $menuArray[] = $entryArray;
340
            }
341
        }
342
        return $menuArray;
343
    }
344
345
    /**
346
     * Creates an array for a HMENU entry of a facet value.
347
     *
348
     * @access protected
349
     *
350
     * @param string $field: The facet's index_name
351
     * @param string $value: The facet's value
352
     * @param int $count: Number of hits for this facet
353
     * @param array $search: The parameters of the current search query
354
     * @param string &$state: The state of the parent item
355
     *
356
     * @return array The array for the facet's menu entry
357
     */
358
    protected function getFacetsMenuEntry($field, $value, $count, $search, &$state)
359
    {
360
        $entryArray = [];
361
        // Translate value.
362
        if ($field == 'owner_faceting') {
363
            // Translate name of holding library.
364
            $entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_libraries', $this->settings['storagePid']));
365
        } elseif ($field == 'type_faceting') {
366
            // Translate document type.
367
            $entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_structures', $this->settings['storagePid']));
368
        } elseif ($field == 'collection_faceting') {
369
            // Translate name of collection.
370
            $entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_collections', $this->settings['storagePid']));
371
        } elseif ($field == 'language_faceting') {
372
            // Translate ISO 639 language code.
373
            $entryArray['title'] = htmlspecialchars(Helper::getLanguageName($value));
374
        } else {
375
            $entryArray['title'] = htmlspecialchars($value);
376
        }
377
        $entryArray['count'] = $count;
378
        $entryArray['doNotLinkIt'] = 0;
379
        // Check if facet is already selected.
380
        $queryColumn = array_column($search['params']['filterquery'], 'query');
381
        $index = array_search($field . ':("' . Solr::escapeQuery($value) . '")', $queryColumn);
382
        if ($index !== false) {
383
            // Facet is selected, thus remove it from filter.
384
            unset($queryColumn[$index]);
385
            $queryColumn = array_values($queryColumn);
386
            $entryArray['ITEM_STATE'] = 'CUR';
387
            $state = 'ACTIFSUB';
388
            // Reset facets
389
            if ($this->settings['resetFacets']) {
390
                $entryArray['resetFacet'] = true;
391
                $entryArray['queryColumn'] = $queryColumn;
392
            }
393
        } else {
394
            // Facet is not selected, thus add it to filter.
395
            $queryColumn[] = $field . ':("' . Solr::escapeQuery($value) . '")';
396
            $entryArray['ITEM_STATE'] = 'NO';
397
        }
398
        $entryArray['queryColumn'] = $queryColumn;
399
400
        return $entryArray;
401
    }
402
403
    /**
404
     * Returns the extended search form and adds the JS files necessary for extended search.
405
     *
406
     * @access protected
407
     *
408
     * @return string The extended search form or an empty string
409
     */
410
    protected function addExtendedSearch()
411
    {
412
        // Quit without doing anything if no fields for extended search are selected.
413
        if (
414
            empty($this->settings['extendedSlotCount'])
415
            || empty($this->settings['extendedFields'])
416
        ) {
417
            return '';
418
        }
419
420
        // Get field selector options.
421
        $searchFields = GeneralUtility::trimExplode(',', $this->settings['extendedFields'], true);
422
423
        $slotCountArray = [];
424
        for ($i = 0; $i < $this->settings['extendedSlotCount']; $i++) {
425
            $slotCountArray[] = $i;
426
        }
427
428
        $this->view->assign('extendedSlotCount', $slotCountArray);
429
        $this->view->assign('extendedFields', $this->settings['extendedFields']);
430
        $this->view->assign('operators', ['AND', 'OR', 'NOT']);
431
        $this->view->assign('searchFields', $searchFields);
432
    }
433
}
434