| Total Complexity | 194 | 
| Total Lines | 949 | 
| Duplicated Lines | 0 % | 
| Changes | 4 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like IiifManifest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use IiifManifest, and based on these observations, apply Extract Interface, too.
| 1 | <?php | ||
| 78 | final class IiifManifest extends AbstractDocument | ||
| 79 | { | ||
| 80 | /** | ||
| 81 | * @access protected | ||
| 82 | * @var string This holds the manifest file as string for serialization purposes | ||
| 83 | * | ||
| 84 | * @see __sleep() / __wakeup() | ||
| 85 | */ | ||
| 86 | protected string $asJson = ''; | ||
| 87 | |||
| 88 | /** | ||
| 89 | * @access protected | ||
| 90 | * @var ManifestInterface|null A PHP object representation of a IIIF manifest | ||
| 91 | */ | ||
| 92 | protected ?ManifestInterface $iiif; | ||
| 93 | |||
| 94 | /** | ||
| 95 | * @access protected | ||
| 96 | * @var string 'IIIF1', 'IIIF2' or 'IIIF3', depending on the API $this->iiif conforms to: IIIF Metadata API 1, IIIF Presentation API 2 or 3 | ||
| 97 | */ | ||
| 98 | protected string $iiifVersion; | ||
| 99 | |||
| 100 | /** | ||
| 101 | * @access protected | ||
| 102 | * @var bool Document has already been analyzed if it contains fulltext for the Solr index | ||
| 103 | */ | ||
| 104 | protected bool $hasFulltextSet = false; | ||
| 105 | |||
| 106 | /** | ||
| 107 | * @access protected | ||
| 108 | * @var array This holds the original manifest's parsed metadata array with their corresponding resource (Manifest / Sequence / Range) ID as array key | ||
| 109 | */ | ||
| 110 | protected array $originalMetadataArray = []; | ||
| 111 | |||
| 112 | /** | ||
| 113 | * @access protected | ||
| 114 | * @var array Holds the mime types of linked resources in the manifest (extracted during parsing) for later use | ||
| 115 | */ | ||
| 116 | protected array $mimeTypes = []; | ||
| 117 | |||
| 118 | /** | ||
| 119 | * @see AbstractDocument::establishRecordId() | ||
| 120 | */ | ||
| 121 | protected function establishRecordId(int $pid): void | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | /** | ||
| 174 | * @see AbstractDocument::getDocument() | ||
| 175 | */ | ||
| 176 | protected function getDocument(): IiifResourceInterface | ||
| 177 |     { | ||
| 178 | return $this->iiif; | ||
| 179 | } | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Returns a string representing the Metadata / Presentation API version which the IIIF resource | ||
| 183 | * conforms to. This is used for example to extract metadata according to configured patterns. | ||
| 184 | * | ||
| 185 | * @access public | ||
| 186 | * | ||
| 187 | * @return string 'IIIF1' if the resource is a Metadata API 1 resource, 'IIIF2' / 'IIIF3' if | ||
| 188 | * the resource is a Presentation API 2 / 3 resource | ||
| 189 | */ | ||
| 190 | public function getIiifVersion(): string | ||
| 191 |     { | ||
| 192 |         if (!isset($this->iiifVersion)) { | ||
| 193 |             if ($this->iiif instanceof AbstractIiifResource1) { | ||
| 194 | $this->iiifVersion = 'IIIF1'; | ||
| 195 |             } elseif ($this->iiif instanceof AbstractIiifResource2) { | ||
| 196 | $this->iiifVersion = 'IIIF2'; | ||
| 197 |             } elseif ($this->iiif instanceof AbstractIiifResource3) { | ||
| 198 | $this->iiifVersion = 'IIIF3'; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | return $this->iiifVersion; | ||
| 202 | } | ||
| 203 | |||
| 204 | /** | ||
| 205 | * True if getUseGroups() has been called and $this->useGrps is loaded | ||
| 206 | * | ||
| 207 | * @var bool | ||
| 208 | * @access protected | ||
| 209 | */ | ||
| 210 | protected bool $useGrpsLoaded = false; | ||
| 211 | |||
| 212 | /** | ||
| 213 | * Holds the configured useGrps as array. | ||
| 214 | * | ||
| 215 | * @var array | ||
| 216 | * @access protected | ||
| 217 | */ | ||
| 218 | protected array $useGrps = []; | ||
| 219 | |||
| 220 | /** | ||
| 221 | * IiifManifest also populates the physical structure array entries for matching | ||
| 222 | * 'fileGrp's. To do that, the configuration has to be loaded; afterwards configured | ||
| 223 | * 'fileGrp's for thumbnails, downloads, audio, fulltext and the 'fileGrp's for images | ||
| 224 | * can be requested with this method. | ||
| 225 | * | ||
| 226 | * @access protected | ||
| 227 | * | ||
| 228 | * @param string $use | ||
| 229 | * | ||
| 230 | * @return array|string | ||
| 231 | */ | ||
| 232 | protected function getUseGroups(string $use) | ||
| 233 |     { | ||
| 234 |         if (!$this->useGrpsLoaded) { | ||
| 235 | // Get configured USE attributes. | ||
| 236 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey, 'files'); | ||
| 237 |             if (!empty($extConf['fileGrpImages'])) { | ||
| 238 |                 $this->useGrps['fileGrpImages'] = GeneralUtility::trimExplode(',', $extConf['fileGrpImages']); | ||
| 239 | } | ||
| 240 |             if (!empty($extConf['fileGrpThumbs'])) { | ||
| 241 |                 $this->useGrps['fileGrpThumbs'] = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']); | ||
| 242 | } | ||
| 243 |             if (!empty($extConf['fileGrpDownload'])) { | ||
| 244 |                 $this->useGrps['fileGrpDownload'] = GeneralUtility::trimExplode(',', $extConf['fileGrpDownload']); | ||
| 245 | } | ||
| 246 |             if (!empty($extConf['fileGrpFulltext'])) { | ||
| 247 |                 $this->useGrps['fileGrpFulltext'] = GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']); | ||
| 248 | } | ||
| 249 |             if (!empty($extConf['fileGrpAudio'])) { | ||
| 250 |                 $this->useGrps['fileGrpAudio'] = GeneralUtility::trimExplode(',', $extConf['fileGrpAudio']); | ||
| 251 | } | ||
| 252 | $this->useGrpsLoaded = true; | ||
| 253 | } | ||
| 254 | return array_key_exists($use, $this->useGrps) ? $this->useGrps[$use] : []; | ||
| 255 | } | ||
| 256 | |||
| 257 | /** | ||
| 258 | * @see AbstractDocument::magicGetPhysicalStructure() | ||
| 259 | */ | ||
| 260 | protected function magicGetPhysicalStructure(): array | ||
| 261 |     { | ||
| 262 | // Is there no physical structure array yet? | ||
| 263 |         if (!$this->physicalStructureLoaded) { | ||
| 264 |             if ($this->iiif == null || !($this->iiif instanceof ManifestInterface)) { | ||
| 265 | return []; | ||
| 266 | } | ||
| 267 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey, 'iiif'); | ||
| 268 | $iiifId = $this->iiif->getId(); | ||
| 269 | $this->physicalStructureInfo[$iiifId]['id'] = $iiifId; | ||
| 270 | $this->physicalStructureInfo[$iiifId]['dmdId'] = $iiifId; | ||
| 271 | $this->physicalStructureInfo[$iiifId]['label'] = $this->iiif->getLabelForDisplay(); | ||
| 272 | $this->physicalStructureInfo[$iiifId]['orderlabel'] = $this->iiif->getLabelForDisplay(); | ||
| 273 | $this->physicalStructureInfo[$iiifId]['type'] = 'physSequence'; | ||
| 274 | $this->physicalStructureInfo[$iiifId]['contentIds'] = null; | ||
| 275 | |||
| 276 | $this->setFileUseDownload($iiifId, $this->iiif); | ||
| 277 | $this->setFileUseFulltext($iiifId, $this->iiif); | ||
| 278 | |||
| 279 |             $fileUseThumbs = $this->getUseGroups('fileGrpThumbs'); | ||
| 280 |             $fileUses = $this->getUseGroups('fileGrpImages'); | ||
| 281 | |||
| 282 |             if (!empty($this->iiif->getDefaultCanvases())) { | ||
| 283 | // canvases have not order property, but the context defines canveses as @list with a specific order, so we can provide an alternative | ||
| 284 | $elements = []; | ||
| 285 | $canvasOrder = 0; | ||
| 286 |                 foreach ($this->iiif->getDefaultCanvases() as $canvas) { | ||
| 287 | $canvasOrder++; | ||
| 288 | $thumbnailUrl = $canvas->getThumbnailUrl(); | ||
| 289 | // put thumbnails in thumbnail filegroup | ||
| 290 | if ( | ||
| 291 | !empty($thumbnailUrl) | ||
| 292 | && empty($this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]]) | ||
| 293 |                     ) { | ||
| 294 | $this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]] = $thumbnailUrl; | ||
| 295 | } | ||
| 296 | // populate structural metadata info | ||
| 297 | $elements[$canvasOrder] = $canvas->getId(); | ||
| 298 | $this->physicalStructureInfo[$elements[$canvasOrder]]['id'] = $canvas->getId(); | ||
| 299 | $this->physicalStructureInfo[$elements[$canvasOrder]]['dmdId'] = null; | ||
| 300 | $this->physicalStructureInfo[$elements[$canvasOrder]]['label'] = $canvas->getLabelForDisplay(); | ||
| 301 | $this->physicalStructureInfo[$elements[$canvasOrder]]['orderlabel'] = $canvas->getLabelForDisplay(); | ||
| 302 | // assume that a canvas always represents a page | ||
| 303 | $this->physicalStructureInfo[$elements[$canvasOrder]]['type'] = 'page'; | ||
| 304 | $this->physicalStructureInfo[$elements[$canvasOrder]]['contentIds'] = null; | ||
| 305 | $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'] = null; | ||
| 306 |                     if (!empty($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING))) { | ||
| 307 | $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'] = []; | ||
| 308 |                         foreach ($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING) as $annotationContainer) { | ||
| 309 | $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'][] = $annotationContainer->getId(); | ||
| 310 |                             if ($extConf['indexAnnotations']) { | ||
| 311 | $this->hasFulltext = true; | ||
| 312 | $this->hasFulltextSet = true; | ||
| 313 | } | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | $this->setFileUseFulltext($elements[$canvasOrder], $canvas); | ||
| 318 | |||
| 319 |                     if (!empty($fileUses)) { | ||
| 320 | $image = $canvas->getImageAnnotations()[0]; | ||
| 321 |                         foreach ($fileUses as $fileUse) { | ||
| 322 |                             if ($image->getBody() !== null && $image->getBody() instanceof ContentResourceInterface) { | ||
| 323 | $this->physicalStructureInfo[$elements[$canvasOrder]]['files'][$fileUse] = $image->getBody()->getId(); | ||
| 324 | } | ||
| 325 | } | ||
| 326 | } | ||
| 327 |                     if (!empty($thumbnailUrl)) { | ||
| 328 | $this->physicalStructureInfo[$elements[$canvasOrder]]['files'][$fileUseThumbs] = $thumbnailUrl; | ||
| 329 | } | ||
| 330 | |||
| 331 | $this->setFileUseDownload($elements[$canvasOrder], $canvas); | ||
| 332 | } | ||
| 333 | $this->numPages = $canvasOrder; | ||
| 334 | // Merge and re-index the array to get nice numeric indexes. | ||
| 335 | array_unshift($elements, $iiifId); | ||
| 336 | $this->physicalStructure = $elements; | ||
| 337 | } | ||
| 338 | $this->physicalStructureLoaded = true; | ||
| 339 | } | ||
| 340 | return $this->physicalStructure; | ||
| 341 | } | ||
| 342 | |||
| 343 | /** | ||
| 344 | * @see AbstractDocument::getDownloadLocation() | ||
| 345 | */ | ||
| 346 | public function getDownloadLocation(string $id): string | ||
| 347 |     { | ||
| 348 | $fileLocation = $this->getFileLocation($id); | ||
| 349 | $resource = $this->iiif->getContainedResourceById($fileLocation); | ||
| 350 |         if ($resource instanceof AbstractImageService) { | ||
| 351 | return $resource->getImageUrl(); | ||
| 352 | } | ||
| 353 | return $fileLocation; | ||
| 354 | } | ||
| 355 | |||
| 356 | /** | ||
| 357 | * @see AbstractDocument::getFileInfo() | ||
| 358 | */ | ||
| 359 | public function getFileInfo($id): ?array | ||
| 360 |     { | ||
| 361 |         if (empty($this->fileInfos[$id]['location'])) { | ||
| 362 | $this->fileInfos[$id]['location'] = $this->getFileLocation($id); | ||
| 363 | } | ||
| 364 | |||
| 365 |         if (empty($this->fileInfos[$id]['mimeType'])) { | ||
| 366 | $this->fileInfos[$id]['mimeType'] = $this->getFileMimeType($id); | ||
| 367 | } | ||
| 368 | |||
| 369 | return $this->fileInfos[$id] ?? null; | ||
| 370 | } | ||
| 371 | |||
| 372 | /** | ||
| 373 | * @see AbstractDocument::getFileLocation() | ||
| 374 | */ | ||
| 375 | public function getFileLocation(string $id): string | ||
| 376 |     { | ||
| 377 |         if ($id == null) { | ||
| 378 | return ''; | ||
| 379 | } | ||
| 380 | $resource = $this->iiif->getContainedResourceById($id); | ||
| 381 |         if (isset($resource)) { | ||
| 382 |             if ($resource instanceof CanvasInterface) { | ||
| 383 | // TODO: Cannot call method getSingleService() on array<Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationInterface>. | ||
| 384 | // @phpstan-ignore-next-line | ||
| 385 | return (!empty($resource->getImageAnnotations()) && $resource->getImageAnnotations()->getSingleService() != null) ? $resource->getImageAnnotations()[0]->getSingleService()->getId() : $id; | ||
| 386 |             } elseif ($resource instanceof ContentResourceInterface) { | ||
| 387 | return $resource->getSingleService() instanceof Service ? $resource->getSingleService()->getId() : $id; | ||
| 388 |             } elseif ($resource instanceof AbstractImageService) { | ||
| 389 | return $resource->getId(); | ||
| 390 |             } elseif ($resource instanceof AnnotationContainerInterface) { | ||
| 391 | return $id; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | return $id; | ||
| 395 | } | ||
| 396 | |||
| 397 | /** | ||
| 398 | * @see AbstractDocument::getFileMimeType() | ||
| 399 | */ | ||
| 400 | public function getFileMimeType(string $id): string | ||
| 420 | } | ||
| 421 | |||
| 422 | /** | ||
| 423 | * @see AbstractDocument::getLogicalStructure() | ||
| 424 | */ | ||
| 425 | public function getLogicalStructure(string $id, bool $recursive = false): array | ||
| 426 |     { | ||
| 427 | $details = []; | ||
| 428 |         if (!$recursive && !empty($this->logicalUnits[$id])) { | ||
| 429 | return $this->logicalUnits[$id]; | ||
| 430 |         } elseif (!empty($id)) { | ||
| 431 | $logUnits[] = $this->iiif->getContainedResourceById($id); | ||
| 432 |         } else { | ||
| 433 | $logUnits[] = $this->iiif; | ||
| 434 | } | ||
| 435 | // TODO: Variable $logUnits in empty() always exists and is not falsy. | ||
| 436 | // @phpstan-ignore-next-line | ||
| 437 |         if (!empty($logUnits)) { | ||
| 438 |             if (!$recursive) { | ||
| 439 | $details = $this->getLogicalStructureInfo($logUnits[0]); | ||
| 440 |             } else { | ||
| 441 | // cache the ranges - they might occur multiple times in the structures "tree" - with full data as well as referenced as id | ||
| 442 | $processedStructures = []; | ||
| 443 |                 foreach ($logUnits as $logUnit) { | ||
| 444 |                     if (array_search($logUnit->getId(), $processedStructures) == false) { | ||
| 445 | $this->tableOfContents[] = $this->getLogicalStructureInfo($logUnit, true, $processedStructures); | ||
| 446 | } | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | return $details; | ||
| 451 | } | ||
| 452 | |||
| 453 | /** | ||
| 454 | * Get the details about a IIIF resource (manifest or range) in the logical structure | ||
| 455 | * | ||
| 456 | * @access protected | ||
| 457 | * | ||
| 458 | * @param IiifResourceInterface $resource IIIF resource, either a manifest or range. | ||
| 459 | * @param bool $recursive Whether to include the child elements | ||
| 460 | * @param array $processedStructures IIIF resources that already have been processed | ||
| 461 | * | ||
| 462 | * @return array Logical structure array | ||
| 463 | */ | ||
| 464 | protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool $recursive = false, array &$processedStructures = []): array | ||
| 465 |     { | ||
| 466 | $details = []; | ||
| 467 | $details['id'] = $resource->getId(); | ||
| 468 | $details['dmdId'] = ''; | ||
| 469 | $details['label'] = $resource->getLabelForDisplay() ?? ''; | ||
| 470 | $details['orderlabel'] = $resource->getLabelForDisplay() ?? ''; | ||
| 471 | $details['contentIds'] = ''; | ||
| 472 | $details['volume'] = ''; | ||
| 473 | $details['pagination'] = ''; | ||
| 474 | $cPid = ($this->cPid ? $this->cPid : $this->pid); | ||
| 475 |         if ($details['id'] == $this->magicGetToplevelId()) { | ||
| 476 | $metadata = $this->getMetadata($details['id'], $cPid); | ||
| 477 |             if (!empty($metadata['type'][0])) { | ||
| 478 | $details['type'] = $metadata['type'][0]; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | $details['thumbnailId'] = $resource->getThumbnailUrl(); | ||
| 482 | $details['points'] = ''; | ||
| 483 | // Load structural mapping | ||
| 484 | $this->magicGetSmLinks(); | ||
| 485 | // Load physical structure. | ||
| 486 | $this->magicGetPhysicalStructure(); | ||
| 487 | |||
| 488 |         if ($resource instanceof ManifestInterface || $resource instanceof RangeInterface) { | ||
| 489 | $startCanvas = $resource->getStartCanvasOrFirstCanvas(); | ||
| 490 | } | ||
| 491 |         if (isset($startCanvas)) { | ||
| 492 | $details['pagination'] = $startCanvas->getLabel(); | ||
| 493 | $startCanvasIndex = array_search($startCanvas, $this->iiif->getDefaultCanvases()); | ||
| 494 |             if ($startCanvasIndex !== false) { | ||
| 495 | $details['points'] = $startCanvasIndex + 1; | ||
| 496 | } | ||
| 497 | } | ||
| 498 |         $useGroups = $this->getUseGroups('fileGrpImages'); | ||
| 499 |         if (is_string($useGroups)) { | ||
| 500 | $useGroups = [$useGroups]; | ||
| 501 | } | ||
| 502 | // Keep for later usage. | ||
| 503 | $this->logicalUnits[$details['id']] = $details; | ||
| 504 | // Walk the structure recursively? And are there any children of the current element? | ||
| 505 |         if ($recursive) { | ||
| 506 | $processedStructures[] = $resource->getId(); | ||
| 507 | $details['children'] = []; | ||
| 508 |             if ($resource instanceof ManifestInterface && $resource->getRootRanges() != null) { | ||
| 509 | $rangesToAdd = []; | ||
| 510 | $rootRanges = []; | ||
| 511 |                 if (count($this->iiif->getRootRanges()) == 1 && $this->iiif->getRootRanges()[0]->isTopRange()) { | ||
| 512 | $rangesToAdd = $this->iiif->getRootRanges()[0]->getMemberRangesAndRanges(); | ||
| 513 |                 } else { | ||
| 514 | $rangesToAdd = $this->iiif->getRootRanges(); | ||
| 515 | } | ||
| 516 |                 foreach ($rangesToAdd as $range) { | ||
| 517 | $rootRanges[] = $range; | ||
| 518 | } | ||
| 519 |                 foreach ($rootRanges as $range) { | ||
| 520 |                     if ((array_search($range->getId(), $processedStructures) == false)) { | ||
| 521 | $details['children'][] = $this->getLogicalStructureInfo($range, true, $processedStructures); | ||
| 522 | } | ||
| 523 | } | ||
| 524 |             } elseif ($resource instanceof RangeInterface) { | ||
| 525 |                 if (!empty($resource->getAllRanges())) { | ||
| 526 |                     foreach ($resource->getAllRanges() as $range) { | ||
| 527 |                         if ((array_search($range->getId(), $processedStructures) == false)) { | ||
| 528 | $details['children'][] = $this->getLogicalStructureInfo($range, true, $processedStructures); | ||
| 529 | } | ||
| 530 | } | ||
| 531 | } | ||
| 532 | } | ||
| 533 | } | ||
| 534 | return $details; | ||
| 535 | } | ||
| 536 | |||
| 537 | /** | ||
| 538 | * Returns metadata for IIIF resources with the ID $id in there original form in | ||
| 539 | * the manifest, but prepared for display to the user. | ||
| 540 | * | ||
| 541 | * @access public | ||
| 542 | * | ||
| 543 | * @param string $id the ID of the IIIF resource | ||
| 544 | * @param bool $withDescription add description / summary to the return value | ||
| 545 | * @param bool $withRights add attribution and license / rights and requiredStatement to the return value | ||
| 546 | * @param bool $withRelated add related links / homepage to the return value | ||
| 547 | * | ||
| 548 | * @return array | ||
| 549 | * | ||
| 550 | * @todo This method is still in experimental; the method signature may change. | ||
| 551 | */ | ||
| 552 | public function getManifestMetadata(string $id, bool $withDescription = true, bool $withRights = true, bool $withRelated = true): array | ||
| 553 |     { | ||
| 554 |         if (!empty($this->originalMetadataArray[$id])) { | ||
| 555 | return $this->originalMetadataArray[$id]; | ||
| 556 | } | ||
| 557 | $iiifResource = $this->iiif->getContainedResourceById($id); | ||
| 558 | $result = []; | ||
| 559 |         if ($iiifResource != null) { | ||
| 560 |             if (!empty($iiifResource->getLabel())) { | ||
| 561 | $result['label'] = $iiifResource->getLabel(); | ||
| 562 | } | ||
| 563 |             if (!empty($iiifResource->getMetadata())) { | ||
| 564 | $result['metadata'] = []; | ||
| 565 |                 foreach ($iiifResource->getMetadataForDisplay() as $metadata) { | ||
| 566 | $result['metadata'][$metadata['label']] = $metadata['value']; | ||
| 567 | } | ||
| 568 | } | ||
| 569 |             if ($withDescription && !empty($iiifResource->getSummary())) { | ||
| 570 | $result["description"] = $iiifResource->getSummaryForDisplay(); | ||
| 571 | } | ||
| 572 |             if ($withRights) { | ||
| 573 |                 if (!empty($iiifResource->getRights())) { | ||
| 574 | $result["rights"] = $iiifResource->getRights(); | ||
| 575 | } | ||
| 576 |                 if (!empty($iiifResource->getRequiredStatement())) { | ||
| 577 | $result["requiredStatement"] = $iiifResource->getRequiredStatementForDisplay(); | ||
| 578 | } | ||
| 579 | } | ||
| 580 |             if ($withRelated && !empty($iiifResource->getWeblinksForDisplay())) { | ||
| 581 | $result["weblinks"] = []; | ||
| 582 |                 foreach ($iiifResource->getWeblinksForDisplay() as $link) { | ||
| 583 |                     $key = array_key_exists("label", $link) ? $link["label"] : $link["@id"]; | ||
| 584 | $result["weblinks"][$key] = $link["@id"]; | ||
| 585 | } | ||
| 586 | } | ||
| 587 | } | ||
| 588 | return $result; | ||
| 589 | } | ||
| 590 | |||
| 591 | /** | ||
| 592 | * @see AbstractDocument::getMetadata() | ||
| 593 | */ | ||
| 594 | public function getMetadata(string $id, int $cPid = 0): array | ||
| 595 |     { | ||
| 596 |         if (!empty($this->metadataArray[$id]) && $this->metadataArray[0] == $cPid) { | ||
| 597 | return $this->metadataArray[$id]; | ||
| 598 | } | ||
| 599 | |||
| 600 |         $metadata = $this->initializeMetadata('IIIF'); | ||
| 601 | |||
| 602 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) | ||
| 603 |             ->getQueryBuilderForTable('tx_dlf_metadata'); | ||
| 604 | // Get hidden records, too. | ||
| 605 | $queryBuilder | ||
| 606 | ->getRestrictions() | ||
| 607 | ->removeByType(HiddenRestriction::class); | ||
| 608 | $result = $queryBuilder | ||
| 609 | ->select( | ||
| 610 | 'tx_dlf_metadata.index_name AS index_name', | ||
| 611 | 'tx_dlf_metadataformat.xpath AS xpath', | ||
| 612 | 'tx_dlf_metadataformat.xpath_sorting AS xpath_sorting', | ||
| 613 | 'tx_dlf_metadata.is_sortable AS is_sortable', | ||
| 614 | 'tx_dlf_metadata.default_value AS default_value', | ||
| 615 | 'tx_dlf_metadata.format AS format' | ||
| 616 | ) | ||
| 617 |             ->from('tx_dlf_metadata') | ||
| 618 |             ->from('tx_dlf_metadataformat') | ||
| 619 |             ->from('tx_dlf_formats') | ||
| 620 | ->where( | ||
| 621 |                 $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $cPid), | ||
| 622 |                 $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $cPid), | ||
| 623 | $queryBuilder->expr()->orX( | ||
| 624 | $queryBuilder->expr()->andX( | ||
| 625 |                         $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'), | ||
| 626 |                         $queryBuilder->expr()->eq('tx_dlf_metadataformat.encoded', 'tx_dlf_formats.uid'), | ||
| 627 |                         $queryBuilder->expr()->eq('tx_dlf_formats.type', $queryBuilder->createNamedParameter($this->getIiifVersion())) | ||
| 628 | ), | ||
| 629 |                     $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0) | ||
| 630 | ) | ||
| 631 | ) | ||
| 632 | ->execute(); | ||
| 633 | $iiifResource = $this->iiif->getContainedResourceById($id); | ||
| 634 |         while ($resArray = $result->fetchAssociative()) { | ||
| 635 | // Set metadata field's value(s). | ||
| 636 |             if ($resArray['format'] > 0 && !empty($resArray['xpath'])) { | ||
| 637 | $values = $iiifResource->jsonPath($resArray['xpath']); | ||
| 638 |                 if (is_string($values)) { | ||
| 639 | $metadata[$resArray['index_name']] = [trim((string) $values)]; | ||
| 640 |                 } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) { | ||
| 641 | $metadata[$resArray['index_name']] = []; | ||
| 642 |                     foreach ($values->data() as $value) { | ||
| 643 | $metadata[$resArray['index_name']][] = trim((string) $value); | ||
| 644 | } | ||
| 645 | } | ||
| 646 | } | ||
| 647 | // Set default value if applicable. | ||
| 648 |             if (empty($metadata[$resArray['index_name']][0]) && strlen($resArray['default_value']) > 0) { | ||
| 649 | $metadata[$resArray['index_name']] = [$resArray['default_value']]; | ||
| 650 | } | ||
| 651 | // Set sorting value if applicable. | ||
| 652 |             if (!empty($metadata[$resArray['index_name']]) && $resArray['is_sortable']) { | ||
| 653 |                 if ($resArray['format'] > 0 && !empty($resArray['xpath_sorting'])) { | ||
| 654 | $values = $iiifResource->jsonPath($resArray['xpath_sorting']); | ||
| 655 |                     if (is_string($values)) { | ||
| 656 | $metadata[$resArray['index_name'] . '_sorting'][0] = [trim((string) $values)]; | ||
| 657 |                     } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) { | ||
| 658 | $metadata[$resArray['index_name']] = []; | ||
| 659 |                         foreach ($values->data() as $value) { | ||
| 660 | $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $value); | ||
| 661 | } | ||
| 662 | } | ||
| 663 | } | ||
| 664 |                 if (empty($metadata[$resArray['index_name'] . '_sorting'][0])) { | ||
| 665 | $metadata[$resArray['index_name'] . '_sorting'][0] = $metadata[$resArray['index_name']][0]; | ||
| 666 | } | ||
| 667 | } | ||
| 668 | } | ||
| 669 | // Set date to empty string if not present. | ||
| 670 |         if (empty($metadata['date'][0])) { | ||
| 671 | $metadata['date'][0] = ''; | ||
| 672 | } | ||
| 673 | return $metadata; | ||
| 674 | } | ||
| 675 | |||
| 676 | /** | ||
| 677 | * @see AbstractDocument::magicGetSmLinks() | ||
| 678 | */ | ||
| 679 | protected function magicGetSmLinks(): array | ||
| 680 |     { | ||
| 681 |         if (!$this->smLinksLoaded && isset($this->iiif) && $this->iiif instanceof ManifestInterface) { | ||
| 682 |             if (!empty($this->iiif->getDefaultCanvases())) { | ||
| 683 |                 foreach ($this->iiif->getDefaultCanvases() as $canvas) { | ||
| 684 | $this->smLinkCanvasToResource($canvas, $this->iiif); | ||
| 685 | } | ||
| 686 | } | ||
| 687 |             if (!empty($this->iiif->getStructures())) { | ||
| 688 |                 foreach ($this->iiif->getStructures() as $range) { | ||
| 689 | $this->smLinkRangeCanvasesRecursively($range); | ||
| 690 | } | ||
| 691 | } | ||
| 692 | $this->smLinksLoaded = true; | ||
| 693 | } | ||
| 694 | return $this->smLinks; | ||
| 695 | } | ||
| 696 | |||
| 697 | /** | ||
| 698 | * Construct a link between a range and it's sub ranges and all contained canvases. | ||
| 699 | * | ||
| 700 | * @access private | ||
| 701 | * | ||
| 702 | * @param RangeInterface $range Current range whose canvases shall be linked | ||
| 703 | * | ||
| 704 | * @return void | ||
| 705 | */ | ||
| 706 | private function smLinkRangeCanvasesRecursively(RangeInterface $range): void | ||
| 707 |     { | ||
| 708 | // map range's canvases including all child ranges' canvases | ||
| 709 |         if (!$range->isTopRange()) { | ||
| 710 |             foreach ($range->getAllCanvasesRecursively() as $canvas) { | ||
| 711 | $this->smLinkCanvasToResource($canvas, $range); | ||
| 712 | } | ||
| 713 | } | ||
| 714 | // recursive call for all ranges | ||
| 715 |         if (!empty($range->getAllRanges())) { | ||
| 716 |             foreach ($range->getAllRanges() as $childRange) { | ||
| 717 | $this->smLinkRangeCanvasesRecursively($childRange); | ||
| 718 | } | ||
| 719 | } | ||
| 720 | } | ||
| 721 | |||
| 722 | /** | ||
| 723 | * Link a single canvas to a containing range | ||
| 724 | * | ||
| 725 | * @access private | ||
| 726 | * | ||
| 727 | * @param CanvasInterface $canvas | ||
| 728 | * @param IiifResourceInterface $resource | ||
| 729 | * | ||
| 730 | * @return void | ||
| 731 | */ | ||
| 732 | private function smLinkCanvasToResource(CanvasInterface $canvas, IiifResourceInterface $resource): void | ||
| 733 |     { | ||
| 734 | $this->smLinks['l2p'][$resource->getId()][] = $canvas->getId(); | ||
| 735 |         if (!is_array($this->smLinks['p2l'][$canvas->getId()]) || !in_array($resource->getId(), $this->smLinks['p2l'][$canvas->getId()])) { | ||
| 736 | $this->smLinks['p2l'][$canvas->getId()][] = $resource->getId(); | ||
| 737 | } | ||
| 738 | } | ||
| 739 | |||
| 740 | /** | ||
| 741 | * @see AbstractDocument::getFullText() | ||
| 742 | */ | ||
| 743 | //TODO: rewrite it to get full OCR | ||
| 744 | public function getFullText(string $id): string | ||
| 745 |     { | ||
| 746 | $rawText = ''; | ||
| 747 | // Get text from raw text array if available. | ||
| 748 |         if (!empty($this->rawTextArray[$id])) { | ||
| 749 | return $this->rawTextArray[$id]; | ||
| 750 | } | ||
| 751 | $this->ensureHasFulltextIsSet(); | ||
| 752 |         if ($this->hasFulltext) { | ||
| 753 | // Load physical structure ... | ||
| 754 | $this->magicGetPhysicalStructure(); | ||
| 755 | // ... and extension configuration. | ||
| 756 | $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); | ||
| 757 |             $fileGrpsFulltext = GeneralUtility::trimExplode(',', $extConf['files']['fileGrpFulltext']); | ||
| 758 |             if (!empty($this->physicalStructureInfo[$id])) { | ||
| 759 |                 while ($fileGrpFulltext = array_shift($fileGrpsFulltext)) { | ||
| 760 |                     if (!empty($this->physicalStructureInfo[$id]['files'][$fileGrpFulltext])) { | ||
| 761 | $rawText = parent::getFullTextFromXml($id); | ||
| 762 | break; | ||
| 763 | } | ||
| 764 | } | ||
| 765 |                 if ($extConf['iiif']['indexAnnotations'] == 1) { | ||
| 766 | $iiifResource = $this->iiif->getContainedResourceById($id); | ||
| 767 | // Get annotation containers | ||
| 768 | $annotationContainerIds = $this->physicalStructureInfo[$id]['annotationContainers']; | ||
| 769 |                     if (!empty($annotationContainerIds)) { | ||
| 770 | $annotationTexts = $this->getAnnotationTexts($annotationContainerIds, $iiifResource->getId()); | ||
| 771 |                         $rawText .= implode(' ', $annotationTexts); | ||
| 772 | } | ||
| 773 | } | ||
| 774 |             } else { | ||
| 775 |                 $this->logger->warning('Invalid structure resource @id "' . $id . '"'); | ||
| 776 | return $rawText; | ||
| 777 | } | ||
| 778 | $this->rawTextArray[$id] = $rawText; | ||
| 779 | } | ||
| 780 | return $rawText; | ||
| 781 | } | ||
| 782 | |||
| 783 | /** | ||
| 784 | * Returns the underlying IiifResourceInterface. | ||
| 785 | * | ||
| 786 | * @access public | ||
| 787 | * | ||
| 788 | * @return IiifResourceInterface | ||
| 789 | */ | ||
| 790 | public function getIiif(): IiifResourceInterface | ||
| 791 |     { | ||
| 792 | return $this->iiif; | ||
| 793 | } | ||
| 794 | |||
| 795 | /** | ||
| 796 | * @see AbstractDocument::init() | ||
| 797 | */ | ||
| 798 | protected function init(string $location, array $settings = []): void | ||
| 799 |     { | ||
| 800 | $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class); | ||
| 801 | } | ||
| 802 | |||
| 803 | /** | ||
| 804 | * @see AbstractDocument::loadLocation() | ||
| 805 | */ | ||
| 806 | protected function loadLocation(string $location): bool | ||
| 807 |     { | ||
| 808 | $fileResource = GeneralUtility::getUrl($location); | ||
| 809 |         if ($fileResource !== false) { | ||
| 810 | $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey, 'iiif'); | ||
| 811 | IiifHelper::setUrlReader(IiifUrlReader::getInstance()); | ||
| 812 | IiifHelper::setMaxThumbnailHeight($conf['thumbnailHeight']); | ||
| 813 | IiifHelper::setMaxThumbnailWidth($conf['thumbnailWidth']); | ||
| 814 | $resource = IiifHelper::loadIiifResource($fileResource); | ||
| 815 |             if ($resource instanceof ManifestInterface) { | ||
| 816 | $this->iiif = $resource; | ||
| 817 | return true; | ||
| 818 | } | ||
| 819 | } | ||
| 820 |         $this->logger->error('Could not load IIIF manifest from "' . $location . '"'); | ||
| 821 | return false; | ||
| 822 | } | ||
| 823 | |||
| 824 | /** | ||
| 825 | * @see AbstractDocument::prepareMetadataArray() | ||
| 826 | */ | ||
| 827 | protected function prepareMetadataArray(int $cPid): void | ||
| 831 | } | ||
| 832 | |||
| 833 | /** | ||
| 834 | * @see AbstractDocument::setPreloadedDocument() | ||
| 835 | */ | ||
| 836 | protected function setPreloadedDocument($preloadedDocument): bool | ||
| 837 |     { | ||
| 838 |         if ($preloadedDocument instanceof ManifestInterface) { | ||
| 839 | $this->iiif = $preloadedDocument; | ||
| 840 | return true; | ||
| 841 | } | ||
| 842 | return false; | ||
| 843 | } | ||
| 844 | |||
| 845 | /** | ||
| 846 | * @see AbstractDocument::ensureHasFulltextIsSet() | ||
| 847 | */ | ||
| 848 | protected function ensureHasFulltextIsSet(): void | ||
| 889 | } | ||
| 890 | } | ||
| 891 | |||
| 892 | /** | ||
| 893 | * @see AbstractDocument::magicGetThumbnail() | ||
| 894 | */ | ||
| 895 | protected function magicGetThumbnail(bool $forceReload = false): string | ||
| 896 |     { | ||
| 897 | return $this->iiif->getThumbnailUrl(); | ||
| 898 | } | ||
| 899 | |||
| 900 | /** | ||
| 901 | * @see AbstractDocument::magicGetToplevelId() | ||
| 902 | */ | ||
| 903 | protected function magicGetToplevelId(): string | ||
| 904 |     { | ||
| 905 |         if (empty($this->toplevelId)) { | ||
| 906 |             if (isset($this->iiif)) { | ||
| 907 | $this->toplevelId = $this->iiif->getId(); | ||
| 908 | } | ||
| 909 | } | ||
| 910 | return $this->toplevelId; | ||
| 911 | } | ||
| 912 | |||
| 913 | /** | ||
| 914 | * Get annotation texts. | ||
| 915 | * | ||
| 916 | * @access private | ||
| 917 | * | ||
| 918 | * @param array $annotationContainerIds | ||
| 919 | * @param string $iiifId | ||
| 920 | * | ||
| 921 | * @return array | ||
| 922 | */ | ||
| 923 | private function getAnnotationTexts($annotationContainerIds, $iiifId): array | ||
| 924 |     { | ||
| 925 | $annotationTexts = []; | ||
| 926 |         foreach ($annotationContainerIds as $annotationListId) { | ||
| 927 | $annotationContainer = $this->iiif->getContainedResourceById($annotationListId); | ||
| 928 | /* @var $annotationContainer \Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface */ | ||
| 929 |             foreach ($annotationContainer->getTextAnnotations(Motivation::PAINTING) as $annotation) { | ||
| 930 | if ( | ||
| 931 | $annotation->getTargetResourceId() == $iiifId && | ||
| 932 | $annotation->getBody() != null && $annotation->getBody()->getChars() != null | ||
| 933 |                 ) { | ||
| 934 | $annotationTexts[] = $annotation->getBody()->getChars(); | ||
| 935 | } | ||
| 936 | } | ||
| 937 | } | ||
| 938 | return $annotationTexts; | ||
| 939 | } | ||
| 940 | |||
| 941 | /** | ||
| 942 | * Set files used for download (PDF). | ||
| 943 | * | ||
| 944 | * @access private | ||
| 945 | * | ||
| 946 | * @param string $iiifId | ||
| 947 | * @param IiifResourceInterface $iiif | ||
| 948 | * | ||
| 949 | * @return void | ||
| 950 | */ | ||
| 951 | private function setFileUseDownload(string $iiifId, $iiif): void | ||
| 952 |     { | ||
| 953 |         $fileUseDownload = $this->getUseGroups('fileGrpDownload'); | ||
| 954 | |||
| 955 |         if (!empty($fileUseDownload)) { | ||
| 956 |             $docPdfRendering = $iiif->getRenderingUrlsForFormat('application/pdf'); | ||
| 957 |             if (!empty($docPdfRendering)) { | ||
| 958 | $this->physicalStructureInfo[$iiifId]['files'][$fileUseDownload[0]] = $docPdfRendering[0]; | ||
| 959 | } | ||
| 960 | } | ||
| 961 | } | ||
| 962 | |||
| 963 | /** | ||
| 964 | * Set files used for full text (ALTO). | ||
| 965 | * | ||
| 966 | * @access private | ||
| 967 | * | ||
| 968 | * @param string $iiifId | ||
| 969 | * @param IiifResourceInterface $iiif | ||
| 970 | * | ||
| 971 | * @return void | ||
| 972 | */ | ||
| 973 | private function setFileUseFulltext(string $iiifId, $iiif): void | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | |||
| 991 | /** | ||
| 992 | * This magic method is executed after the object is deserialized | ||
| 993 | * @see __sleep() | ||
| 994 | * | ||
| 995 | * @access public | ||
| 996 | * | ||
| 997 | * @return void | ||
| 998 | */ | ||
| 999 | public function __wakeup(): void | ||
| 1000 |     { | ||
| 1001 | $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey, 'iiif'); | ||
| 1002 | IiifHelper::setUrlReader(IiifUrlReader::getInstance()); | ||
| 1003 | IiifHelper::setMaxThumbnailHeight($conf['thumbnailHeight']); | ||
| 1004 | IiifHelper::setMaxThumbnailWidth($conf['thumbnailWidth']); | ||
| 1005 | $resource = IiifHelper::loadIiifResource($this->asJson); | ||
| 1006 |         if ($resource instanceof ManifestInterface) { | ||
| 1007 | $this->asJson = ''; | ||
| 1008 | $this->iiif = $resource; | ||
| 1009 |             $this->init(''); | ||
| 1010 |         } else { | ||
| 1011 | $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class); | ||
| 1012 |             $this->logger->error('Could not load IIIF after deserialization'); | ||
| 1013 | } | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | /** | ||
| 1017 | * @access public | ||
| 1018 | * | ||
| 1019 | * @return string[] | ||
| 1020 | */ | ||
| 1021 | public function __sleep(): array | ||
| 1027 | } | ||
| 1028 | } | ||
| 1029 |