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 — dev-extbase-fluid (#754)
by Alexander
08:45
created

SearchController::addCurrentDocument()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 9
eloc 15
c 4
b 0
f 0
nc 11
nop 0
dl 0
loc 28
rs 8.0555
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
class SearchController extends AbstractController
25
{
26
    /**
27
     * @var CollectionRepository
28
     */
29
    protected $collectionRepository;
30
31
    /**
32
     * @param CollectionRepository $collectionRepository
33
     */
34
    public function injectCollectionRepository(CollectionRepository $collectionRepository)
35
    {
36
        $this->collectionRepository = $collectionRepository;
37
    }
38
39
    /**
40
     * @var MetadataRepository
41
     */
42
    protected $metadataRepository;
43
44
    /**
45
     * @param MetadataRepository $metadataRepository
46
     */
47
    public function injectMetadataRepository(MetadataRepository $metadataRepository)
48
    {
49
        $this->metadataRepository = $metadataRepository;
50
    }
51
52
    /**
53
     * @var array $this->searchParams: The current search parameter
54
     * @access protected
55
     */
56
    protected $searchParams;
57
58
    /**
59
     * Search Action
60
     *
61
     * @return void
62
     */
63
    public function searchAction()
64
    {
65
        // if search was triggered, get search parameters from POST variables
66
        $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...
67
68
        // output is done by main action
69
        $this->forward('main', null, null, ['searchParameter' => $this->searchParams]);
70
    }
71
72
    /**
73
     * Main action
74
     *
75
     * This shows the search form and optional the facets and extended search form.
76
     *
77
     * @return void
78
     */
79
    public function mainAction()
80
    {
81
        $listViewSearch = false;
82
        // Quit without doing anything if required variables are not set.
83
        if (empty($this->settings['solrcore'])) {
84
            $this->logger->warning('Incomplete plugin configuration');
85
            return;
86
        }
87
88
        // if search was triggered, get search parameters from POST variables
89
        $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...
90
        // if search was triggered by the ListView plugin, get the parameters from GET variables
91
        $listRequestData = GeneralUtility::_GPmerged('tx_dlf_listview');
92
93
        if (isset($listRequestData['searchParameter']) && is_array($listRequestData['searchParameter'])) {
94
            $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

94
            $this->searchParams = array_merge(/** @scrutinizer ignore-type */ $this->searchParams ? : [], $listRequestData['searchParameter']);
Loading history...
95
            $listViewSearch = true;
96
        }
97
98
        // Pagination of Results: Pass the currentPage to the fluid template to calculate current index of search result.
99
        $widgetPage = $this->getParametersSafely('@widget_0');
100
        if (empty($widgetPage)) {
101
            $widgetPage = ['currentPage' => 1];
102
        }
103
104
        // If a targetPid is given, the results will be shown by ListView on the target page.
105
        if (!empty($this->settings['targetPid']) && !empty($this->searchParams) && !$listViewSearch) {
106
            $this->redirect('main', 'ListView', null,
107
                [
108
                    'searchParameter' => $this->searchParams,
109
                    'widgetPage' => $widgetPage
110
                ], $this->settings['targetPid']
111
            );
112
        }
113
114
        // If no search has been executed, no variables habe to be prepared. An empty form will be shown.
115
        if (is_array($this->searchParams) && !empty($this->searchParams)) {
116
            // get all sortable metadata records
117
            $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

117
            /** @scrutinizer ignore-call */ 
118
            $sortableMetadata = $this->metadataRepository->findByIsSortable(true);
Loading history...
118
119
            // get all metadata records to be shown in results
120
            $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

120
            /** @scrutinizer ignore-call */ 
121
            $listedMetadata = $this->metadataRepository->findByIsListed(true);
Loading history...
121
122
            $solrResults = [];
123
            // Do not execute the Solr search if used together with ListView plugin.
124
            if (!$listViewSearch) {
125
                $solrResults = $this->documentRepository->findSolrByCollection(null, $this->settings, $this->searchParams, $listedMetadata);
126
            }
127
128
            $documents = $solrResults['documents'] ? : [];
129
            $this->view->assign('documents', $documents);
130
            $rawResults = $solrResults['solrResults']['documents'] ? : [];
131
            $this->view->assign('numResults', count($rawResults));
132
            $this->view->assign('widgetPage', $widgetPage);
133
            $this->view->assign('lastSearch', $this->searchParams);
134
            $this->view->assign('listedMetadata', $listedMetadata);
135
            $this->view->assign('sortableMetadata', $sortableMetadata);
136
137
            // Add the facets menu
138
            $this->addFacetsMenu();
139
140
        }
141
142
        // Get additional fields for extended search.
143
        $this->addExtendedSearch();
144
145
        // Add the current document if present to fluid. This way, we can limit further searches to this document.
146
        if (isset($this->requestData['id'])) {
147
            $currentDocument = $this->documentRepository->findByUid($this->requestData['id']);
148
            $this->view->assign('currentDocument', $currentDocument);
149
        }
150
151
        // Add uHash parameter to suggest parameter to make a basic protection of this form.
152
        if ($this->settings['suggest']) {
153
            $this->view->assign('uHash', GeneralUtility::hmac((string)(new Typo3Version()) . Environment::getExtensionsPath('dlf'), 'SearchSuggest'));
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Core\Core\Envi...nt::getExtensionsPath() has too many arguments starting with 'dlf'. ( Ignorable by Annotation )

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

153
            $this->view->assign('uHash', GeneralUtility::hmac((string)(new Typo3Version()) . Environment::/** @scrutinizer ignore-call */ getExtensionsPath('dlf'), 'SearchSuggest'));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

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