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 | /** |
||||||
4 | * (c) Kitodo. Key to digital objects e.V. <[email protected]> |
||||||
5 | * |
||||||
6 | * This file is part of the Kitodo and TYPO3 projects. |
||||||
7 | * |
||||||
8 | * @license GNU General Public License version 3 or later. |
||||||
9 | * For the full copyright and license information, please read the |
||||||
10 | * LICENSE.txt file that was distributed with this source code. |
||||||
11 | */ |
||||||
12 | |||||||
13 | namespace Kitodo\Dlf\Common; |
||||||
14 | |||||||
15 | use Flow\JSONPath\JSONPath; |
||||||
16 | use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; |
||||||
17 | use TYPO3\CMS\Core\Database\ConnectionPool; |
||||||
18 | use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction; |
||||||
19 | use TYPO3\CMS\Core\Log\LogManager; |
||||||
20 | use TYPO3\CMS\Core\Utility\GeneralUtility; |
||||||
21 | use Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface; |
||||||
22 | use Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationInterface; |
||||||
23 | use Ubl\Iiif\Presentation\Common\Model\Resources\CanvasInterface; |
||||||
24 | use Ubl\Iiif\Presentation\Common\Model\Resources\ContentResourceInterface; |
||||||
25 | use Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface; |
||||||
26 | use Ubl\Iiif\Presentation\Common\Model\Resources\ManifestInterface; |
||||||
27 | use Ubl\Iiif\Presentation\Common\Model\Resources\RangeInterface; |
||||||
28 | use Ubl\Iiif\Presentation\Common\Vocabulary\Motivation; |
||||||
29 | use Ubl\Iiif\Presentation\V1\Model\Resources\AbstractIiifResource1; |
||||||
30 | use Ubl\Iiif\Presentation\V2\Model\Resources\AbstractIiifResource2; |
||||||
31 | use Ubl\Iiif\Presentation\V3\Model\Resources\AbstractIiifResource3; |
||||||
32 | use Ubl\Iiif\Services\AbstractImageService; |
||||||
33 | use Ubl\Iiif\Services\Service; |
||||||
34 | use Ubl\Iiif\Tools\IiifHelper; |
||||||
35 | |||||||
36 | /** |
||||||
37 | * IiifManifest class for the 'dlf' extension. |
||||||
38 | * |
||||||
39 | * @package TYPO3 |
||||||
40 | * @subpackage dlf |
||||||
41 | * |
||||||
42 | * @access public |
||||||
43 | * |
||||||
44 | * @property int $cPid this holds the PID for the configuration |
||||||
45 | * @property-read array $formats this holds the configuration for all supported metadata encodings |
||||||
46 | * @property bool $formatsLoaded flag with information if the available metadata formats are loaded |
||||||
47 | * @property-read bool $hasFulltext flag with information if there are any fulltext files available |
||||||
48 | * @property array $lastSearchedPhysicalPage the last searched logical and physical page |
||||||
49 | * @property array $logicalUnits this holds the logical units |
||||||
50 | * @property-read array $metadataArray this holds the documents' parsed metadata array |
||||||
51 | * @property bool $metadataArrayLoaded flag with information if the metadata array is loaded |
||||||
52 | * @property-read int $numPages the holds the total number of pages |
||||||
53 | * @property-read int $parentId this holds the UID of the parent document or zero if not multi-volumed |
||||||
54 | * @property-read array $physicalStructure this holds the physical structure |
||||||
55 | * @property-read array $physicalStructureInfo this holds the physical structure metadata |
||||||
56 | * @property bool $physicalStructureLoaded flag with information if the physical structure is loaded |
||||||
57 | * @property-read int $pid this holds the PID of the document or zero if not in database |
||||||
58 | * @property array $rawTextArray this holds the documents' raw text pages with their corresponding structMap//div's ID (METS) or Range / Manifest / Sequence ID (IIIF) as array key |
||||||
59 | * @property-read bool $ready Is the document instantiated successfully? |
||||||
60 | * @property-read string $recordId the METS file's / IIIF manifest's record identifier |
||||||
61 | * @property array $registry this holds the singleton object of the document |
||||||
62 | * @property-read int $rootId this holds the UID of the root document or zero if not multi-volumed |
||||||
63 | * @property-read array $smLinks this holds the smLinks between logical and physical structMap |
||||||
64 | * @property bool $smLinksLoaded flag with information if the smLinks are loaded |
||||||
65 | * @property-read array $tableOfContents this holds the logical structure |
||||||
66 | * @property bool $tableOfContentsLoaded flag with information if the table of contents is loaded |
||||||
67 | * @property-read string $thumbnail this holds the document's thumbnail location |
||||||
68 | * @property bool $thumbnailLoaded flag with information if the thumbnail is loaded |
||||||
69 | * @property-read string $toplevelId this holds the toplevel structure's "@ID" (METS) or the manifest's "@id" (IIIF) |
||||||
70 | * @property \SimpleXMLElement $xml this holds the whole XML file as \SimpleXMLElement object |
||||||
71 | * @property string $asJson this holds the manifest file as string for serialization purposes |
||||||
72 | * @property ManifestInterface $iiif a PHP object representation of a IIIF manifest |
||||||
73 | * @property string $iiifVersion 'IIIF1', 'IIIF2' or 'IIIF3', depending on the API $this->iiif conforms to |
||||||
74 | * @property bool $hasFulltextSet flag if document has already been analyzed for presence of the fulltext for the Solr index |
||||||
75 | * @property array $originalMetadataArray this holds the original manifest's parsed metadata array with their corresponding resource (Manifest / Sequence / Range) ID as array key |
||||||
76 | * @property array $mimeTypes this holds the mime types of linked resources in the manifest (extracted during parsing) for later us |
||||||
77 | * |
||||||
78 | */ |
||||||
79 | final class IiifManifest extends AbstractDocument |
||||||
80 | { |
||||||
81 | /** |
||||||
82 | * @access protected |
||||||
83 | * @var string This holds the manifest file as string for serialization purposes |
||||||
84 | * |
||||||
85 | * @see __sleep() / __wakeup() |
||||||
86 | */ |
||||||
87 | protected string $asJson = ''; |
||||||
88 | |||||||
89 | /** |
||||||
90 | * @access protected |
||||||
91 | * @var ManifestInterface|null A PHP object representation of a IIIF manifest |
||||||
92 | */ |
||||||
93 | protected ?ManifestInterface $iiif; |
||||||
94 | |||||||
95 | /** |
||||||
96 | * @access protected |
||||||
97 | * @var string 'IIIF1', 'IIIF2' or 'IIIF3', depending on the API $this->iiif conforms to: IIIF Metadata API 1, IIIF Presentation API 2 or 3 |
||||||
98 | */ |
||||||
99 | protected string $iiifVersion; |
||||||
100 | |||||||
101 | /** |
||||||
102 | * @access protected |
||||||
103 | * @var bool Document has already been analyzed if it contains fulltext for the Solr index |
||||||
104 | */ |
||||||
105 | protected bool $hasFulltextSet = false; |
||||||
106 | |||||||
107 | /** |
||||||
108 | * @access protected |
||||||
109 | * @var array This holds the original manifest's parsed metadata array with their corresponding resource (Manifest / Sequence / Range) ID as array key |
||||||
110 | */ |
||||||
111 | protected array $originalMetadataArray = []; |
||||||
112 | |||||||
113 | /** |
||||||
114 | * @access protected |
||||||
115 | * @var array Holds the mime types of linked resources in the manifest (extracted during parsing) for later use |
||||||
116 | */ |
||||||
117 | protected array $mimeTypes = []; |
||||||
118 | |||||||
119 | /** |
||||||
120 | * @see AbstractDocument::establishRecordId() |
||||||
121 | */ |
||||||
122 | protected function establishRecordId(int $pid): void |
||||||
123 | { |
||||||
124 | if ($this->iiif !== null) { |
||||||
125 | /* |
||||||
126 | * FIXME This will not consistently work because we can not be sure to have the pid at hand. It may miss |
||||||
127 | * if the plugin that actually loads the manifest allows content from other pages. |
||||||
128 | * Up until now the cPid is only set after the document has been initialized. We need it before to |
||||||
129 | * check the configuration. |
||||||
130 | * TODO Saving / indexing should still work - check! |
||||||
131 | */ |
||||||
132 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
||||||
133 | ->getQueryBuilderForTable('tx_dlf_metadata'); |
||||||
134 | // Get hidden records, too. |
||||||
135 | $queryBuilder |
||||||
136 | ->getRestrictions() |
||||||
137 | ->removeByType(HiddenRestriction::class); |
||||||
138 | $result = $queryBuilder |
||||||
139 | ->select('tx_dlf_metadataformat.xpath AS querypath') |
||||||
140 | ->from('tx_dlf_metadata') |
||||||
141 | ->from('tx_dlf_metadataformat') |
||||||
142 | ->from('tx_dlf_formats') |
||||||
143 | ->where( |
||||||
144 | $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $pid), |
||||||
145 | $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $pid), |
||||||
146 | $queryBuilder->expr()->orX( |
||||||
147 | $queryBuilder->expr()->andX( |
||||||
148 | $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'), |
||||||
149 | $queryBuilder->expr()->eq('tx_dlf_metadataformat.encoded', 'tx_dlf_formats.uid'), |
||||||
150 | $queryBuilder->expr()->eq('tx_dlf_metadata.index_name', $queryBuilder->createNamedParameter('record_id')), |
||||||
151 | $queryBuilder->expr()->eq('tx_dlf_formats.type', $queryBuilder->createNamedParameter($this->getIiifVersion())) |
||||||
152 | ), |
||||||
153 | $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0) |
||||||
154 | ) |
||||||
155 | ) |
||||||
156 | ->execute(); |
||||||
157 | while ($resArray = $result->fetchAssociative()) { |
||||||
158 | $recordIdPath = $resArray['querypath']; |
||||||
159 | if (!empty($recordIdPath)) { |
||||||
160 | try { |
||||||
161 | $this->recordId = $this->iiif->jsonPath($recordIdPath); |
||||||
0 ignored issues
–
show
|
|||||||
162 | } catch (\Exception $e) { |
||||||
163 | $this->logger->warning('Could not evaluate JSONPath to get IIIF record ID'); |
||||||
164 | } |
||||||
165 | } |
||||||
166 | } |
||||||
167 | // For now, it's a hardcoded ID, not only as a fallback |
||||||
168 | if (!isset($this->recordId)) { |
||||||
169 | $this->recordId = $this->iiif->getId(); |
||||||
170 | } |
||||||
171 | } |
||||||
172 | } |
||||||
173 | |||||||
174 | /** |
||||||
175 | * @see AbstractDocument::getDocument() |
||||||
176 | */ |
||||||
177 | protected function getDocument(): IiifResourceInterface |
||||||
178 | { |
||||||
179 | return $this->iiif; |
||||||
180 | } |
||||||
181 | |||||||
182 | /** |
||||||
183 | * Returns a string representing the Metadata / Presentation API version which the IIIF resource |
||||||
184 | * conforms to. This is used for example to extract metadata according to configured patterns. |
||||||
185 | * |
||||||
186 | * @access public |
||||||
187 | * |
||||||
188 | * @return string 'IIIF1' if the resource is a Metadata API 1 resource, 'IIIF2' / 'IIIF3' if |
||||||
189 | * the resource is a Presentation API 2 / 3 resource |
||||||
190 | */ |
||||||
191 | public function getIiifVersion(): string |
||||||
192 | { |
||||||
193 | if (!isset($this->iiifVersion)) { |
||||||
194 | if ($this->iiif instanceof AbstractIiifResource1) { |
||||||
195 | $this->iiifVersion = 'IIIF1'; |
||||||
196 | } elseif ($this->iiif instanceof AbstractIiifResource2) { |
||||||
197 | $this->iiifVersion = 'IIIF2'; |
||||||
198 | } elseif ($this->iiif instanceof AbstractIiifResource3) { |
||||||
199 | $this->iiifVersion = 'IIIF3'; |
||||||
200 | } |
||||||
201 | } |
||||||
202 | return $this->iiifVersion; |
||||||
203 | } |
||||||
204 | |||||||
205 | /** |
||||||
206 | * True if getUseGroups() has been called and $this->useGrps is loaded |
||||||
207 | * |
||||||
208 | * @var bool |
||||||
209 | * @access protected |
||||||
210 | */ |
||||||
211 | protected bool $useGrpsLoaded = false; |
||||||
212 | |||||||
213 | /** |
||||||
214 | * Holds the configured useGrps as array. |
||||||
215 | * |
||||||
216 | * @var array |
||||||
217 | * @access protected |
||||||
218 | */ |
||||||
219 | protected array $useGrps = []; |
||||||
220 | |||||||
221 | /** |
||||||
222 | * IiifManifest also populates the physical structure array entries for matching |
||||||
223 | * 'fileGrp's. To do that, the configuration has to be loaded; afterwards configured |
||||||
224 | * 'fileGrp's for thumbnails, downloads, audio, fulltext and the 'fileGrp's for images |
||||||
225 | * can be requested with this method. |
||||||
226 | * |
||||||
227 | * @access protected |
||||||
228 | * |
||||||
229 | * @param string $use |
||||||
230 | * |
||||||
231 | * @return array|string |
||||||
232 | */ |
||||||
233 | protected function getUseGroups(string $use) |
||||||
234 | { |
||||||
235 | if (!$this->useGrpsLoaded) { |
||||||
236 | // Get configured USE attributes. |
||||||
237 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); |
||||||
238 | if (!empty($extConf['fileGrpImages'])) { |
||||||
239 | $this->useGrps['fileGrpImages'] = GeneralUtility::trimExplode(',', $extConf['fileGrpImages']); |
||||||
240 | } |
||||||
241 | if (!empty($extConf['fileGrpThumbs'])) { |
||||||
242 | $this->useGrps['fileGrpThumbs'] = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']); |
||||||
243 | } |
||||||
244 | if (!empty($extConf['fileGrpDownload'])) { |
||||||
245 | $this->useGrps['fileGrpDownload'] = GeneralUtility::trimExplode(',', $extConf['fileGrpDownload']); |
||||||
246 | } |
||||||
247 | if (!empty($extConf['fileGrpFulltext'])) { |
||||||
248 | $this->useGrps['fileGrpFulltext'] = GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']); |
||||||
249 | } |
||||||
250 | if (!empty($extConf['fileGrpAudio'])) { |
||||||
251 | $this->useGrps['fileGrpAudio'] = GeneralUtility::trimExplode(',', $extConf['fileGrpAudio']); |
||||||
252 | } |
||||||
253 | $this->useGrpsLoaded = true; |
||||||
254 | } |
||||||
255 | return array_key_exists($use, $this->useGrps) ? $this->useGrps[$use] : []; |
||||||
256 | } |
||||||
257 | |||||||
258 | /** |
||||||
259 | * @see AbstractDocument::magicGetPhysicalStructure() |
||||||
260 | */ |
||||||
261 | protected function magicGetPhysicalStructure(): array |
||||||
262 | { |
||||||
263 | // Is there no physical structure array yet? |
||||||
264 | if (!$this->physicalStructureLoaded) { |
||||||
265 | if ($this->iiif == null || !($this->iiif instanceof ManifestInterface)) { |
||||||
266 | return []; |
||||||
267 | } |
||||||
268 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); |
||||||
269 | $iiifId = $this->iiif->getId(); |
||||||
270 | $this->physicalStructureInfo[$iiifId]['id'] = $iiifId; |
||||||
271 | $this->physicalStructureInfo[$iiifId]['dmdId'] = $iiifId; |
||||||
272 | $this->physicalStructureInfo[$iiifId]['label'] = $this->iiif->getLabelForDisplay(); |
||||||
273 | $this->physicalStructureInfo[$iiifId]['orderlabel'] = $this->iiif->getLabelForDisplay(); |
||||||
274 | $this->physicalStructureInfo[$iiifId]['type'] = 'physSequence'; |
||||||
275 | $this->physicalStructureInfo[$iiifId]['contentIds'] = null; |
||||||
276 | |||||||
277 | $this->setFileUseDownload($iiifId, $this->iiif); |
||||||
278 | $this->setFileUseFulltext($iiifId, $this->iiif); |
||||||
279 | |||||||
280 | $fileUseThumbs = $this->getUseGroups('fileGrpThumbs'); |
||||||
281 | $fileUses = $this->getUseGroups('fileGrpImages'); |
||||||
282 | |||||||
283 | if (!empty($this->iiif->getDefaultCanvases())) { |
||||||
284 | // canvases have not order property, but the context defines canveses as @list with a specific order, so we can provide an alternative |
||||||
285 | $elements = []; |
||||||
286 | $canvasOrder = 0; |
||||||
287 | foreach ($this->iiif->getDefaultCanvases() as $canvas) { |
||||||
288 | $canvasOrder++; |
||||||
289 | $thumbnailUrl = $canvas->getThumbnailUrl(); |
||||||
290 | // put thumbnails in thumbnail filegroup |
||||||
291 | if ( |
||||||
292 | !empty($thumbnailUrl) |
||||||
293 | && empty($this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]]) |
||||||
294 | ) { |
||||||
295 | $this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]] = $thumbnailUrl; |
||||||
296 | } |
||||||
297 | // populate structural metadata info |
||||||
298 | $elements[$canvasOrder] = $canvas->getId(); |
||||||
299 | $this->physicalStructureInfo[$elements[$canvasOrder]]['id'] = $canvas->getId(); |
||||||
300 | $this->physicalStructureInfo[$elements[$canvasOrder]]['dmdId'] = null; |
||||||
301 | $this->physicalStructureInfo[$elements[$canvasOrder]]['label'] = $canvas->getLabelForDisplay(); |
||||||
302 | $this->physicalStructureInfo[$elements[$canvasOrder]]['orderlabel'] = $canvas->getLabelForDisplay(); |
||||||
303 | // assume that a canvas always represents a page |
||||||
304 | $this->physicalStructureInfo[$elements[$canvasOrder]]['type'] = 'page'; |
||||||
305 | $this->physicalStructureInfo[$elements[$canvasOrder]]['contentIds'] = null; |
||||||
306 | $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'] = null; |
||||||
307 | if (!empty($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING))) { |
||||||
308 | $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'] = []; |
||||||
309 | foreach ($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING) as $annotationContainer) { |
||||||
310 | $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'][] = $annotationContainer->getId(); |
||||||
311 | if ($extConf['indexAnnotations']) { |
||||||
312 | $this->hasFulltext = true; |
||||||
313 | $this->hasFulltextSet = true; |
||||||
314 | } |
||||||
315 | } |
||||||
316 | } |
||||||
317 | |||||||
318 | $this->setFileUseFulltext($elements[$canvasOrder], $canvas); |
||||||
319 | |||||||
320 | if (!empty($fileUses)) { |
||||||
321 | $image = $canvas->getImageAnnotations()[0]; |
||||||
322 | foreach ($fileUses as $fileUse) { |
||||||
323 | if ($image->getBody() !== null && $image->getBody() instanceof ContentResourceInterface) { |
||||||
324 | $this->physicalStructureInfo[$elements[$canvasOrder]]['files'][$fileUse] = $image->getBody()->getId(); |
||||||
325 | } |
||||||
326 | } |
||||||
327 | } |
||||||
328 | if (!empty($thumbnailUrl)) { |
||||||
329 | $this->physicalStructureInfo[$elements[$canvasOrder]]['files'][$fileUseThumbs] = $thumbnailUrl; |
||||||
330 | } |
||||||
331 | |||||||
332 | $this->setFileUseDownload($elements[$canvasOrder], $canvas); |
||||||
333 | } |
||||||
334 | $this->numPages = $canvasOrder; |
||||||
335 | // Merge and re-index the array to get nice numeric indexes. |
||||||
336 | array_unshift($elements, $iiifId); |
||||||
337 | $this->physicalStructure = $elements; |
||||||
338 | } |
||||||
339 | $this->physicalStructureLoaded = true; |
||||||
340 | } |
||||||
341 | return $this->physicalStructure; |
||||||
342 | } |
||||||
343 | |||||||
344 | /** |
||||||
345 | * @see AbstractDocument::getDownloadLocation() |
||||||
346 | */ |
||||||
347 | public function getDownloadLocation(string $id): string |
||||||
348 | { |
||||||
349 | $fileLocation = $this->getFileLocation($id); |
||||||
350 | $resource = $this->iiif->getContainedResourceById($fileLocation); |
||||||
351 | if ($resource instanceof AbstractImageService) { |
||||||
352 | return $resource->getImageUrl(); |
||||||
353 | } |
||||||
354 | return $fileLocation; |
||||||
355 | } |
||||||
356 | |||||||
357 | /** |
||||||
358 | * @see AbstractDocument::getFileInfo() |
||||||
359 | */ |
||||||
360 | public function getFileInfo($id): ?array |
||||||
361 | { |
||||||
362 | if (empty($this->fileInfos[$id]['location'])) { |
||||||
363 | $this->fileInfos[$id]['location'] = $this->getFileLocation($id); |
||||||
364 | } |
||||||
365 | |||||||
366 | if (empty($this->fileInfos[$id]['mimeType'])) { |
||||||
367 | $this->fileInfos[$id]['mimeType'] = $this->getFileMimeType($id); |
||||||
368 | } |
||||||
369 | |||||||
370 | return $this->fileInfos[$id]; |
||||||
371 | } |
||||||
372 | |||||||
373 | /** |
||||||
374 | * @see AbstractDocument::getFileLocation() |
||||||
375 | */ |
||||||
376 | public function getFileLocation(string $id): string |
||||||
377 | { |
||||||
378 | if ($id == null) { |
||||||
379 | return ''; |
||||||
380 | } |
||||||
381 | $resource = $this->iiif->getContainedResourceById($id); |
||||||
382 | if (isset($resource)) { |
||||||
383 | if ($resource instanceof CanvasInterface) { |
||||||
384 | // TODO: Cannot call method getSingleService() on array<Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationInterface>. |
||||||
385 | // @phpstan-ignore-next-line |
||||||
386 | return (!empty($resource->getImageAnnotations()) && $resource->getImageAnnotations()->getSingleService() != null) ? $resource->getImageAnnotations()[0]->getSingleService()->getId() : $id; |
||||||
387 | } elseif ($resource instanceof ContentResourceInterface) { |
||||||
388 | return $resource->getSingleService() instanceof Service ? $resource->getSingleService()->getId() : $id; |
||||||
0 ignored issues
–
show
|
|||||||
389 | } elseif ($resource instanceof AbstractImageService) { |
||||||
390 | return $resource->getId(); |
||||||
391 | } elseif ($resource instanceof AnnotationContainerInterface) { |
||||||
392 | return $id; |
||||||
393 | } |
||||||
394 | } |
||||||
395 | return $id; |
||||||
396 | } |
||||||
397 | |||||||
398 | /** |
||||||
399 | * @see AbstractDocument::getFileMimeType() |
||||||
400 | */ |
||||||
401 | public function getFileMimeType(string $id): string |
||||||
402 | { |
||||||
403 | $fileResource = $this->iiif->getContainedResourceById($id); |
||||||
404 | if ($fileResource instanceof CanvasInterface) { |
||||||
405 | $format = "application/vnd.kitodo.iiif"; |
||||||
406 | } elseif ($fileResource instanceof AnnotationInterface) { |
||||||
407 | $format = "application/vnd.kitodo.iiif"; |
||||||
408 | } elseif ($fileResource instanceof ContentResourceInterface) { |
||||||
409 | if ($fileResource->isText() || $fileResource->isImage() && ($fileResource->getSingleService() == null || !($fileResource->getSingleService() instanceof AbstractImageService))) { |
||||||
410 | // Support static images without an image service |
||||||
411 | return $fileResource->getFormat(); |
||||||
412 | } |
||||||
413 | $format = "application/vnd.kitodo.iiif"; |
||||||
414 | } elseif ($fileResource instanceof AbstractImageService) { |
||||||
415 | $format = "application/vnd.kitodo.iiif"; |
||||||
416 | } else { |
||||||
417 | // Assumptions: this can only be the thumbnail and the thumbnail is a jpeg - TODO determine mimetype |
||||||
418 | $format = "image/jpeg"; |
||||||
419 | } |
||||||
420 | return $format; |
||||||
421 | } |
||||||
422 | |||||||
423 | /** |
||||||
424 | * @see AbstractDocument::getLogicalStructure() |
||||||
425 | */ |
||||||
426 | public function getLogicalStructure(string $id, bool $recursive = false): array |
||||||
427 | { |
||||||
428 | $details = []; |
||||||
429 | if (!$recursive && !empty($this->logicalUnits[$id])) { |
||||||
430 | return $this->logicalUnits[$id]; |
||||||
431 | } elseif (!empty($id)) { |
||||||
432 | $logUnits[] = $this->iiif->getContainedResourceById($id); |
||||||
433 | } else { |
||||||
434 | $logUnits[] = $this->iiif; |
||||||
435 | } |
||||||
436 | // TODO: Variable $logUnits in empty() always exists and is not falsy. |
||||||
437 | // @phpstan-ignore-next-line |
||||||
438 | if (!empty($logUnits)) { |
||||||
439 | if (!$recursive) { |
||||||
440 | $details = $this->getLogicalStructureInfo($logUnits[0]); |
||||||
441 | } else { |
||||||
442 | // cache the ranges - they might occur multiple times in the structures "tree" - with full data as well as referenced as id |
||||||
443 | $processedStructures = []; |
||||||
444 | foreach ($logUnits as $logUnit) { |
||||||
445 | if (array_search($logUnit->getId(), $processedStructures) == false) { |
||||||
0 ignored issues
–
show
|
|||||||
446 | $this->tableOfContents[] = $this->getLogicalStructureInfo($logUnit, true, $processedStructures); |
||||||
447 | } |
||||||
448 | } |
||||||
449 | } |
||||||
450 | } |
||||||
451 | return $details; |
||||||
452 | } |
||||||
453 | |||||||
454 | /** |
||||||
455 | * Get the details about a IIIF resource (manifest or range) in the logical structure |
||||||
456 | * |
||||||
457 | * @access protected |
||||||
458 | * |
||||||
459 | * @param IiifResourceInterface $resource IIIF resource, either a manifest or range. |
||||||
460 | * @param bool $recursive Whether to include the child elements |
||||||
461 | * @param array $processedStructures IIIF resources that already have been processed |
||||||
462 | * |
||||||
463 | * @return array Logical structure array |
||||||
464 | */ |
||||||
465 | protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool $recursive = false, array &$processedStructures = []): array |
||||||
466 | { |
||||||
467 | $details = []; |
||||||
468 | $details['id'] = $resource->getId(); |
||||||
469 | $details['dmdId'] = ''; |
||||||
470 | $details['label'] = $resource->getLabelForDisplay() ?? ''; |
||||||
471 | $details['orderlabel'] = $resource->getLabelForDisplay() ?? ''; |
||||||
472 | $details['contentIds'] = ''; |
||||||
473 | $details['volume'] = ''; |
||||||
474 | $details['pagination'] = ''; |
||||||
475 | $cPid = ($this->cPid ? $this->cPid : $this->pid); |
||||||
476 | if ($details['id'] == $this->magicGetToplevelId()) { |
||||||
477 | $metadata = $this->getMetadata($details['id'], $cPid); |
||||||
478 | if (!empty($metadata['type'][0])) { |
||||||
479 | $details['type'] = $metadata['type'][0]; |
||||||
480 | } |
||||||
481 | } |
||||||
482 | $details['thumbnailId'] = $resource->getThumbnailUrl(); |
||||||
483 | $details['points'] = ''; |
||||||
484 | // Load structural mapping |
||||||
485 | $this->magicGetSmLinks(); |
||||||
486 | // Load physical structure. |
||||||
487 | $this->magicGetPhysicalStructure(); |
||||||
488 | |||||||
489 | if ($resource instanceof ManifestInterface || $resource instanceof RangeInterface) { |
||||||
490 | $startCanvas = $resource->getStartCanvasOrFirstCanvas(); |
||||||
491 | } |
||||||
492 | if (isset($startCanvas)) { |
||||||
493 | $details['pagination'] = $startCanvas->getLabel(); |
||||||
494 | $startCanvasIndex = array_search($startCanvas, $this->iiif->getDefaultCanvases()); |
||||||
495 | if ($startCanvasIndex !== false) { |
||||||
496 | $details['points'] = $startCanvasIndex + 1; |
||||||
497 | } |
||||||
498 | } |
||||||
499 | $useGroups = $this->getUseGroups('fileGrpImages'); |
||||||
500 | if (is_string($useGroups)) { |
||||||
501 | $useGroups = [$useGroups]; |
||||||
0 ignored issues
–
show
|
|||||||
502 | } |
||||||
503 | // Keep for later usage. |
||||||
504 | $this->logicalUnits[$details['id']] = $details; |
||||||
505 | // Walk the structure recursively? And are there any children of the current element? |
||||||
506 | if ($recursive) { |
||||||
507 | $processedStructures[] = $resource->getId(); |
||||||
508 | $details['children'] = []; |
||||||
509 | if ($resource instanceof ManifestInterface && $resource->getRootRanges() != null) { |
||||||
510 | $rangesToAdd = []; |
||||||
511 | $rootRanges = []; |
||||||
512 | if (count($this->iiif->getRootRanges()) == 1 && $this->iiif->getRootRanges()[0]->isTopRange()) { |
||||||
513 | $rangesToAdd = $this->iiif->getRootRanges()[0]->getMemberRangesAndRanges(); |
||||||
0 ignored issues
–
show
The method
getMemberRangesAndRanges() does not exist on Ubl\Iiif\Presentation\Co...esources\RangeInterface . It seems like you code against a sub-type of Ubl\Iiif\Presentation\Co...esources\RangeInterface such as Ubl\Iiif\Presentation\V2\Model\Resources\Range2 .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
514 | } else { |
||||||
515 | $rangesToAdd = $this->iiif->getRootRanges(); |
||||||
516 | } |
||||||
517 | foreach ($rangesToAdd as $range) { |
||||||
518 | $rootRanges[] = $range; |
||||||
519 | } |
||||||
520 | foreach ($rootRanges as $range) { |
||||||
521 | if ((array_search($range->getId(), $processedStructures) == false)) { |
||||||
0 ignored issues
–
show
|
|||||||
522 | $details['children'][] = $this->getLogicalStructureInfo($range, true, $processedStructures); |
||||||
523 | } |
||||||
524 | } |
||||||
525 | } elseif ($resource instanceof RangeInterface) { |
||||||
526 | if (!empty($resource->getAllRanges())) { |
||||||
527 | foreach ($resource->getAllRanges() as $range) { |
||||||
528 | if ((array_search($range->getId(), $processedStructures) == false)) { |
||||||
0 ignored issues
–
show
|
|||||||
529 | $details['children'][] = $this->getLogicalStructureInfo($range, true, $processedStructures); |
||||||
530 | } |
||||||
531 | } |
||||||
532 | } |
||||||
533 | } |
||||||
534 | } |
||||||
535 | return $details; |
||||||
536 | } |
||||||
537 | |||||||
538 | /** |
||||||
539 | * Returns metadata for IIIF resources with the ID $id in there original form in |
||||||
540 | * the manifest, but prepared for display to the user. |
||||||
541 | * |
||||||
542 | * @access public |
||||||
543 | * |
||||||
544 | * @param string $id the ID of the IIIF resource |
||||||
545 | * @param bool $withDescription add description / summary to the return value |
||||||
546 | * @param bool $withRights add attribution and license / rights and requiredStatement to the return value |
||||||
547 | * @param bool $withRelated add related links / homepage to the return value |
||||||
548 | * |
||||||
549 | * @return array |
||||||
550 | * |
||||||
551 | * @todo This method is still in experimental; the method signature may change. |
||||||
552 | */ |
||||||
553 | public function getManifestMetadata(string $id, bool $withDescription = true, bool $withRights = true, bool $withRelated = true): array |
||||||
554 | { |
||||||
555 | if (!empty($this->originalMetadataArray[$id])) { |
||||||
556 | return $this->originalMetadataArray[$id]; |
||||||
557 | } |
||||||
558 | $iiifResource = $this->iiif->getContainedResourceById($id); |
||||||
559 | $result = []; |
||||||
560 | if ($iiifResource != null) { |
||||||
561 | if (!empty($iiifResource->getLabel())) { |
||||||
562 | $result['label'] = $iiifResource->getLabel(); |
||||||
563 | } |
||||||
564 | if (!empty($iiifResource->getMetadata())) { |
||||||
565 | $result['metadata'] = []; |
||||||
566 | foreach ($iiifResource->getMetadataForDisplay() as $metadata) { |
||||||
567 | $result['metadata'][$metadata['label']] = $metadata['value']; |
||||||
568 | } |
||||||
569 | } |
||||||
570 | if ($withDescription && !empty($iiifResource->getSummary())) { |
||||||
571 | $result["description"] = $iiifResource->getSummaryForDisplay(); |
||||||
572 | } |
||||||
573 | if ($withRights) { |
||||||
574 | if (!empty($iiifResource->getRights())) { |
||||||
575 | $result["rights"] = $iiifResource->getRights(); |
||||||
576 | } |
||||||
577 | if (!empty($iiifResource->getRequiredStatement())) { |
||||||
578 | $result["requiredStatement"] = $iiifResource->getRequiredStatementForDisplay(); |
||||||
579 | } |
||||||
580 | } |
||||||
581 | if ($withRelated && !empty($iiifResource->getWeblinksForDisplay())) { |
||||||
582 | $result["weblinks"] = []; |
||||||
583 | foreach ($iiifResource->getWeblinksForDisplay() as $link) { |
||||||
584 | $key = array_key_exists("label", $link) ? $link["label"] : $link["@id"]; |
||||||
585 | $result["weblinks"][$key] = $link["@id"]; |
||||||
586 | } |
||||||
587 | } |
||||||
588 | } |
||||||
589 | return $result; |
||||||
590 | } |
||||||
591 | |||||||
592 | /** |
||||||
593 | * @see AbstractDocument::getMetadata() |
||||||
594 | */ |
||||||
595 | public function getMetadata(string $id, int $cPid = 0): array |
||||||
596 | { |
||||||
597 | if (!empty($this->metadataArray[$id]) && $this->metadataArray[0] == $cPid) { |
||||||
598 | return $this->metadataArray[$id]; |
||||||
599 | } |
||||||
600 | |||||||
601 | $metadata = $this->initializeMetadata('IIIF'); |
||||||
602 | |||||||
603 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
||||||
604 | ->getQueryBuilderForTable('tx_dlf_metadata'); |
||||||
605 | // Get hidden records, too. |
||||||
606 | $queryBuilder |
||||||
607 | ->getRestrictions() |
||||||
608 | ->removeByType(HiddenRestriction::class); |
||||||
609 | $result = $queryBuilder |
||||||
610 | ->select( |
||||||
611 | 'tx_dlf_metadata.index_name AS index_name', |
||||||
612 | 'tx_dlf_metadataformat.xpath AS xpath', |
||||||
613 | 'tx_dlf_metadataformat.xpath_sorting AS xpath_sorting', |
||||||
614 | 'tx_dlf_metadata.is_sortable AS is_sortable', |
||||||
615 | 'tx_dlf_metadata.default_value AS default_value', |
||||||
616 | 'tx_dlf_metadata.format AS format' |
||||||
617 | ) |
||||||
618 | ->from('tx_dlf_metadata') |
||||||
619 | ->from('tx_dlf_metadataformat') |
||||||
620 | ->from('tx_dlf_formats') |
||||||
621 | ->where( |
||||||
622 | $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $cPid), |
||||||
623 | $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $cPid), |
||||||
624 | $queryBuilder->expr()->orX( |
||||||
625 | $queryBuilder->expr()->andX( |
||||||
626 | $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'), |
||||||
627 | $queryBuilder->expr()->eq('tx_dlf_metadataformat.encoded', 'tx_dlf_formats.uid'), |
||||||
628 | $queryBuilder->expr()->eq('tx_dlf_formats.type', $queryBuilder->createNamedParameter($this->getIiifVersion())) |
||||||
629 | ), |
||||||
630 | $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0) |
||||||
631 | ) |
||||||
632 | ) |
||||||
633 | ->execute(); |
||||||
634 | $iiifResource = $this->iiif->getContainedResourceById($id); |
||||||
635 | while ($resArray = $result->fetchAssociative()) { |
||||||
636 | // Set metadata field's value(s). |
||||||
637 | if ($resArray['format'] > 0 && !empty($resArray['xpath'])) { |
||||||
638 | $values = $iiifResource->jsonPath($resArray['xpath']); |
||||||
639 | if (is_string($values)) { |
||||||
640 | $metadata[$resArray['index_name']] = [trim((string) $values)]; |
||||||
641 | } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) { |
||||||
642 | $metadata[$resArray['index_name']] = []; |
||||||
643 | foreach ($values->data() as $value) { |
||||||
644 | $metadata[$resArray['index_name']][] = trim((string) $value); |
||||||
645 | } |
||||||
646 | } |
||||||
647 | } |
||||||
648 | // Set default value if applicable. |
||||||
649 | if (empty($metadata[$resArray['index_name']][0]) && strlen($resArray['default_value']) > 0) { |
||||||
650 | $metadata[$resArray['index_name']] = [$resArray['default_value']]; |
||||||
651 | } |
||||||
652 | // Set sorting value if applicable. |
||||||
653 | if (!empty($metadata[$resArray['index_name']]) && $resArray['is_sortable']) { |
||||||
654 | if ($resArray['format'] > 0 && !empty($resArray['xpath_sorting'])) { |
||||||
655 | $values = $iiifResource->jsonPath($resArray['xpath_sorting']); |
||||||
656 | if (is_string($values)) { |
||||||
657 | $metadata[$resArray['index_name'] . '_sorting'][0] = [trim((string) $values)]; |
||||||
658 | } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) { |
||||||
659 | $metadata[$resArray['index_name']] = []; |
||||||
660 | foreach ($values->data() as $value) { |
||||||
661 | $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $value); |
||||||
662 | } |
||||||
663 | } |
||||||
664 | } |
||||||
665 | if (empty($metadata[$resArray['index_name'] . '_sorting'][0])) { |
||||||
666 | $metadata[$resArray['index_name'] . '_sorting'][0] = $metadata[$resArray['index_name']][0]; |
||||||
667 | } |
||||||
668 | } |
||||||
669 | } |
||||||
670 | // Set date to empty string if not present. |
||||||
671 | if (empty($metadata['date'][0])) { |
||||||
672 | $metadata['date'][0] = ''; |
||||||
673 | } |
||||||
674 | return $metadata; |
||||||
675 | } |
||||||
676 | |||||||
677 | /** |
||||||
678 | * @see AbstractDocument::magicGetSmLinks() |
||||||
679 | */ |
||||||
680 | protected function magicGetSmLinks(): array |
||||||
681 | { |
||||||
682 | if (!$this->smLinksLoaded && isset($this->iiif) && $this->iiif instanceof ManifestInterface) { |
||||||
683 | if (!empty($this->iiif->getDefaultCanvases())) { |
||||||
684 | foreach ($this->iiif->getDefaultCanvases() as $canvas) { |
||||||
685 | $this->smLinkCanvasToResource($canvas, $this->iiif); |
||||||
686 | } |
||||||
687 | } |
||||||
688 | if (!empty($this->iiif->getStructures())) { |
||||||
689 | foreach ($this->iiif->getStructures() as $range) { |
||||||
690 | $this->smLinkRangeCanvasesRecursively($range); |
||||||
691 | } |
||||||
692 | } |
||||||
693 | $this->smLinksLoaded = true; |
||||||
694 | } |
||||||
695 | return $this->smLinks; |
||||||
696 | } |
||||||
697 | |||||||
698 | /** |
||||||
699 | * Construct a link between a range and it's sub ranges and all contained canvases. |
||||||
700 | * |
||||||
701 | * @access private |
||||||
702 | * |
||||||
703 | * @param RangeInterface $range Current range whose canvases shall be linked |
||||||
704 | * |
||||||
705 | * @return void |
||||||
706 | */ |
||||||
707 | private function smLinkRangeCanvasesRecursively(RangeInterface $range): void |
||||||
708 | { |
||||||
709 | // map range's canvases including all child ranges' canvases |
||||||
710 | if (!$range->isTopRange()) { |
||||||
711 | foreach ($range->getAllCanvasesRecursively() as $canvas) { |
||||||
712 | $this->smLinkCanvasToResource($canvas, $range); |
||||||
713 | } |
||||||
714 | } |
||||||
715 | // recursive call for all ranges |
||||||
716 | if (!empty($range->getAllRanges())) { |
||||||
717 | foreach ($range->getAllRanges() as $childRange) { |
||||||
718 | $this->smLinkRangeCanvasesRecursively($childRange); |
||||||
719 | } |
||||||
720 | } |
||||||
721 | } |
||||||
722 | |||||||
723 | /** |
||||||
724 | * Link a single canvas to a containing range |
||||||
725 | * |
||||||
726 | * @access private |
||||||
727 | * |
||||||
728 | * @param CanvasInterface $canvas |
||||||
729 | * @param IiifResourceInterface $resource |
||||||
730 | * |
||||||
731 | * @return void |
||||||
732 | */ |
||||||
733 | private function smLinkCanvasToResource(CanvasInterface $canvas, IiifResourceInterface $resource): void |
||||||
734 | { |
||||||
735 | $this->smLinks['l2p'][$resource->getId()][] = $canvas->getId(); |
||||||
736 | if (!is_array($this->smLinks['p2l'][$canvas->getId()]) || !in_array($resource->getId(), $this->smLinks['p2l'][$canvas->getId()])) { |
||||||
737 | $this->smLinks['p2l'][$canvas->getId()][] = $resource->getId(); |
||||||
738 | } |
||||||
739 | } |
||||||
740 | |||||||
741 | /** |
||||||
742 | * @see AbstractDocument::getFullText() |
||||||
743 | */ |
||||||
744 | //TODO: rewrite it to get full OCR |
||||||
745 | public function getFullText(string $id): string |
||||||
746 | { |
||||||
747 | $rawText = ''; |
||||||
748 | // Get text from raw text array if available. |
||||||
749 | if (!empty($this->rawTextArray[$id])) { |
||||||
750 | return $this->rawTextArray[$id]; |
||||||
751 | } |
||||||
752 | $this->ensureHasFulltextIsSet(); |
||||||
753 | if ($this->hasFulltext) { |
||||||
754 | // Load physical structure ... |
||||||
755 | $this->magicGetPhysicalStructure(); |
||||||
756 | // ... and extension configuration. |
||||||
757 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); |
||||||
758 | $fileGrpsFulltext = GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']); |
||||||
759 | if (!empty($this->physicalStructureInfo[$id])) { |
||||||
760 | while ($fileGrpFulltext = array_shift($fileGrpsFulltext)) { |
||||||
761 | if (!empty($this->physicalStructureInfo[$id]['files'][$fileGrpFulltext])) { |
||||||
762 | $rawText = parent::getFullTextFromXml($id); |
||||||
763 | break; |
||||||
764 | } |
||||||
765 | } |
||||||
766 | if ($extConf['indexAnnotations'] == 1) { |
||||||
767 | $iiifResource = $this->iiif->getContainedResourceById($id); |
||||||
768 | // Get annotation containers |
||||||
769 | $annotationContainerIds = $this->physicalStructureInfo[$id]['annotationContainers']; |
||||||
770 | if (!empty($annotationContainerIds)) { |
||||||
771 | $annotationTexts = $this->getAnnotationTexts($annotationContainerIds, $iiifResource->getId()); |
||||||
772 | $rawText .= implode(' ', $annotationTexts); |
||||||
773 | } |
||||||
774 | } |
||||||
775 | } else { |
||||||
776 | $this->logger->warning('Invalid structure resource @id "' . $id . '"'); |
||||||
777 | return $rawText; |
||||||
778 | } |
||||||
779 | $this->rawTextArray[$id] = $rawText; |
||||||
780 | } |
||||||
781 | return $rawText; |
||||||
782 | } |
||||||
783 | |||||||
784 | /** |
||||||
785 | * Returns the underlying IiifResourceInterface. |
||||||
786 | * |
||||||
787 | * @access public |
||||||
788 | * |
||||||
789 | * @return IiifResourceInterface |
||||||
790 | */ |
||||||
791 | public function getIiif(): IiifResourceInterface |
||||||
792 | { |
||||||
793 | return $this->iiif; |
||||||
794 | } |
||||||
795 | |||||||
796 | /** |
||||||
797 | * @see AbstractDocument::init() |
||||||
798 | */ |
||||||
799 | protected function init(string $location, array $settings = []): void |
||||||
800 | { |
||||||
801 | $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class); |
||||||
802 | } |
||||||
803 | |||||||
804 | /** |
||||||
805 | * @see AbstractDocument::loadLocation() |
||||||
806 | */ |
||||||
807 | protected function loadLocation(string $location): bool |
||||||
808 | { |
||||||
809 | $fileResource = GeneralUtility::getUrl($location); |
||||||
810 | if ($fileResource !== false) { |
||||||
811 | $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); |
||||||
812 | IiifHelper::setUrlReader(IiifUrlReader::getInstance()); |
||||||
813 | IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']); |
||||||
814 | IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']); |
||||||
815 | $resource = IiifHelper::loadIiifResource($fileResource); |
||||||
816 | if ($resource instanceof ManifestInterface) { |
||||||
817 | $this->iiif = $resource; |
||||||
818 | return true; |
||||||
819 | } |
||||||
820 | } |
||||||
821 | $this->logger->error('Could not load IIIF manifest from "' . $location . '"'); |
||||||
822 | return false; |
||||||
823 | } |
||||||
824 | |||||||
825 | /** |
||||||
826 | * @see AbstractDocument::prepareMetadataArray() |
||||||
827 | */ |
||||||
828 | protected function prepareMetadataArray(int $cPid): void |
||||||
829 | { |
||||||
830 | $id = $this->iiif->getId(); |
||||||
831 | $this->metadataArray[(string) $id] = $this->getMetadata((string) $id, $cPid); |
||||||
832 | } |
||||||
833 | |||||||
834 | /** |
||||||
835 | * @see AbstractDocument::setPreloadedDocument() |
||||||
836 | */ |
||||||
837 | protected function setPreloadedDocument($preloadedDocument): bool |
||||||
838 | { |
||||||
839 | if ($preloadedDocument instanceof ManifestInterface) { |
||||||
840 | $this->iiif = $preloadedDocument; |
||||||
841 | return true; |
||||||
842 | } |
||||||
843 | return false; |
||||||
844 | } |
||||||
845 | |||||||
846 | /** |
||||||
847 | * @see AbstractDocument::ensureHasFulltextIsSet() |
||||||
848 | */ |
||||||
849 | protected function ensureHasFulltextIsSet(): void |
||||||
850 | { |
||||||
851 | /* |
||||||
852 | * TODO Check annotations and annotation lists of canvas for ALTO documents. |
||||||
853 | * Example: |
||||||
854 | * https://digi.ub.uni-heidelberg.de/diglit/iiif/hirsch_hamburg1933_04_25/manifest.json links |
||||||
855 | * https://digi.ub.uni-heidelberg.de/diglit/iiif/hirsch_hamburg1933_04_25/list/0001.json |
||||||
856 | */ |
||||||
857 | if (!$this->hasFulltextSet && $this->iiif instanceof ManifestInterface) { |
||||||
858 | $manifest = $this->iiif; |
||||||
859 | $canvases = $manifest->getDefaultCanvases(); |
||||||
860 | foreach ($canvases as $canvas) { |
||||||
861 | if ( |
||||||
862 | !empty($canvas->getSeeAlsoUrlsForFormat("application/alto+xml")) || |
||||||
863 | !empty($canvas->getSeeAlsoUrlsForProfile("http://www.loc.gov/standards/alto/")) |
||||||
864 | ) { |
||||||
865 | $this->hasFulltextSet = true; |
||||||
866 | $this->hasFulltext = true; |
||||||
867 | return; |
||||||
868 | } |
||||||
869 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); |
||||||
870 | if ($extConf['indexAnnotations'] == 1 && !empty($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING))) { |
||||||
871 | foreach ($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING) as $annotationContainer) { |
||||||
872 | $textAnnotations = $annotationContainer->getTextAnnotations(Motivation::PAINTING); |
||||||
873 | if ($textAnnotations != null) { |
||||||
874 | foreach ($textAnnotations as $annotation) { |
||||||
875 | if ( |
||||||
876 | $annotation->getBody() != null && |
||||||
877 | $annotation->getBody()->getFormat() == "text/plain" && |
||||||
878 | $annotation->getBody()->getChars() != null |
||||||
879 | ) { |
||||||
880 | $this->hasFulltextSet = true; |
||||||
881 | $this->hasFulltext = true; |
||||||
882 | return; |
||||||
883 | } |
||||||
884 | } |
||||||
885 | } |
||||||
886 | } |
||||||
887 | } |
||||||
888 | } |
||||||
889 | $this->hasFulltextSet = true; |
||||||
890 | } |
||||||
891 | } |
||||||
892 | |||||||
893 | /** |
||||||
894 | * @see AbstractDocument::magicGetThumbnail() |
||||||
895 | */ |
||||||
896 | protected function magicGetThumbnail(bool $forceReload = false): string |
||||||
897 | { |
||||||
898 | return $this->iiif->getThumbnailUrl(); |
||||||
899 | } |
||||||
900 | |||||||
901 | /** |
||||||
902 | * @see AbstractDocument::magicGetToplevelId() |
||||||
903 | */ |
||||||
904 | protected function magicGetToplevelId(): string |
||||||
905 | { |
||||||
906 | if (empty($this->toplevelId)) { |
||||||
907 | if (isset($this->iiif)) { |
||||||
908 | $this->toplevelId = $this->iiif->getId(); |
||||||
909 | } |
||||||
910 | } |
||||||
911 | return $this->toplevelId; |
||||||
912 | } |
||||||
913 | |||||||
914 | /** |
||||||
915 | * Get annotation texts. |
||||||
916 | * |
||||||
917 | * @access private |
||||||
918 | * |
||||||
919 | * @param array $annotationContainerIds |
||||||
920 | * @param string $iiifId |
||||||
921 | * |
||||||
922 | * @return array |
||||||
923 | */ |
||||||
924 | private function getAnnotationTexts($annotationContainerIds, $iiifId): array |
||||||
925 | { |
||||||
926 | $annotationTexts = []; |
||||||
927 | foreach ($annotationContainerIds as $annotationListId) { |
||||||
928 | $annotationContainer = $this->iiif->getContainedResourceById($annotationListId); |
||||||
929 | /* @var $annotationContainer \Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface */ |
||||||
930 | foreach ($annotationContainer->getTextAnnotations(Motivation::PAINTING) as $annotation) { |
||||||
0 ignored issues
–
show
The method
getTextAnnotations() does not exist on Ubl\Iiif\Presentation\Co...s\IiifResourceInterface . It seems like you code against a sub-type of Ubl\Iiif\Presentation\Co...s\IiifResourceInterface such as Ubl\Iiif\Presentation\Co...ationContainerInterface or Ubl\Iiif\Presentation\V2...sources\AnnotationList2 or Ubl\Iiif\Presentation\V3...sources\AnnotationPage3 or Ubl\Iiif\Presentation\V1...sources\AnnotationList1 or Ubl\Iiif\Presentation\V2...sources\AnnotationList2 or Ubl\Iiif\Presentation\V3...sources\AnnotationPage3 or Ubl\Iiif\Presentation\V1...sources\AnnotationList1 .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
931 | if ( |
||||||
932 | $annotation->getTargetResourceId() == $iiifId && |
||||||
933 | $annotation->getBody() != null && $annotation->getBody()->getChars() != null |
||||||
934 | ) { |
||||||
935 | $annotationTexts[] = $annotation->getBody()->getChars(); |
||||||
936 | } |
||||||
937 | } |
||||||
938 | } |
||||||
939 | return $annotationTexts; |
||||||
940 | } |
||||||
941 | |||||||
942 | /** |
||||||
943 | * Set files used for download (PDF). |
||||||
944 | * |
||||||
945 | * @access private |
||||||
946 | * |
||||||
947 | * @param string $iiifId |
||||||
948 | * @param IiifResourceInterface $iiif |
||||||
949 | * |
||||||
950 | * @return void |
||||||
951 | */ |
||||||
952 | private function setFileUseDownload(string $iiifId, $iiif): void |
||||||
953 | { |
||||||
954 | $fileUseDownload = $this->getUseGroups('fileGrpDownload'); |
||||||
955 | |||||||
956 | if (!empty($fileUseDownload)) { |
||||||
957 | $docPdfRendering = $iiif->getRenderingUrlsForFormat('application/pdf'); |
||||||
958 | if (!empty($docPdfRendering)) { |
||||||
959 | $this->physicalStructureInfo[$iiifId]['files'][$fileUseDownload[0]] = $docPdfRendering[0]; |
||||||
960 | } |
||||||
961 | } |
||||||
962 | } |
||||||
963 | |||||||
964 | /** |
||||||
965 | * Set files used for full text (ALTO). |
||||||
966 | * |
||||||
967 | * @access private |
||||||
968 | * |
||||||
969 | * @param string $iiifId |
||||||
970 | * @param IiifResourceInterface $iiif |
||||||
971 | * |
||||||
972 | * @return void |
||||||
973 | */ |
||||||
974 | private function setFileUseFulltext(string $iiifId, $iiif): void |
||||||
975 | { |
||||||
976 | $fileUseFulltext = $this->getUseGroups('fileGrpFulltext'); |
||||||
977 | |||||||
978 | if (!empty($fileUseFulltext)) { |
||||||
979 | $alto = $iiif->getSeeAlsoUrlsForFormat('application/alto+xml'); |
||||||
980 | if (empty($alto)) { |
||||||
981 | $alto = $iiif->getSeeAlsoUrlsForProfile('http://www.loc.gov/standards/alto/', true); |
||||||
982 | } |
||||||
983 | if (!empty($alto)) { |
||||||
984 | $this->mimeTypes[$alto[0]] = 'application/alto+xml'; |
||||||
985 | $this->physicalStructureInfo[$iiifId]['files'][$fileUseFulltext[0]] = $alto[0]; |
||||||
986 | $this->hasFulltext = true; |
||||||
987 | $this->hasFulltextSet = true; |
||||||
988 | } |
||||||
989 | } |
||||||
990 | } |
||||||
991 | |||||||
992 | /** |
||||||
993 | * This magic method is executed after the object is deserialized |
||||||
994 | * @see __sleep() |
||||||
995 | * |
||||||
996 | * @access public |
||||||
997 | * |
||||||
998 | * @return void |
||||||
999 | */ |
||||||
1000 | public function __wakeup(): void |
||||||
1001 | { |
||||||
1002 | $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); |
||||||
1003 | IiifHelper::setUrlReader(IiifUrlReader::getInstance()); |
||||||
1004 | IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']); |
||||||
1005 | IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']); |
||||||
1006 | $resource = IiifHelper::loadIiifResource($this->asJson); |
||||||
1007 | if ($resource instanceof ManifestInterface) { |
||||||
1008 | $this->asJson = ''; |
||||||
1009 | $this->iiif = $resource; |
||||||
1010 | $this->init(''); |
||||||
1011 | } else { |
||||||
1012 | $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class); |
||||||
1013 | $this->logger->error('Could not load IIIF after deserialization'); |
||||||
1014 | } |
||||||
1015 | } |
||||||
1016 | |||||||
1017 | /** |
||||||
1018 | * @access public |
||||||
1019 | * |
||||||
1020 | * @return string[] |
||||||
1021 | */ |
||||||
1022 | public function __sleep(): array |
||||||
1023 | { |
||||||
1024 | // TODO implement serialization in IIIF library |
||||||
1025 | $jsonArray = $this->iiif->getOriginalJsonArray(); |
||||||
1026 | $this->asJson = json_encode($jsonArray); |
||||||
1027 | return ['uid', 'pid', 'recordId', 'parentId', 'asJson']; |
||||||
1028 | } |
||||||
1029 | } |
||||||
1030 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.