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.

Issues (210)

Classes/Controller/TableOfContentsController.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
4
 *
5
 * This file is part of the Kitodo and TYPO3 projects.
6
 *
7
 * @license GNU General Public License version 3 or later.
8
 * For the full copyright and license information, please read the
9
 * LICENSE.txt file that was distributed with this source code.
10
 */
11
12
namespace Kitodo\Dlf\Controller;
13
14
use Kitodo\Dlf\Common\Helper;
15
use Kitodo\Dlf\Common\MetsDocument;
16
use Psr\Http\Message\ResponseInterface;
17
use TYPO3\CMS\Core\Utility\MathUtility;
18
19
/**
20
 * Controller class for plugin 'Table Of Contents'.
21
 *
22
 * @package TYPO3
23
 * @subpackage dlf
24
 *
25
 * @access public
26
 */
27
class TableOfContentsController extends AbstractController
28
{
29
    /**
30
     * This holds the active entries according to the currently selected page
31
     *
32
     * @access protected
33
     * @var array This holds the active entries according to the currently selected page
34
     */
35
    protected array $activeEntries = [];
36
37
    /**
38
     * The main method of the plugin
39
     *
40
     * @access public
41
     *
42
     * @return ResponseInterface the response
43
     */
44
    public function mainAction(): ResponseInterface
45
    {
46
        // Load current document.
47
        $this->loadDocument();
48
        if ($this->isDocMissing()) {
49
            // Quit without doing anything if required variables are not set.
50
            return $this->htmlResponse();
51
        } else {
52
            $this->setPage();
53
54
            $this->view->assign('toc', $this->makeMenuArray());
55
        }
56
57
        return $this->htmlResponse();
58
    }
59
60
    /**
61
     * This builds a menu array for HMENU
62
     *
63
     * @access private
64
     *
65
     * @return array HMENU array
66
     */
67
    private function makeMenuArray(): array
68
    {
69
        $menuArray = [];
70
        // Does the document have physical elements or is it an external file?
71
        if (
72
            !empty($this->document->getCurrentDocument()->physicalStructure)
0 ignored issues
show
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

72
            !empty($this->document->/** @scrutinizer ignore-call */ getCurrentDocument()->physicalStructure)

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...
73
            || !MathUtility::canBeInterpretedAsInteger($this->requestData['id'])
74
        ) {
75
            $this->getAllLogicalUnits();
76
            // Go through table of contents and create all menu entries.
77
            foreach ($this->document->getCurrentDocument()->tableOfContents as $entry) {
78
                $menuArray[] = $this->getMenuEntry($entry, true);
79
            }
80
        } else {
81
            // Go through table of contents and create top-level menu entries.
82
            foreach ($this->document->getCurrentDocument()->tableOfContents as $entry) {
83
                $menuArray[] = $this->getMenuEntry($entry, false);
84
            }
85
            // Build table of contents from database.
86
            $result = $this->documentRepository->getTableOfContentsFromDb($this->document->getUid(), $this->document->getPid(), $this->settings);
87
88
            $allResults = $result->fetchAllAssociative();
89
90
            if (count($allResults) > 0) {
91
                $menuArray[0]['ITEM_STATE'] = 'CURIFSUB';
92
                $menuArray[0]['_SUB_MENU'] = [];
93
                foreach ($allResults as $resArray) {
94
                    $entry = [
95
                        'label' => !empty($resArray['mets_label']) ? $resArray['mets_label'] : $resArray['title'],
96
                        'type' => $resArray['type'],
97
                        'volume' => $resArray['volume'],
98
                        'year' => $resArray['year'],
99
                        'orderlabel' => $resArray['mets_orderlabel'],
100
                        'pagination' => '',
101
                        'targetUid' => $resArray['uid']
102
                    ];
103
                    $menuArray[0]['_SUB_MENU'][] = $this->getMenuEntry($entry, false);
104
                }
105
            }
106
        }
107
        $this->sortMenu($menuArray);
108
        return $menuArray;
109
    }
110
111
    /**
112
     * This builds an array for one menu entry
113
     *
114
     * @access private
115
     *
116
     * @param array $entry The entry's array from AbstractDocument->getLogicalStructure
117
     * @param bool $recursive Whether to include the child entries
118
     *
119
     * @return array HMENU array for menu entry
120
     */
121
    private function getMenuEntry(array $entry, bool $recursive = false): array
122
    {
123
        $entry = $this->resolveMenuEntry($entry);
124
125
        $entryArray = [];
126
        // Set "title", "volume", "type" and "pagination" from $entry array.
127
        $entryArray['title'] = $this->setTitle($entry);
128
        $entryArray['volume'] = $entry['volume'];
129
        $entryArray['year'] = $entry['year'];
130
        $entryArray['orderlabel'] = $entry['orderlabel'];
131
        $entryArray['type'] = $this->getTranslatedType($entry['type']);
132
        $entryArray['pagination'] = htmlspecialchars($entry['pagination']);
133
        $entryArray['_OVERRIDE_HREF'] = '';
134
        $entryArray['doNotLinkIt'] = 1;
135
        $entryArray['ITEM_STATE'] = 'NO';
136
137
        $this->buildMenuLinks($entryArray, $entry['id'], $entry['points'] ?? null, $entry['targetUid'] ?? null);
138
139
        // Set "ITEM_STATE" to "CUR" if this entry points to current page.
140
        if (in_array($entry['id'], $this->activeEntries)) {
141
            $entryArray['ITEM_STATE'] = 'CUR';
142
        }
143
        // Build sub-menu if available and called recursively.
144
        if (
145
            $recursive === true
146
            && !empty($entry['children'])
147
        ) {
148
            // Build sub-menu only if one of the following conditions apply:
149
            // 1. Current menu node is in rootline
150
            // 2. Current menu node points to another file
151
            // 3. Current menu node has no corresponding images
152
            if (
153
                $entryArray['ITEM_STATE'] == 'CUR'
154
                || (array_key_exists('points', $entry) && is_string($entry['points']))
155
                || empty($this->document->getCurrentDocument()->smLinks['l2p'][$entry['id']])
156
            ) {
157
                $entryArray['_SUB_MENU'] = [];
158
                foreach ($entry['children'] as $child) {
159
                    // Set "ITEM_STATE" to "ACT" if this entry points to current page and has sub-entries pointing to the same page.
160
                    if (in_array($child['id'], $this->activeEntries)) {
161
                        $entryArray['ITEM_STATE'] = 'ACT';
162
                    }
163
                    $entryArray['_SUB_MENU'][] = $this->getMenuEntry($child, true);
164
                }
165
            }
166
            // Append "IFSUB" to "ITEM_STATE" if this entry has sub-entries.
167
            $entryArray['ITEM_STATE'] = ($entryArray['ITEM_STATE'] == 'NO' ? 'IFSUB' : $entryArray['ITEM_STATE'] . 'IFSUB');
168
        }
169
        return $entryArray;
170
    }
171
172
    /**
173
     * Build menu links based on the $entry['points'] array.
174
     *
175
     * @access private
176
     *
177
     * @param array &$entryArray passed by reference
178
     * @param mixed $id
179
     * @param mixed $points
180
     * @param mixed $targetUid
181
     *
182
     * @return void
183
     */
184
    private function buildMenuLinks(array &$entryArray, $id, $points, $targetUid): void
185
    {
186
        if (
187
            !empty($points)
188
            && MathUtility::canBeInterpretedAsInteger($points)
189
        ) {
190
            $entryArray['page'] = $points;
191
            $entryArray['doNotLinkIt'] = 0;
192
            $this->setBasket($entryArray, $id, $points);
193
        } elseif (
194
            !empty($points)
195
            && is_string($points)
196
        ) {
197
            $entryArray['id'] = $points;
198
            $entryArray['page'] = 1;
199
            $entryArray['doNotLinkIt'] = 0;
200
            $this->setBasket($entryArray, $id, $points);
201
        } elseif (!empty($targetUid)) {
202
            $entryArray['id'] = $targetUid;
203
            $entryArray['page'] = 1;
204
            $entryArray['doNotLinkIt'] = 0;
205
            $this->setBasket($entryArray, $id, $targetUid);
206
        }
207
    }
208
209
    /**
210
     * Set basket if basket is included in settings.
211
     *
212
     * @param array $entryArray passed by reference
213
     * @param mixed $id
214
     * @param mixed $startPage
215
     * @return void
216
     */
217
    private function setBasket(array &$entryArray, $id, $startPage): void
218
    {
219
        if (isset($this->settings['basketButton'])) {
220
            $entryArray['basketButton'] = [
221
                'logId' => $id,
222
                'startpage' => $startPage
223
            ];
224
        }
225
    }
226
227
    /**
228
     * If $entry references an external METS file (as mptr),
229
     * try to resolve its database UID and return an updated $entry.
230
     *
231
     * This is so that when linking from a child document back to its parent,
232
     * that link is via UID, so that subsequently the parent's TOC is built from database.
233
     *
234
     * @access private
235
     *
236
     * @param array $entry
237
     *
238
     * @return array
239
     */
240
    private function resolveMenuEntry(array $entry): array
241
    {
242
        // If the menu entry points to the parent document,
243
        // resolve to the parent UID set on indexation.
244
        $doc = $this->document->getCurrentDocument();
245
        if (
246
            $doc instanceof MetsDocument
247
            && ((array_key_exists('points', $entry) && $entry['points'] === $doc->parentHref) || $this->isMultiElement($entry['type']))
248
            && !empty($this->document->getPartof())
249
        ) {
250
            unset($entry['points']);
251
            $entry['targetUid'] = $this->document->getPartof();
252
        }
253
254
        return $entry;
255
    }
256
257
    /**
258
     * Get all logical units the current page or track is a part of.
259
     *
260
     * @access private
261
     *
262
     * @return void
263
     */
264
    private function getAllLogicalUnits(): void
265
    {
266
        $physicalStructure = $this->document->getCurrentDocument()->physicalStructure;
267
268
        if (isset($this->requestData['page']) &&
269
            !empty($this->requestData['page'])
270
            && !empty($physicalStructure)
271
        ) {
272
            $page = $this->requestData['page'];
273
            $structureMapLinks = $this->document->getCurrentDocument()->smLinks;
274
275
            $this->activeEntries = array_merge(
276
                (array) ($structureMapLinks['p2l'][$physicalStructure[0]] ?? []),
277
                (array) ($structureMapLinks['p2l'][$physicalStructure[$page]] ?? [])
278
            );
279
            if (
280
                !empty($this->requestData['double'])
281
                && $page < $this->document->getCurrentDocument()->numPages
282
            ) {
283
                $this->activeEntries = array_merge(
284
                    $this->activeEntries,
285
                    (array) ($structureMapLinks['p2l'][$physicalStructure[$page + 1]] ?? [])
286
                );
287
            }
288
        }
289
    }
290
291
    /**
292
     * Get translated type of entry.
293
     *
294
     * @access private
295
     *
296
     * @param string $type
297
     *
298
     * @return string
299
     */
300
    private function getTranslatedType(string $type): string
301
    {
302
        return Helper::translate($type, 'tx_dlf_structures', $this->settings['storagePid']);
303
    }
304
305
    /**
306
     * Check if element has type 'multivolume_work' or 'multipart_manuscript'.
307
     * For Kitodo.Production prior to version 3.x, hierarchical child documents
308
     * always come with their own METS file for their parent document, even
309
     * if multiple documents in fact have the same parent. To make sure that all
310
     * of them point to the same parent document in Kitodo.Presentation, we
311
     * need some workaround here.
312
     *
313
     * @todo Should be removed when Kitodo.Production 2.x is no longer supported.
314
     *
315
     * @access private
316
     *
317
     * @param string $type
318
     *
319
     * @return bool
320
     */
321
    private function isMultiElement(string $type): bool
322
    {
323
        return $type === 'multivolume_work' || $type === 'multipart_manuscript';
324
    }
325
    /**
326
     * Set title from entry.
327
     *
328
     * @access private
329
     *
330
     * @param array $entry
331
     *
332
     * @return string
333
     */
334
    private function setTitle(array $entry): string
335
    {
336
        $label = $entry['label'];
337
        $orderLabel = $entry['orderlabel'];
338
339
        if (empty($label) && empty($orderLabel)) {
340
            foreach ($this->settings['titleReplacements'] as $titleReplacement) {
341
                if ($entry['type'] == $titleReplacement['type']) {
342
                    $fields = explode(",", $titleReplacement['fields']);
343
                    $title = '';
344
                    foreach ($fields as $field) {
345
                        if ($field == 'type') {
346
                            $title .= $this->getTranslatedType($entry['type']) . ' ';
347
                        } else {
348
                            $title .= $entry[$field] . ' ';
349
                        }
350
                    }
351
                    return trim($title);
352
                }
353
            }
354
        }
355
        return $label ?: $orderLabel;
356
    }
357
358
    /**
359
     * Sort menu by orderlabel.
360
     *
361
     * @access private
362
     *
363
     * @param array &$menu
364
     *
365
     * @return void
366
     */
367
    private function sortMenu(array &$menu): void
368
    {
369
        if ($menu[0]['type'] == $this->getTranslatedType("newspaper")) {
370
            $this->sortSubMenu($menu);
371
        }
372
        if ($menu[0]['type'] == $this->getTranslatedType("year")) {
373
            $this->sortSubMenu($menu);
374
        }
375
    }
376
377
    /**
378
     * Sort sub menu e.g years of the newspaper by orderlabel.
379
     *
380
     * @access private
381
     *
382
     * @param array &$menu
383
     *
384
     * @return void
385
     */
386
    private function sortSubMenu(array &$menu): void
387
    {
388
        usort(
389
            $menu[0]['_SUB_MENU'],
390
            function ($firstElement, $secondElement) {
391
                if (!empty($firstElement['orderlabel'])) {
392
                    return $firstElement['orderlabel'] <=> $secondElement['orderlabel'];
393
                }
394
                return $firstElement['year'] <=> $secondElement['year'];
395
            }
396
        );
397
    }
398
}
399