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
02:56
created

SearchController::injectMetadataRepository()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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\Doc;
15
use Kitodo\Dlf\Common\DocumentList;
16
use Kitodo\Dlf\Common\Helper;
17
use Kitodo\Dlf\Common\Indexer;
18
use Kitodo\Dlf\Common\Solr;
19
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
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
        // get results from search
115
        // find all documents from Solr
116
        $solrResults = [];
117
        if (is_array($this->searchParams) && !empty($this->searchParams)) {
118
            // get all sortable metadata records
119
            $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

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

122
            /** @scrutinizer ignore-call */ 
123
            $listedMetadata = $this->metadataRepository->findByIsListed(true);
Loading history...
123
124
            if (is_array($this->searchParams) && !empty($this->searchParams) && !$listViewSearch) {
125
                $solrResults = $this->documentRepository->findSolrByCollection('', $this->settings, $this->searchParams, $listedMetadata);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type Kitodo\Dlf\Domain\Model\Collection expected by parameter $collection of Kitodo\Dlf\Domain\Reposi...:findSolrByCollection(). ( Ignorable by Annotation )

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

125
                $solrResults = $this->documentRepository->findSolrByCollection(/** @scrutinizer ignore-type */ '', $this->settings, $this->searchParams, $listedMetadata);
Loading history...
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
138
        // ABTODO: facets and extended search might fail
139
        // Add the facets menu
140
        $this->addFacetsMenu();
141
142
        // Get additional fields for extended search.
143
        $this->addExtendedSearch();
144
    }
145
146
    /**
147
     * Adds the current document's UID or parent ID to the search form
148
     *
149
     * @access protected
150
     *
151
     * @return string HTML input fields with current document's UID
152
     */
153
    protected function addCurrentDocument()
154
    {
155
        // Load current list object.
156
        $list = GeneralUtility::makeInstance(DocumentList::class);
157
        // Load current document.
158
        if (
159
            !empty($this->requestData['id'])
160
            && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->requestData['id'])
161
        ) {
162
            $this->loadDocument($this->requestData);
163
            // Get document's UID
164
            if ($this->document) {
165
                $this->view->assign('DOCUMENT_ID', $this->document->getUid());
166
            }
167
        } elseif (!empty($list->metadata['options']['params']['filterquery'])) {
168
            // Get document's UID from search metadata.
169
            // The string may be e.g. "{!join from=uid to=partof}uid:{!join from=uid to=partof}uid:2" OR {!join from=uid to=partof}uid:2 OR uid:2"
170
            // or "collection_faceting:("Some Collection Title")"
171
            foreach ($list->metadata['options']['params']['filterquery'] as $facet) {
172
                if (($lastUidPos = strrpos($facet['query'], 'uid:')) !== false) {
173
                    $facetKeyVal = explode(':', substr($facet['query'], $lastUidPos));
174
                    if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($facetKeyVal[1])) {
175
                        $documentId = (int) $facetKeyVal[1];
176
                    }
177
                }
178
            }
179
            if (!empty($documentId)) {
180
                $this->view->assign('DOCUMENT_ID', $documentId);
181
            }
182
        }
183
    }
184
185
186
    /**
187
     * Adds the current collection's UID to the search form
188
     *
189
     * @access protected
190
     *
191
     * @return string HTML input fields with current document's UID and parent ID
192
     */
193
    protected function addCurrentCollection()
194
    {
195
        // Load current collection.
196
        $list = GeneralUtility::makeInstance(DocumentList::class);
197
        if (
198
            !empty($list->metadata['options']['source'])
199
            && $list->metadata['options']['source'] == 'collection'
200
        ) {
201
            $this->view->assign('COLLECTION_ID', $list->metadata['options']['select']);
202
            // Get collection's UID.
203
        } elseif (!empty($list->metadata['options']['params']['filterquery'])) {
204
            // Get collection's UID from search metadata.
205
            foreach ($list->metadata['options']['params']['filterquery'] as $facet) {
206
                $facetKeyVal = explode(':', $facet['query'], 2);
207
                if (
208
                    $facetKeyVal[0] == 'collection_faceting'
209
                    && !strpos($facetKeyVal[1], '" OR "')
210
                ) {
211
                    $collectionId = Helper::getUidFromIndexName(trim($facetKeyVal[1], '(")'), 'tx_dlf_collections');
212
                }
213
            }
214
            $this->view->assign('COLLECTION_ID', $collectionId);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $collectionId does not seem to be defined for all execution paths leading up to this point.
Loading history...
215
        }
216
    }
217
218
    /**
219
     * Adds the facets menu to the search form
220
     *
221
     * @access protected
222
     *
223
     * @return string HTML output of facets menu
224
     */
225
    protected function addFacetsMenu()
226
    {
227
        // Check for typoscript configuration to prevent fatal error.
228
        if (empty($this->settings['facetsConf'])) {
229
            $this->logger->warning('Incomplete plugin configuration');
230
            return '';
231
        }
232
        // Quit without doing anything if no facets are selected.
233
        if (empty($this->settings['facets']) && empty($this->settings['facetCollections'])) {
234
            return '';
235
        }
236
237
        // Get facets from plugin configuration.
238
        $facets = [];
239
        foreach (GeneralUtility::trimExplode(',', $this->settings['facets'], true) as $facet) {
240
            $facets[$facet . '_faceting'] = Helper::translate($facet, 'tx_dlf_metadata', $this->settings['storagePid']);
241
        }
242
243
        $this->view->assign('facetsMenu', $this->makeFacetsMenuArray($facets));
244
    }
245
246
    /**
247
     * This builds a menu array for HMENU
248
     *
249
     * @access public
250
     *
251
     * @param string $content: The PlugIn content
252
     * @param array $conf: The PlugIn configuration
253
     *
254
     * @return array HMENU array
255
     */
256
    public function makeFacetsMenuArray($facets)
257
    {
258
        $menuArray = [];
259
        // Set default value for facet search.
260
        $search = [
261
            'query' => '*',
262
            'params' => [
263
                'component' => [
264
                    'facetset' => [
265
                        'facet' => []
266
                    ]
267
                ]
268
            ]
269
        ];
270
        // Extract query and filter from last search.
271
        $search['query'] = $this->searchParams['query'];
272
        if (isset($this->searchParams['fq']) && is_array($this->searchParams['fq'])) {
273
            foreach($this->searchParams['fq'] as $fq) {
274
                $search['params']['filterquery'][]['query'] = $fq;
275
            }
276
        }
277
        // Get applicable facets.
278
        $solr = Solr::getInstance($this->settings['solrcore']);
279
        if (!$solr->ready) {
280
            $this->logger->error('Apache Solr not available');
281
            return [];
282
        }
283
        // Set needed parameters for facet search.
284
        if (empty($search['params']['filterquery'])) {
285
            $search['params']['filterquery'] = [];
286
        }
287
288
        foreach (array_keys($facets) as $field) {
289
            $search['params']['component']['facetset']['facet'][] = [
290
                'type' => 'field',
291
                'key' => $field,
292
                'field' => $field,
293
                'limit' => $this->settings['limitFacets'],
294
                'sort' => isset($this->settings['sortingFacets']) ? $this->settings['sortingFacets'] : 'count'
295
            ];
296
        }
297
298
        // Set additional query parameters.
299
        $search['params']['start'] = 0;
300
        $search['params']['rows'] = 0;
301
        // Set query.
302
        $search['params']['query'] = $search['query'];
303
        // Perform search.
304
        $selectQuery = $solr->service->createSelect($search['params']);
305
        $results = $solr->service->select($selectQuery);
306
        $facet = $results->getFacetSet();
307
308
        $facetCollectionArray = [];
309
310
        // replace everything expect numbers and comma
311
        $facetCollections = preg_replace('/[^0-9,]/', '', $this->settings['facetCollections']);
312
313
        if (!empty($facetCollections)) {
314
            $collections = $this->collectionRepository->findCollectionsBySettings(['collections' => $facetCollections]);
315
316
            /** @var Collection $collection */
317
            foreach ($collections as $collection) {
318
                $facetCollectionArray[] = $collection->getIndexName();
319
            }
320
        }
321
322
        // Process results.
323
        if ($facet) {
324
            foreach ($facet as $field => $values) {
325
                $entryArray = [];
326
                $entryArray['title'] = htmlspecialchars($facets[$field]);
327
                $entryArray['count'] = 0;
328
                $entryArray['_OVERRIDE_HREF'] = '';
329
                $entryArray['doNotLinkIt'] = 1;
330
                $entryArray['ITEM_STATE'] = 'NO';
331
                // Count number of facet values.
332
                $i = 0;
333
                foreach ($values as $value => $count) {
334
                    if ($count > 0) {
335
                        // check if facet collection configuration exists
336
                        if (!empty($this->settings['facetCollections'])) {
337
                            if ($field == "collection_faceting" && !in_array($value, $facetCollectionArray)) {
338
                                continue;
339
                            }
340
                        }
341
                        $entryArray['count']++;
342
                        if ($entryArray['ITEM_STATE'] == 'NO') {
343
                            $entryArray['ITEM_STATE'] = 'IFSUB';
344
                        }
345
                        $entryArray['_SUB_MENU'][] = $this->getFacetsMenuEntry($field, $value, $count, $search, $entryArray['ITEM_STATE']);
346
                        if (++$i == $this->settings['limit']) {
347
                            break;
348
                        }
349
                    } else {
350
                        break;
351
                    }
352
                }
353
                $menuArray[] = $entryArray;
354
            }
355
        }
356
        return $menuArray;
357
    }
358
359
    /**
360
     * Creates an array for a HMENU entry of a facet value.
361
     *
362
     * @access protected
363
     *
364
     * @param string $field: The facet's index_name
365
     * @param string $value: The facet's value
366
     * @param int $count: Number of hits for this facet
367
     * @param array $search: The parameters of the current search query
368
     * @param string &$state: The state of the parent item
369
     *
370
     * @return array The array for the facet's menu entry
371
     */
372
    protected function getFacetsMenuEntry($field, $value, $count, $search, &$state)
373
    {
374
        $entryArray = [];
375
        // Translate value.
376
        if ($field == 'owner_faceting') {
377
            // Translate name of holding library.
378
            $entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_libraries', $this->settings['storagePid']));
379
        } elseif ($field == 'type_faceting') {
380
            // Translate document type.
381
            $entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_structures', $this->settings['storagePid']));
382
        } elseif ($field == 'collection_faceting') {
383
            // Translate name of collection.
384
            $entryArray['title'] = htmlspecialchars(Helper::translate($value, 'tx_dlf_collections', $this->settings['storagePid']));
385
        } elseif ($field == 'language_faceting') {
386
            // Translate ISO 639 language code.
387
            $entryArray['title'] = htmlspecialchars(Helper::getLanguageName($value));
388
        } else {
389
            $entryArray['title'] = htmlspecialchars($value);
390
        }
391
        $entryArray['count'] = $count;
392
        $entryArray['doNotLinkIt'] = 0;
393
        // Check if facet is already selected.
394
        $queryColumn = array_column($search['params']['filterquery'], 'query');
395
        $index = array_search($field . ':("' . Solr::escapeQuery($value) . '")', $queryColumn);
396
        if ($index !== false) {
397
            // Facet is selected, thus remove it from filter.
398
            unset($queryColumn[$index]);
399
            $queryColumn = array_values($queryColumn);
400
            $entryArray['ITEM_STATE'] = 'CUR';
401
            $state = 'ACTIFSUB';
402
            // Reset facets
403
            if ($this->settings['resetFacets']) {
404
                //remove ($count) for selected facet in template
405
                $entryArray['count'] = false;
406
                $entryArray['queryColumn'] = $queryColumn;
407
                $entryArray['title'] = sprintf(LocalizationUtility::translate('search.resetFacet', 'dlf'), $entryArray['title']);
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Extbase\Utilit...rch.resetFacet', 'dlf') can also be of type null; however, parameter $format of sprintf() does only seem to accept string, 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

407
                $entryArray['title'] = sprintf(/** @scrutinizer ignore-type */ LocalizationUtility::translate('search.resetFacet', 'dlf'), $entryArray['title']);
Loading history...
408
            }
409
        } else {
410
            // Facet is not selected, thus add it to filter.
411
            $queryColumn[] = $field . ':("' . Solr::escapeQuery($value) . '")';
412
            $entryArray['ITEM_STATE'] = 'NO';
413
        }
414
        $entryArray['queryColumn'] = $queryColumn;
415
416
        return $entryArray;
417
    }
418
419
    /**
420
     * Returns the extended search form and adds the JS files necessary for extended search.
421
     *
422
     * @access protected
423
     *
424
     * @return string The extended search form or an empty string
425
     */
426
    protected function addExtendedSearch()
427
    {
428
        // Quit without doing anything if no fields for extended search are selected.
429
        if (
430
            empty($this->settings['extendedSlotCount'])
431
            || empty($this->settings['extendedFields'])
432
        ) {
433
            return '';
434
        }
435
        // Get operator options.
436
        $operatorOptions = [];
437
        foreach (['AND', 'OR', 'NOT'] as $operator) {
438
            $operatorOptions[$operator] = htmlspecialchars(LocalizationUtility::translate('search.' . $operator, 'dlf'));
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Extbase\Utilit...h.' . $operator, 'dlf') can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, 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

438
            $operatorOptions[$operator] = htmlspecialchars(/** @scrutinizer ignore-type */ LocalizationUtility::translate('search.' . $operator, 'dlf'));
Loading history...
439
        }
440
        // Get field selector options.
441
        $fieldSelectorOptions = [];
442
        $searchFields = GeneralUtility::trimExplode(',', $this->settings['extendedFields'], true);
443
        foreach ($searchFields as $searchField) {
444
            $fieldSelectorOptions[$searchField] = Helper::translate($searchField, 'tx_dlf_metadata', $this->settings['storagePid']);
445
        }
446
        $slotCountArray = [];
447
        for ($i = 0; $i < $this->settings['extendedSlotCount']; $i++) {
448
            $slotCountArray[] = $i;
449
        }
450
451
        $this->view->assign('extendedSlotCount', $slotCountArray);
452
        $this->view->assign('extendedFields', $this->settings['extendedFields']);
453
        $this->view->assign('operators', $operatorOptions);
454
        $this->view->assign('searchFields', $fieldSelectorOptions);
455
    }
456
}
457