Passed
Pull Request — master (#123)
by
unknown
04:15
created

AbstractController::buildSimplePagination()   C

Complexity

Conditions 12
Paths 240

Size

Total Lines 110
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 52
c 1
b 0
f 0
nc 240
nop 2
dl 0
loc 110
rs 5.6333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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