Passed
Pull Request — master (#168)
by
unknown
20:44
created

SearchController::getSearchQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 7
dl 0
loc 37
rs 9.7998
c 0
b 0
f 0
1
<?php
2
namespace EWW\Dpf\Controller;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use EWW\Dpf\Services\ElasticSearch\ElasticSearch;
18
use EWW\Dpf\Exceptions\DPFExceptionInterface;
19
use EWW\Dpf\Security\DocumentVoter;
20
use EWW\Dpf\Security\Security;
21
use EWW\Dpf\Domain\Workflow\DocumentWorkflow;
22
use TYPO3\CMS\Core\Messaging\AbstractMessage;
23
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
24
use EWW\Dpf\Session\SearchSessionData;
25
26
/**
27
 * SearchController
28
 */
29
class SearchController extends \EWW\Dpf\Controller\AbstractController
30
{
31
32
    /**
33
     * documentRepository
34
     *
35
     * @var \EWW\Dpf\Domain\Repository\DocumentRepository
36
     * @inject
37
     */
38
    protected $documentRepository = null;
39
40
    /**
41
     * documenTypeRepository
42
     *
43
     * @var \EWW\Dpf\Domain\Repository\DocumentTypeRepository
44
     * @inject
45
     */
46
    protected $documentTypeRepository;
47
48
    /**
49
     * clientRepository
50
     *
51
     * @var \EWW\Dpf\Domain\Repository\ClientRepository
52
     * @inject
53
     */
54
    protected $clientRepository = null;
55
56
57
    /**
58
     * elasticSearch
59
     *
60
     * @var \EWW\Dpf\Services\ElasticSearch\ElasticSearch
61
     * @inject
62
     */
63
    protected $elasticSearch = null;
64
65
66
    /**
67
     * queryBuilder
68
     *
69
     * @var \EWW\Dpf\Services\ElasticSearch\QueryBuilder
70
     * @inject
71
     */
72
    protected $queryBuilder = null;
73
74
75
    /**
76
     * persistence manager
77
     *
78
     * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
79
     * @inject
80
     */
81
    protected $persistenceManager;
82
83
84
    /**
85
     * bookmarkRepository
86
     *
87
     * @var \EWW\Dpf\Domain\Repository\BookmarkRepository
88
     * @inject
89
     */
90
    protected $bookmarkRepository = null;
91
92
93
    const RESULT_COUNT      = 500;
94
    const NEXT_RESULT_COUNT = 500;
95
96
97
98
    /**
99
     * list
100
     *
101
     * @param int $from
102
     * @param int $queryString
103
     *
104
     * @return void
105
     */
106
    protected function list($from = 0, $queryString = '')
107
    {
108
        /** @var SearchSessionData $workspaceSessionData */
109
        $workspaceSessionData = $this->session->getWorkspaceData();
110
        $filters = $workspaceSessionData->getFilters();
111
        $excludeFilters = $workspaceSessionData->getExcludeFilters();
112
        $sortField = $workspaceSessionData->getSortField();
113
        $sortOrder = $workspaceSessionData->getSortOrder();
114
115
        if ($this->security->getUser()->getUserRole() == Security::ROLE_LIBRARIAN) {
116
            $query = $this->getSearchQuery($from, [],
117
                $filters, $excludeFilters, $sortField, $sortOrder, $queryString);
118
        } elseif ($this->security->getUser()->getUserRole() == Security::ROLE_RESEARCHER) {
119
            $query = $this->getSearchQuery($from, [],
120
                $filters, $excludeFilters, $sortField, $sortOrder, $queryString);
121
        }
122
123
        try {
124
            $results = $this->elasticSearch->search($query, 'object');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $query does not seem to be defined for all execution paths leading up to this point.
Loading history...
125
        } catch (\Exception $e) {
126
            $workspaceSessionData->clearSort();
127
            $workspaceSessionData->clearFilters();
128
            $this->session->setWorkspaceData($workspaceSessionData);
129
130
            $this->addFlashMessage(
131
                "Error while building the list!", '', AbstractMessage::ERROR
132
            );
133
        }
134
135
        if ($this->request->hasArgument('message')) {
136
            $this->view->assign('message', $this->request->getArgument('message'));
137
        }
138
139
        if ($this->request->hasArgument('errorFiles')) {
140
            $this->view->assign('errorFiles', $this->request->getArgument('errorFiles'));
141
        }
142
143
144
        $this->view->assign('documentCount', $results['hits']['total']['value']);
145
        $this->view->assign('documents', $results['hits']['hits']);
146
        $this->view->assign('pages', range(1, $results['hits']['total']['value']));
147
        $this->view->assign('itemsPerPage', $this->itemsPerPage());
148
        $this->view->assign('aggregations', $results['aggregations']);
149
        $this->view->assign('filters', $filters);
150
        $this->view->assign('isHideDiscarded', array_key_exists('aliasState', $excludeFilters));
151
        $this->view->assign('isBookmarksOnly', array_key_exists('bookmarks', $excludeFilters));
152
        $this->view->assign('bookmarkIdentifiers', []);
153
    }
154
155
156
    /**
157
     * action list
158
     *
159
     * @return void
160
     */
161
    public function searchAction()
162
    {
163
        $args = $this->request->getArguments();
164
165
        /** @var SearchSessionData $workspaceSessionData */
166
        $workspaceSessionData = $this->session->getWorkspaceData();
167
168
        if ($args['query'] && array_key_exists('fulltext', $args['query'])) {
169
            $queryString = $args['query']['fulltext'];
170
            $workspaceSessionData->setSimpleQuery($queryString);
171
        }
172
173
        if ($args['refresh']) {
174
            $workspaceSessionData->clearSort();
175
            $workspaceSessionData->clearFilters();
176
            $workspaceSessionData->setSimpleQuery("");
177
        }
178
        $this->session->setWorkspaceData($workspaceSessionData);
179
180
        $simpleSearch = $workspaceSessionData->getSimpleQuery();
181
182
        $this->session->setListAction($this->getCurrentAction(), $this->getCurrentController(),
183
            $this->uriBuilder->getRequest()->getRequestUri()
0 ignored issues
show
introduced by
The method getRequestUri() does not exist on TYPO3\CMS\Extbase\Mvc\Request. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

183
            $this->uriBuilder->getRequest()->/** @scrutinizer ignore-call */ getRequestUri()
Loading history...
184
        );
185
186
        $currentPage = null;
187
        $checkedDocumentIdentifiers = [];
188
        $pagination = $this->getParametersSafely('@widget_0');
189
        if ($pagination) {
190
            $checkedDocumentIdentifiers = [];
191
            $currentPage = $pagination['currentPage'];
192
        } else {
193
            $currentPage = 1;
194
        }
195
196
        $this->list(
197
            (empty($currentPage)? 0 : ($currentPage-1) * $this->itemsPerPage()),
198
            $this->escapeQuery(trim($simpleSearch))
0 ignored issues
show
Bug introduced by
$this->escapeQuery(trim($simpleSearch)) of type string is incompatible with the type integer expected by parameter $queryString of EWW\Dpf\Controller\SearchController::list(). ( Ignorable by Annotation )

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

198
            /** @scrutinizer ignore-type */ $this->escapeQuery(trim($simpleSearch))
Loading history...
199
        );
200
201
        $this->view->assign('simpleSearch', $simpleSearch);
202
        $this->view->assign('currentPage', $currentPage);
203
        $this->view->assign('workspaceListAction', $this->getCurrentAction());
204
        $this->view->assign('checkedDocumentIdentifiers', $checkedDocumentIdentifiers);
205
    }
206
207
208
    /**
209
     * get list view data for the workspace
210
     *
211
     * @param int $from
212
     * @param array $bookmarkIdentifiers
213
     * @param array $filters
214
     * @param array $excludeFilters
215
     * @param string $sortField
216
     * @param string $sortOrder
217
     * @param string $queryString
218
     *
219
     * @return array
220
     */
221
    protected function getSearchQuery(
222
        $from = 0, $bookmarkIdentifiers = [], $filters= [], $excludeFilters = [],
223
        $sortField = null, $sortOrder = null, $queryString = null
224
    )
225
    {
226
        $workspaceFilter = [
227
            'bool' => [
228
                'must' => [
229
                    [
230
                        'bool' => [
231
                            'should' => [
232
                                [
233
                                    'term' => [
234
                                        'creator' => $this->security->getUser()->getUid()
235
                                    ]
236
                                ],
237
                                [
238
                                    'bool' => [
239
                                        'must_not' => [
240
                                            [
241
                                                'term' => [
242
                                                    'state' => DocumentWorkflow::STATE_NEW_NONE
243
                                                ]
244
                                            ]
245
                                        ]
246
                                    ]
247
                                ]
248
                            ]
249
                        ]
250
                    ]
251
                ]
252
            ]
253
        ];
254
255
        return $this->queryBuilder->buildQuery(
256
            $this->itemsPerPage(), $workspaceFilter, $from, $bookmarkIdentifiers, $filters,
257
            $excludeFilters, $sortField, $sortOrder, $queryString
258
        );
259
    }
260
261
262
    /**
263
     * Batch operations action.
264
     * @param array $listData
265
     */
266
    public function batchAction($listData)
267
    {
268
        if (array_key_exists('action', $listData)) {
269
            $this->forward($listData['action'], null, null, ['listData' => $listData]);
270
        }
271
    }
272
273
274
    /**
275
     * Batch operation, bookmark documents.
276
     * @param array $listData
277
     */
278
    public function batchBookmarkAction($listData)
279
    {
280
        $successful = [];
281
        $checkedDocumentIdentifiers = [];
282
283
        if (array_key_exists('documentIdentifiers', $listData) && is_array($listData['documentIdentifiers']) ) {
284
            $checkedDocumentIdentifiers = $listData['documentIdentifiers'];
285
286
            foreach ($listData['documentIdentifiers'] as $documentIdentifier) {
287
288
                if ( $listData['documentAliasState'][$documentIdentifier] != DocumentWorkflow::ALIAS_STATE_NEW) {
289
                    if (
290
                        $this->bookmarkRepository->addBookmark(
291
                            $this->security->getUser()->getUid(),
292
                            $documentIdentifier
293
                        )
294
                    ) {
295
                        $successful[] = $documentIdentifier;
296
                    }
297
                }
298
            }
299
300
            if (sizeof($successful) == 1) {
301
                $locallangKey = 'manager.workspace.batchAction.bookmark.success.singular';
302
            } else {
303
                $locallangKey = 'manager.workspace.batchAction.bookmark.success.plural';
304
            }
305
306
            $message = LocalizationUtility::translate(
307
                $locallangKey,
308
                'dpf',
309
                [sizeof($successful), sizeof($listData['documentIdentifiers'])]
310
            );
311
            $this->addFlashMessage(
312
                $message, '',
313
                (sizeof($successful) > 0 ? AbstractMessage::OK : AbstractMessage::WARNING)
314
            );
315
316
        } else {
317
            $message = LocalizationUtility::translate(
318
                'manager.workspace.batchAction.failure',
319
                'dpf');
320
            $this->addFlashMessage($message, '', AbstractMessage::ERROR);
321
        }
322
323
        list($redirectAction, $redirectController) = $this->session->getListAction();
324
        $this->redirect(
325
            $redirectAction, $redirectController, null,
326
            array('message' => $message, 'checkedDocumentIdentifiers' =>  $checkedDocumentIdentifiers));
327
    }
328
329
        /**
330
     * extended search action
331
     */
332
    public function extendedSearchAction()
333
    {
334
        /** @var FrontendUser $feUser */
335
        $feUser = $this->security->getUser();
0 ignored issues
show
Unused Code introduced by
The assignment to $feUser is dead and can be removed.
Loading history...
336
337
        $args = $this->request->getArguments();
338
339
        /** @var SearchSessionData $workspaceSessionData */
340
        $workspaceSessionData = $this->session->getWorkspaceData();
341
342
        if ($args['query'] && array_key_exists('fulltext', $args['query'])) {
343
            $queryString = $args['query']['fulltext'];
344
            $workspaceSessionData->setSimpleQuery($queryString);
345
        }
346
347
        if ($args['refresh']) {
348
            $workspaceSessionData->clearSort();
349
            $workspaceSessionData->clearFilters();
350
            $workspaceSessionData->setSimpleQuery("");
351
        }
352
        $this->session->setWorkspaceData($workspaceSessionData);
353
354
        $simpleSearch = $workspaceSessionData->getSimpleQuery();
355
356
        $documentTypes = $this->documentTypeRepository->findAll();
357
358
        $docTypes = [];
359
        foreach ($documentTypes as $documentType) {
360
            $docTypes[$documentType->getName()] = $documentType->getDisplayName();
361
        }
362
        asort($docTypes, SORT_LOCALE_STRING);
363
        $this->view->assign('documentTypes', $docTypes);
364
365
        $states[DocumentWorkflow::ALIAS_STATE_NEW] = LocalizationUtility::translate(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$states was never initialized. Although not strictly required by PHP, it is generally a good practice to add $states = array(); before regardless.
Loading history...
366
            "manager.documentList.state.".DocumentWorkflow::ALIAS_STATE_NEW, 'dpf'
367
        );
368
        $states[DocumentWorkflow::ALIAS_STATE_REGISTERED] = LocalizationUtility::translate(
369
            "manager.documentList.state.".DocumentWorkflow::ALIAS_STATE_REGISTERED, 'dpf'
370
        );
371
        $states[DocumentWorkflow::ALIAS_STATE_IN_PROGRESS] = LocalizationUtility::translate(
372
            "manager.documentList.state.".DocumentWorkflow::ALIAS_STATE_IN_PROGRESS, 'dpf'
373
        );
374
        $states[DocumentWorkflow::ALIAS_STATE_RELEASED] = LocalizationUtility::translate(
375
            "manager.documentList.state.".DocumentWorkflow::ALIAS_STATE_RELEASED, 'dpf'
376
        );
377
        $states[DocumentWorkflow::ALIAS_STATE_POSTPONED] = LocalizationUtility::translate(
378
            "manager.documentList.state.".DocumentWorkflow::ALIAS_STATE_POSTPONED, 'dpf'
379
        );
380
        $states[DocumentWorkflow::ALIAS_STATE_DISCARDED] = LocalizationUtility::translate(
381
            "manager.documentList.state.".DocumentWorkflow::ALIAS_STATE_DISCARDED, 'dpf'
382
        );
383
384
        $this->view->assign('states', $states);
385
386
        $this->session->setListAction($this->getCurrentAction(), $this->getCurrentController(),
387
            $this->uriBuilder->getRequest()->getRequestUri()
388
        );
389
390
        $currentPage = null;
391
        $checkedDocumentIdentifiers = [];
392
        $pagination = $this->getParametersSafely('@widget_0');
393
        if ($pagination) {
394
            $checkedDocumentIdentifiers = [];
395
            $currentPage = $pagination['currentPage'];
396
        } else {
397
            $currentPage = 1;
398
        }
399
400
        $this->list((empty($currentPage)? 0 : ($currentPage-1) * $this->itemsPerPage()), $simpleSearch);
0 ignored issues
show
Bug introduced by
$simpleSearch of type string is incompatible with the type integer expected by parameter $queryString of EWW\Dpf\Controller\SearchController::list(). ( Ignorable by Annotation )

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

400
        $this->list((empty($currentPage)? 0 : ($currentPage-1) * $this->itemsPerPage()), /** @scrutinizer ignore-type */ $simpleSearch);
Loading history...
401
402
        $this->view->assign('simpleSearch', $simpleSearch);
403
        $this->view->assign('currentPage', $currentPage);
404
        $this->view->assign('workspaceListAction', $this->getCurrentAction());
405
        $this->view->assign('checkedDocumentIdentifiers', $checkedDocumentIdentifiers);
406
    }
407
408
    /**
409
     * gets a list of latest documents
410
     */
411
    public function latestAction()
412
    {
413
        try {
414
            $query = $this->searchLatest();
415
416
            // set type local vs object
417
            $type = 'object';
418
419
            $results = $this->getResultList($query, $type);
0 ignored issues
show
Bug introduced by
The method getResultList() does not exist on EWW\Dpf\Controller\SearchController. ( Ignorable by Annotation )

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

419
            /** @scrutinizer ignore-call */ 
420
            $results = $this->getResultList($query, $type);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
420
        } catch (\Exception $exception) {
421
            $severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::ERROR;
422
423
            if ($exception instanceof DPFExceptionInterface) {
424
                $key = $exception->messageLanguageKey();
425
            } else {
426
                $key = 'LLL:EXT:dpf/Resources/Private/Language/locallang.xlf:error.unexpected';
427
            }
428
429
            $message = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate($key, 'dpf');
430
431
            $this->addFlashMessage(
432
                $message,
433
                '',
434
                $severity,
435
                true
436
            );
437
438
        }
439
440
        $this->forward("list", null, null, array('results' => $results));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $results does not seem to be defined for all execution paths leading up to this point.
Loading history...
441
    }
442
443
444
    /**
445
     * action doubletCheck
446
     *
447
     * @param  \EWW\Dpf\Domain\Model\Document $document
448
     * @return void
449
     */
450
    public function doubletCheckAction(\EWW\Dpf\Domain\Model\Document $document)
451
    {
452
        $this->authorizationChecker->denyAccessUnlessGranted(DocumentVoter::DOUBLET_CHECK, $document);
453
454
        try {
455
            $elasticSearch = $this->objectManager->get(ElasticSearch::class);
456
457
            $client = $this->clientRepository->findAll()->current();
458
459
            // es source fields
460
            // title
461
            // abstract
462
            // author
463
            // language
464
            // publisher
465
            // publisher_place
466
            // distributor
467
            // distributor_place
468
            // distributor_date
469
            // classification
470
            // tag
471
            // identifier
472
            // submitter
473
            // project
474
475
            // is doublet existing?
476
            $query['body']['query']['bool']['must'][]['match']['title'] = $document->getTitle();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.
Loading history...
477
478
            // set owner id
479
            $query['body']['query']['bool']['must'][]['term']['OWNER_ID'] = $client->getOwnerId();
480
481
            $results = $elasticSearch->search($query, '');
482
483
            $searchList = array();
484
485
            // filter out identical document from the search result list
486
            foreach ($results['hits'] as $entry) {
487
488
                if ($document->getObjectIdentifier() && ($document->getObjectIdentifier() === $entry['_source']['PID'])) {
489
                    continue;
490
                }
491
492
                $entryIdentifier = $entry['_source']['_dissemination']['_content']['identifier'][0];
493
                if (is_numeric($entryIdentifier) && $document->getUid() == $entryIdentifier) {
494
                    continue;
495
                }
496
497
                $searchList[] = $entry;
498
            }
499
500
501
            $objectIdentifiers = $this->documentRepository->getObjectIdentifiers();
502
503
            $this->view->assign('document', $document);
504
            $this->view->assign('searchList', $searchList);
505
            $this->view->assign('alreadyImported', $objectIdentifiers);
506
507
        } catch (\Exception $exception) {
508
            $severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::ERROR;
509
510
            if ($exception instanceof DPFExceptionInterface) {
511
                $key = $exception->messageLanguageKey();
512
            } else {
513
                $key = 'LLL:EXT:dpf/Resources/Private/Language/locallang.xlf:error.unexpected';
514
            }
515
516
            $message = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate($key, 'dpf');
517
518
            $this->addFlashMessage(
519
                $message,
520
                '',
521
                $severity,
522
                true
523
            );
524
525
            $this->redirect('list', 'Document', null);
526
        }
527
528
    }
529
530
    /**
531
     * returns the query to get latest documents
532
     * @return mixed
533
     */
534
    public function searchLatest()
535
    {
536
        $client = $this->clientRepository->findAll()->current();
537
538
        // get the latest documents /CREATED_DATE
539
        $query['body']['sort'] = array('CREATED_DATE' => array('order' => 'desc'));
0 ignored issues
show
Comprehensibility Best Practice introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.
Loading history...
540
541
        // add owner id
542
        $query['body']['query']['bool']['must']['term']['OWNER_ID'] = $client->getOwnerId(); // qucosa
543
544
        $query['body']['query']['bool']['should'][0]['query_string']['query']                       = '*';
545
        $query['body']['query']['bool']['should'][1]['has_child']['query']['query_string']['query'] = '*';
546
547
        $query['body']['query']['bool']['minimum_should_match'] = "1"; // 1
548
549
        // child_type is invalid in elasticsearch 7.5
550
        $query['body']['query']['bool']['should'][1]['has_child']['type'] = "datastream"; // 1
551
552
        return $query;
553
    }
554
555
556
    /**
557
     * Returns the number of items to be shown per page.
558
     *
559
     * @return int
560
     */
561
    protected function itemsPerPage()
562
    {
563
        /** @var SearchSessionData $workspaceData */
564
        $workspaceData = $this->session->getWorkspaceData();
565
        $itemsPerPage = $workspaceData->getItemsPerPage();
566
567
        $default = ($this->settings['workspaceItemsPerPage'])? $this->settings['workspaceItemsPerPage'] : 10;
568
        return ($itemsPerPage)? $itemsPerPage : $default;
569
    }
570
571
572
    /**
573
     * escapes lucene reserved characters from string
574
     * @param $string
575
     * @return mixed
576
     */
577
    private function escapeQuery($string)
578
    {
579
        $luceneReservedCharacters = preg_quote('+-&|!(){}[]^~?:\\');
580
        $string                   = preg_replace_callback(
581
            '/([' . $luceneReservedCharacters . '])/',
582
            function ($matches) {
583
                return '\\' . $matches[0];
584
            },
585
            $string
586
        );
587
588
        $string = str_replace("/", "\/", $string);
589
590
        return $string;
591
    }
592
593
}
594