We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
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
|
|||
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 |
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.