Passed
Pull Request — master (#123)
by
unknown
08:11 queued 03:22
created

AbstractController::isDocMissingOrEmpty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 0
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\AbstractDocument;
15
use Kitodo\Dlf\Common\Helper;
16
use Kitodo\Dlf\Domain\Model\Document;
17
use Kitodo\Dlf\Domain\Repository\DocumentRepository;
18
use Psr\Log\LoggerAwareInterface;
19
use Psr\Log\LoggerAwareTrait;
20
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
21
use TYPO3\CMS\Core\Localization\LanguageService;
22
use TYPO3\CMS\Core\Pagination\PaginationInterface;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Core\Utility\MathUtility;
25
use TYPO3\CMS\Core\Pagination\PaginatorInterface;
26
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
27
28
/**
29
 * Abstract controller class for most of the plugin controller.
30
 *
31
 * @package TYPO3
32
 * @subpackage dlf
33
 *
34
 * @access public
35
 *
36
 * @abstract
37
 */
38
abstract class AbstractController extends ActionController implements LoggerAwareInterface
39
{
40
    use LoggerAwareTrait;
41
42
    /**
43
     * @access protected
44
     * @var DocumentRepository
45
     */
46
    protected DocumentRepository $documentRepository;
47
48
    /**
49
     * @access public
50
     *
51
     * @param DocumentRepository $documentRepository
52
     *
53
     * @return void
54
     */
55
    public function injectDocumentRepository(DocumentRepository $documentRepository): void
56
    {
57
        $this->documentRepository = $documentRepository;
58
    }
59
60
    /**
61
     * @access protected
62
     * @var Document|null This holds the current document
63
     */
64
    protected ?Document $document = null;
65
66
    /**
67
     * @access protected
68
     * @var array
69
     */
70
    protected array $extConf;
71
72
    /**
73
     * @access protected
74
     * @var array This holds the request parameter
75
     */
76
    protected array $requestData;
77
78
    /**
79
     * @access protected
80
     * @var array This holds some common data for the fluid view
81
     */
82
    protected array $viewData;
83
84
    /**
85
     * @access protected
86
     * @var int
87
     */
88
    protected int $pageUid;
89
90
    /**
91
     * Initialize the plugin controller
92
     *
93
     * @access protected
94
     *
95
     * @return void
96
     */
97
    protected function initialize(): void
98
    {
99
        $this->requestData = GeneralUtility::_GPmerged('tx_dlf');
100
        $this->pageUid = (int) GeneralUtility::_GET('id');
101
102
        // Sanitize user input to prevent XSS attacks.
103
        $this->sanitizeRequestData();
104
105
        // Get extension configuration.
106
        $this->extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('dlf');
107
108
        $this->viewData = [
109
            'pageUid' => $this->pageUid,
110
            'uniqueId' => uniqid(),
111
            'requestData' => $this->requestData
112
        ];
113
    }
114
115
    /**
116
     * Loads the current document into $this->document
117
     *
118
     * @access protected
119
     *
120
     * @param int $documentId The document's UID (fallback: $this->requestData[id])
121
     *
122
     * @return void
123
     */
124
    protected function loadDocument(int $documentId = 0): void
125
    {
126
        // Get document ID from request data if not passed as parameter.
127
        if ($documentId === 0 && !empty($this->requestData['id'])) {
128
            $documentId = $this->requestData['id'];
129
        }
130
131
        // Try to get document format from database
132
        if (!empty($documentId)) {
133
134
            $doc = null;
135
136
            if (MathUtility::canBeInterpretedAsInteger($documentId)) {
137
                $doc = $this->getDocumentByUid($documentId);
138
            } elseif (GeneralUtility::isValidUrl($documentId)) {
139
                $doc = $this->getDocumentByUrl($documentId);
140
            }
141
142
            if ($this->document !== null && $doc !== null) {
143
                $this->document->setCurrentDocument($doc);
144
            }
145
146
        } elseif (!empty($this->requestData['recordId'])) {
147
148
            $this->document = $this->documentRepository->findOneByRecordId($this->requestData['recordId']);
0 ignored issues
show
Bug introduced by
The method findOneByRecordId() does not exist on Kitodo\Dlf\Domain\Repository\DocumentRepository. 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

148
            /** @scrutinizer ignore-call */ 
149
            $this->document = $this->documentRepository->findOneByRecordId($this->requestData['recordId']);
Loading history...
149
150
            if ($this->document !== null) {
151
                $doc = AbstractDocument::getInstance($this->document->getLocation(), $this->settings, true);
152
                if ($doc !== null) {
153
                    $this->document->setCurrentDocument($doc);
154
                } else {
155
                    $this->logger->error('Failed to load document with record ID "' . $this->requestData['recordId'] . '"');
0 ignored issues
show
Bug introduced by
The method error() does not exist on null. ( Ignorable by Annotation )

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

155
                    $this->logger->/** @scrutinizer ignore-call */ 
156
                                   error('Failed to load document with record ID "' . $this->requestData['recordId'] . '"');

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...
156
                }
157
            }
158
        } else {
159
            $this->logger->error('Invalid ID "' . $documentId . '" or PID "' . $this->settings['storagePid'] . '" for document loading');
160
        }
161
    }
162
163
    /**
164
     * Configure URL for proxy.
165
     *
166
     * @access protected
167
     *
168
     * @param string $url URL for proxy configuration
169
     *
170
     * @return void
171
     */
172
    protected function configureProxyUrl(string &$url): void
173
    {
174
        $this->uriBuilder->reset()
175
            ->setTargetPageUid($this->pageUid)
176
            ->setCreateAbsoluteUri(!empty($this->extConf['general']['forceAbsoluteUrl']))
177
            ->setArguments(
178
                [
179
                    'eID' => 'tx_dlf_pageview_proxy',
180
                    'url' => $url,
181
                    'uHash' => GeneralUtility::hmac($url, 'PageViewProxy')
182
                ]
183
            )
184
            ->build();
185
    }
186
187
    /**
188
     * Checks if doc is missing or is empty (no pages)
189
     *
190
     * @access protected
191
     *
192
     * @return bool
193
     */
194
    protected function isDocMissingOrEmpty(): bool
195
    {
196
        return $this->isDocMissing() || $this->document->getCurrentDocument()->numPages < 1;
197
    }
198
199
    /**
200
     * Checks if doc is missing
201
     *
202
     * @access protected
203
     *
204
     * @return bool
205
     */
206
    protected function isDocMissing(): bool
207
    {
208
        return $this->document === null || $this->document->getCurrentDocument() === null;
209
    }
210
211
    /**
212
     * Returns the LanguageService
213
     *
214
     * @access protected
215
     *
216
     * @return LanguageService
217
     */
218
    protected function getLanguageService(): LanguageService
219
    {
220
        return $GLOBALS['LANG'];
221
    }
222
223
    /**
224
     * Safely gets Parameters from request if they exist
225
     *
226
     * @access protected
227
     *
228
     * @param string $parameterName
229
     *
230
     * @return null|string|array
231
     */
232
    protected function getParametersSafely(string $parameterName)
233
    {
234
        if ($this->request->hasArgument($parameterName)) {
235
            return $this->request->getArgument($parameterName);
236
        }
237
        return null;
238
    }
239
240
    /**
241
     * Sanitize input variables.
242
     *
243
     * @access protected
244
     *
245
     * @return void
246
     */
247
    protected function sanitizeRequestData(): void
248
    {
249
        // tx_dlf[id] may only be an UID or URI.
250
        if (
251
            !empty($this->requestData['id'])
252
            && !MathUtility::canBeInterpretedAsInteger($this->requestData['id'])
253
            && !GeneralUtility::isValidUrl($this->requestData['id'])
254
        ) {
255
            $this->logger->warning('Invalid ID or URI "' . $this->requestData['id'] . '" for document loading');
256
            unset($this->requestData['id']);
257
        }
258
259
        // tx_dlf[page] may only be a positive integer or valid XML ID.
260
        if (
261
            !empty($this->requestData['page'])
262
            && !MathUtility::canBeInterpretedAsInteger($this->requestData['page'])
263
            && !Helper::isValidXmlId($this->requestData['page'])
264
        ) {
265
            $this->requestData['page'] = 1;
266
        }
267
268
        // tx_dlf[double] may only be 0 or 1.
269
        $this->requestData['double'] = MathUtility::forceIntegerInRange($this->requestData['double'] ?? 0, 0, 1);
270
    }
271
272
    /**
273
     * Sets page value.
274
     *
275
     * @access protected
276
     *
277
     * @return void
278
     */
279
    protected function setPage(): void
280
    {
281
        if (!empty($this->requestData['logicalPage'])) {
282
            $this->requestData['page'] = $this->document->getCurrentDocument()->getPhysicalPage($this->requestData['logicalPage']);
0 ignored issues
show
Bug introduced by
The method getCurrentDocument() does not exist on null. ( Ignorable by Annotation )

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

282
            $this->requestData['page'] = $this->document->/** @scrutinizer ignore-call */ getCurrentDocument()->getPhysicalPage($this->requestData['logicalPage']);

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...
283
            // The logical page parameter should not appear again
284
            unset($this->requestData['logicalPage']);
285
        }
286
287
        $this->setDefaultPage();
288
    }
289
290
    /**
291
     * Sets default page value.
292
     *
293
     * @access protected
294
     *
295
     * @return void
296
     */
297
    protected function setDefaultPage(): void
298
    {
299
        // Set default values if not set.
300
        // $this->requestData['page'] may be integer or string (physical structure @ID)
301
        if (empty($this->requestData['page'])) {
302
            $this->requestData['page'] = 1;
303
        } elseif ((int) $this->requestData['page'] > 0) {
304
            $this->requestData['page'] = MathUtility::forceIntegerInRange((int) $this->requestData['page'], 1, $this->document->getCurrentDocument()->numPages, 1);
305
        } else {
306
            $this->requestData['page'] = array_search($this->requestData['page'], $this->document->getCurrentDocument()->physicalStructure);
307
        }
308
        // reassign viewData to get correct page
309
        $this->viewData['requestData'] = $this->requestData;
310
    }
311
312
    /**
313
     * This is the constructor
314
     *
315
     * @access public
316
     *
317
     * @return void
318
     */
319
    public function __construct()
320
    {
321
        $this->initialize();
322
    }
323
324
    /**
325
     * build simple pagination
326
     *
327
     * @param PaginationInterface $pagination
328
     * @param PaginatorInterface $paginator
329
     * @return array
330
     */
331
    //TODO: clean this function
332
    protected function buildSimplePagination(PaginationInterface $pagination, PaginatorInterface $paginator): array
333
    {
334
        $firstPage = $pagination->getFirstPageNumber();
335
        $lastPage = $pagination->getLastPageNumber();
336
        $currentPageNumber = $paginator->getCurrentPageNumber();
337
338
        $pages = [];
339
        $pagesSect = [];
340
        $aRange = [];
341
        $nRange = 5;    // ToDo: should be made configurable
342
343
        // lower limit of the range
344
        $nBottom = $currentPageNumber - $nRange;
345
        // upper limit of the range
346
        $nTop = $currentPageNumber + $nRange;
347
        // page range
348
        for ($i = $nBottom; $i <= $nTop; $i++) {
349
            if ($i > 0 and $i <= $lastPage) {
350
                array_push($aRange, $i);
351
            };
352
        };
353
354
        // check whether the first screen page is > 1, if yes then points must be added
355
        if ($aRange[0] > 1) {
356
            array_push($pagesSect, ['label' => '...','startRecordNumber' => '...']);
357
        };
358
        $lastStartRecordNumberGrid = 0; // due to validity outside the loop
359
        foreach (range($firstPage, $lastPage) as $i) {
360
            // detect which pagination is active: ListView or GridView
361
            if (get_class($pagination) == 'TYPO3\CMS\Core\Pagination\SimplePagination') {  // ListView
362
                $lastStartRecordNumberGrid = $i; // save last $startRecordNumber for LastPage button
363
364
                $pages[$i] = [
365
                    'label' => $i,
366
                    'startRecordNumber' => $i
367
                ];
368
369
                // Check if screen page is in range
370
                // <f:for each="{pagination.pagesR}" as="page">
371
                if (in_array($i, $aRange)) {
372
                    array_push($pagesSect, ['label' => $i, 'startRecordNumber' => $i]);
373
                };
374
            } else { // GridView
375
                // to calculate the values for generation the links for the pagination pages
376
                /** @var \Kitodo\Dlf\Pagination\PageGridPaginator $paginator */
377
                $itemsPerPage = $paginator->getPublicItemsPerPage();
378
379
                $startRecordNumber = $itemsPerPage * $i;
380
                $startRecordNumber = $startRecordNumber + 1;
381
                $startRecordNumber = $startRecordNumber - $itemsPerPage;
382
383
                $lastStartRecordNumberGrid = $startRecordNumber; // save last $startRecordNumber for LastPage button
384
385
                // array with label as screen/pagination page number
386
                // and startRecordNumer for correct structure of the link
387
                //<f:link.action action="{action}"
388
                //      addQueryString="true"
389
                //      argumentsToBeExcludedFromQueryString="{0: 'tx_dlf[page]'}"
390
                //      additionalParams="{'tx_dlf[page]': page.startRecordNumber}"
391
                //      arguments="{searchParameter: lastSearch}">{page.label}</f:link.action>
392
                $pages[$i] = [
393
                    'label' => $i,
394
                    'startRecordNumber' => $startRecordNumber
395
                ];
396
397
                // Check if screen page is in range
398
                if (in_array($i, $aRange)) {
399
                    array_push($pagesSect, ['label' => $i,'startRecordNumber' => $startRecordNumber]);
400
                };
401
            };
402
        };
403
404
        // check whether the last element from $aRange <= last screen page, if yes then points must be added
405
        if ($aRange[array_key_last($aRange)] < $lastPage) {
406
            array_push($pagesSect, ['label' => '...', 'startRecordNumber' => '...']);
407
        };
408
409
        // Safely get the next and previous page numbers
410
        $nextPageNumber = isset($pages[$currentPageNumber + 1]) ? $pages[$currentPageNumber + 1]['startRecordNumber'] : null;
411
        $previousPageNumber = isset($pages[$currentPageNumber - 1]) ? $pages[$currentPageNumber - 1]['startRecordNumber'] : null;
412
413
        // 'startRecordNumber' is not required in GridView, only the variant for each loop is required
414
        // 'endRecordNumber' is not required in both views
415
        //
416
        // lastPageNumber       =>  last screen page
417
        // lastPageNumber       =>  Document page to build the last screen page. This is the first document
418
        //                          of the last block of 10 (or less) documents on the last screen page
419
        // firstPageNumber      =>  always 1
420
        // nextPageNumber       =>  Document page to build the next screen page
421
        // nextPageNumberG      =>  Number of the screen page for the next screen page
422
        // previousPageNumber   =>  Document page to build up the previous screen page
423
        // previousPageNumberG  =>  Number of the screen page for the previous screen page
424
        // currentPageNumber    =>  Number of the current screen page
425
        // pagesG               =>  Array with two keys
426
        //    label             =>  Number of the screen page
427
        //    startRecordNumber =>  First document of this block of 10 documents on the same screen page
428
        return [
429
            'lastPageNumber' => $lastPage,
430
            'lastPageNumberG' => $lastStartRecordNumberGrid,
431
            'firstPageNumber' => $firstPage,
432
            'nextPageNumber' => $nextPageNumber,
433
            'nextPageNumberG' => $currentPageNumber + 1,
434
            'previousPageNumber' => $previousPageNumber,
435
            'previousPageNumberG' => $currentPageNumber - 1,
436
            'startRecordNumber' => $pagination->getStartRecordNumber(),
437
            'endRecordNumber' => $pagination->getEndRecordNumber(),
438
            'currentPageNumber' => $currentPageNumber,
439
            'pages' => range($firstPage, $lastPage),
440
            'pagesG' => $pages,
441
            'pagesR' => $pagesSect
442
        ];
443
    }
444
445
    /**
446
     * Get document from repository by uid.
447
     *
448
     * @access private
449
     *
450
     * @param int $documentId The document's UID
451
     *
452
     * @return AbstractDocument
453
     */
454
    private function getDocumentByUid(int $documentId)
455
    {
456
        $doc = null;
457
        $this->document = $this->documentRepository->findOneByIdAndSettings($documentId);
458
459
        if ($this->document) {
460
            $doc = AbstractDocument::getInstance($this->document->getLocation(), $this->settings, true);
461
        } else {
462
            $this->logger->error('Invalid UID "' . $documentId . '" or PID "' . $this->settings['storagePid'] . '" for document loading');
463
        }
464
465
        return $doc;
466
    }
467
468
    /**
469
     * Get document by URL.
470
     *
471
     * @access private
472
     *
473
     * @param string $documentId The document's URL
474
     *
475
     * @return AbstractDocument
476
     */
477
    private function getDocumentByUrl(string $documentId)
478
    {
479
        $doc = AbstractDocument::getInstance($documentId, $this->settings, true);
480
481
        if ($doc !== null) {
482
            if ($doc->recordId) {
483
                // find document from repository by recordId
484
                $docFromRepository = $this->documentRepository->findOneByRecordId($doc->recordId);
485
                if ($docFromRepository !== null) {
486
                    $this->document = $docFromRepository;
487
                } else {
488
                    // create new dummy Document object
489
                    $this->document = GeneralUtility::makeInstance(Document::class);
490
                }
491
            }
492
493
            // Make sure configuration PID is set when applicable
494
            if ($doc->cPid == 0) {
495
                $doc->cPid = max((int) $this->settings['storagePid'], 0);
496
            }
497
498
            $this->document->setLocation($documentId);
499
        } else {
500
            $this->logger->error('Invalid location given "' . $documentId . '" for document loading');
501
        }
502
503
        return $doc;
504
    }
505
}
506