From 93797275e7282162046ab942f074cb6d63e46021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Sch=C3=B6lzel?= <142507449+fschoelzel@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:03:59 +0100 Subject: [PATCH 01/17] [BUGFIX] reassign requestData to viewData after assigning default values (#1143) Co-authored-by: Sebastian Meyer --- Classes/Controller/NavigationController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Classes/Controller/NavigationController.php b/Classes/Controller/NavigationController.php index 9371e69ab0..7f9341b434 100644 --- a/Classes/Controller/NavigationController.php +++ b/Classes/Controller/NavigationController.php @@ -73,6 +73,8 @@ public function mainAction(): void } else { $this->requestData['page'] = 0; $this->requestData['double'] = 0; + // reassign requestData to viewData after assigning default values + $this->viewData['requestData'] = $this->requestData; } // Steps for X pages backward / forward. Double page view uses double steps. From 473a8312df2d79c9ab8371e16d831f783c26f82a Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:07:23 +0100 Subject: [PATCH 02/17] [BUGFIX] Display paginated results only if there are some results (#1148) Co-authored-by: Sebastian Meyer --- Resources/Private/Templates/ListView/Main.html | 6 ++++-- Resources/Private/Templates/Search/Main.html | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Resources/Private/Templates/ListView/Main.html b/Resources/Private/Templates/ListView/Main.html index 2c7407da7c..e3bdfb9dfd 100644 --- a/Resources/Private/Templates/ListView/Main.html +++ b/Resources/Private/Templates/ListView/Main.html @@ -26,9 +26,11 @@

- - + + + + diff --git a/Resources/Private/Templates/Search/Main.html b/Resources/Private/Templates/Search/Main.html index 44a06eb674..fb017d7d88 100644 --- a/Resources/Private/Templates/Search/Main.html +++ b/Resources/Private/Templates/Search/Main.html @@ -118,8 +118,10 @@ - - + + + + From 8fed85a094c3af03f508bd099b68094fb7224dcf Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:15:34 +0100 Subject: [PATCH 03/17] [MAINTENANCE] Translate language code in own function (#1126) Co-authored-by: Sebastian Meyer --- Classes/Common/Solr/SolrSearch.php | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Classes/Common/Solr/SolrSearch.php b/Classes/Common/Solr/SolrSearch.php index 17af84579b..677f7bcb07 100644 --- a/Classes/Common/Solr/SolrSearch.php +++ b/Classes/Common/Solr/SolrSearch.php @@ -545,12 +545,7 @@ public function submit($start, $rows, $processResults = true) $documents[$doc['uid']] = $allDocuments[$doc['uid']]; } if ($documents[$doc['uid']]) { - // translate language code if applicable - if ($doc['metadata']['language']) { - foreach ($doc['metadata']['language'] as $indexName => $language) { - $doc['metadata']['language'][$indexName] = Helper::getLanguageName($language); - } - } + $this->translateLanguageCode($doc); if ($doc['toplevel'] === false) { // this maybe a chapter, article, ..., year if ($doc['type'] === 'year') { @@ -661,12 +656,7 @@ protected function fetchToplevelMetadataFromSolr(array $queryParams): array $result = $this->searchSolr($params, true); foreach ($result['documents'] as $doc) { - // translate language code if applicable - if($doc['metadata']['language']) { - foreach($doc['metadata']['language'] as $indexName => $language) { - $doc['metadata']['language'][$indexName] = Helper::getLanguageName($doc['metadata']['language'][$indexName]); - } - } + $this->translateLanguageCode($doc); $metadataArray[$doc['uid']] = $doc['metadata']; } @@ -813,4 +803,22 @@ private function getDocument(Document $record, array $highlighting, array $field return $document; } + + /** + * Translate language code if applicable. + * + * @access private + * + * @param &$doc document array + * + * @return void + */ + private function translateLanguageCode(&$doc): void + { + if ($doc['metadata']['language']) { + foreach($doc['metadata']['language'] as $indexName => $language) { + $doc['metadata']['language'][$indexName] = Helper::getLanguageName($language); + } + } + } } From 2c0454753c814e40e4d5b63c080c642625a4fa6f Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:18:50 +0100 Subject: [PATCH 04/17] [MAINTENANCE] Return values without assign to variables (#1128) Co-authored-by: Sebastian Meyer --- Classes/Common/Helper.php | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Classes/Common/Helper.php b/Classes/Common/Helper.php index 1f32b452ab..8b66ebdbec 100644 --- a/Classes/Common/Helper.php +++ b/Classes/Common/Helper.php @@ -205,8 +205,7 @@ public static function decrypt(string $encrypted) $data = substr($binary, openssl_cipher_iv_length(self::$cipherAlgorithm)); $key = openssl_digest($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'], self::$hashAlgorithm, true); // Decrypt data. - $decrypted = openssl_decrypt($data, self::$cipherAlgorithm, $key, OPENSSL_RAW_DATA, $iv); - return $decrypted; + return openssl_decrypt($data, self::$cipherAlgorithm, $key, OPENSSL_RAW_DATA, $iv); } /** @@ -294,8 +293,7 @@ public static function digest(string $string) return false; } // Hash string. - $hashed = openssl_digest($string, self::$hashAlgorithm); - return $hashed; + return openssl_digest($string, self::$hashAlgorithm); } /** @@ -371,8 +369,7 @@ public static function getCleanString(string $string): string // Remove multiple dashes or whitespaces. $string = preg_replace('/[\s-]+/', ' ', $string); // Convert whitespaces and underscore to dash. - $string = preg_replace('/[\s_]/', '-', $string); - return $string; + return preg_replace('/[\s_]/', '-', $string); } /** @@ -530,9 +527,7 @@ public static function getDocumentStructures(int $pid = -1): array $allStructures = $kitodoStructures->fetchAllAssociative(); // make lookup-table indexName -> uid - $allStructures = array_column($allStructures, 'indexName', 'uid'); - - return $allStructures; + return array_column($allStructures, 'indexName', 'uid'); } /** @@ -685,9 +680,8 @@ public static function renderFlashMessages(string $queue = 'kitodo.default.flash $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); $flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier($queue); $flashMessages = $flashMessageQueue->getAllMessagesAndFlush(); - $content = GeneralUtility::makeInstance(KitodoFlashMessageRenderer::class) + return GeneralUtility::makeInstance(KitodoFlashMessageRenderer::class) ->render($flashMessages); - return $content; } /** @@ -925,9 +919,7 @@ public static function getUrl(string $url) self::log('Could not fetch data from URL "' . $url . '". Error: ' . $e->getMessage() . '.', LOG_SEVERITY_WARNING); return false; } - $content = $response->getBody()->getContents(); - - return $content; + return $response->getBody()->getContents(); } /** From aa5bb1c3bd8ed71cfec2b876b6f9ea1789fd7664 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:21:18 +0100 Subject: [PATCH 05/17] [MAINTENANCE] Improvements in IiiFManifest class (#1129) Co-authored-by: Sebastian Meyer --- Classes/Common/IiifManifest.php | 101 ++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/Classes/Common/IiifManifest.php b/Classes/Common/IiifManifest.php index a95e9fbb18..aa0f409cbc 100644 --- a/Classes/Common/IiifManifest.php +++ b/Classes/Common/IiifManifest.php @@ -141,8 +141,8 @@ protected function establishRecordId(int $pid): void ->from('tx_dlf_metadataformat') ->from('tx_dlf_formats') ->where( - $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($pid)), - $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', intval($pid)), + $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $pid), + $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $pid), $queryBuilder->expr()->orX( $queryBuilder->expr()->andX( $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'), @@ -267,13 +267,12 @@ protected function magicGetPhysicalStructure(): array } $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); $iiifId = $this->iiif->getId(); - $physSeq[0] = $iiifId; - $this->physicalStructureInfo[$physSeq[0]]['id'] = $iiifId; - $this->physicalStructureInfo[$physSeq[0]]['dmdId'] = $iiifId; - $this->physicalStructureInfo[$physSeq[0]]['label'] = $this->iiif->getLabelForDisplay(); - $this->physicalStructureInfo[$physSeq[0]]['orderlabel'] = $this->iiif->getLabelForDisplay(); - $this->physicalStructureInfo[$physSeq[0]]['type'] = 'physSequence'; - $this->physicalStructureInfo[$physSeq[0]]['contentIds'] = null; + $this->physicalStructureInfo[$iiifId]['id'] = $iiifId; + $this->physicalStructureInfo[$iiifId]['dmdId'] = $iiifId; + $this->physicalStructureInfo[$iiifId]['label'] = $this->iiif->getLabelForDisplay(); + $this->physicalStructureInfo[$iiifId]['orderlabel'] = $this->iiif->getLabelForDisplay(); + $this->physicalStructureInfo[$iiifId]['type'] = 'physSequence'; + $this->physicalStructureInfo[$iiifId]['contentIds'] = null; $fileUseDownload = $this->getUseGroups('fileGrpDownload'); $fileUseFulltext = $this->getUseGroups('fileGrpFulltext'); $fileUseThumbs = $this->getUseGroups('fileGrpThumbs'); @@ -281,7 +280,7 @@ protected function magicGetPhysicalStructure(): array if (!empty($fileUseDownload)) { $docPdfRendering = $this->iiif->getRenderingUrlsForFormat('application/pdf'); if (!empty($docPdfRendering)) { - $this->physicalStructureInfo[$physSeq[0]]['files'][$fileUseDownload[0]] = $docPdfRendering[0]; + $this->physicalStructureInfo[$iiifId]['files'][$fileUseDownload[0]] = $docPdfRendering[0]; } } if (!empty($fileUseFulltext)) { @@ -291,7 +290,7 @@ protected function magicGetPhysicalStructure(): array } if (!empty($iiifAlto)) { $this->mimeTypes[$iiifAlto[0]] = 'application/alto+xml'; - $this->physicalStructureInfo[$physSeq[0]]['files'][$fileUseFulltext[0]] = $iiifAlto[0]; + $this->physicalStructureInfo[$iiifId]['files'][$fileUseFulltext[0]] = $iiifAlto[0]; $this->hasFulltext = true; $this->hasFulltextSet = true; } @@ -305,9 +304,9 @@ protected function magicGetPhysicalStructure(): array // put thumbnails in thumbnail filegroup if ( !empty($thumbnailUrl) - && empty($this->physicalStructureInfo[$physSeq[0]]['files'][$fileUseThumbs[0]]) + && empty($this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]]) ) { - $this->physicalStructureInfo[$physSeq[0]]['files'][$fileUseThumbs[0]] = $thumbnailUrl; + $this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]] = $thumbnailUrl; } // populate structural metadata info $elements[$canvasOrder] = $canvas->getId(); @@ -361,7 +360,8 @@ protected function magicGetPhysicalStructure(): array } $this->numPages = $canvasOrder; // Merge and re-index the array to get nice numeric indexes. - $this->physicalStructure = array_merge($physSeq, $elements); + array_unshift($elements, $iiifId); + $this->physicalStructure = $elements; } $this->physicalStructureLoaded = true; } @@ -412,7 +412,7 @@ public function getFileLocation(string $id): string // @phpstan-ignore-next-line return (!empty($resource->getImageAnnotations()) && $resource->getImageAnnotations()->getSingleService() != null) ? $resource->getImageAnnotations()[0]->getSingleService()->getId() : $id; } elseif ($resource instanceof ContentResourceInterface) { - return $resource->getSingleService() != null && $resource->getSingleService() instanceof Service ? $resource->getSingleService()->getId() : $id; + return $resource->getSingleService() instanceof Service ? $resource->getSingleService()->getId() : $id; } elseif ($resource instanceof AbstractImageService) { return $resource->getId(); } elseif ($resource instanceof AnnotationContainerInterface) { @@ -512,13 +512,9 @@ protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool $this->magicGetSmLinks(); // Load physical structure. $this->magicGetPhysicalStructure(); - $canvases = []; - if ($resource instanceof ManifestInterface) { - $startCanvas = $resource->getStartCanvasOrFirstCanvas(); - $canvases = $resource->getDefaultCanvases(); - } elseif ($resource instanceof RangeInterface) { + + if ($resource instanceof ManifestInterface || $resource instanceof RangeInterface) { $startCanvas = $resource->getStartCanvasOrFirstCanvas(); - $canvases = $resource->getAllCanvases(); } if (isset($startCanvas)) { $details['pagination'] = $startCanvas->getLabel(); @@ -540,7 +536,7 @@ protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool if ($resource instanceof ManifestInterface && $resource->getRootRanges() != null) { $rangesToAdd = []; $rootRanges = []; - if (sizeof($this->iiif->getRootRanges()) == 1 && $this->iiif->getRootRanges()[0]->isTopRange()) { + if (count($this->iiif->getRootRanges()) == 1 && $this->iiif->getRootRanges()[0]->isTopRange()) { $rangesToAdd = $this->iiif->getRootRanges()[0]->getMemberRangesAndRanges(); } else { $rangesToAdd = $this->iiif->getRootRanges(); @@ -589,7 +585,7 @@ public function getManifestMetadata(string $id, bool $withDescription = true, bo $iiifResource = $this->iiif->getContainedResourceById($id); $result = []; if ($iiifResource != null) { - if ($iiifResource->getLabel() != null && $iiifResource->getLabel() != "") { + if (!empty($iiifResource->getLabel())) { $result['label'] = $iiifResource->getLabel(); } if (!empty($iiifResource->getMetadata())) { @@ -650,8 +646,8 @@ public function getMetadata(string $id, int $cPid = 0): array ->from('tx_dlf_metadataformat') ->from('tx_dlf_formats') ->where( - $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)), - $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', intval($cPid)), + $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $cPid), + $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $cPid), $queryBuilder->expr()->orX( $queryBuilder->expr()->andX( $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'), @@ -804,19 +800,7 @@ public function getFullText(string $id): string // Get annotation containers $annotationContainerIds = $this->physicalStructureInfo[$id]['annotationContainers']; if (!empty($annotationContainerIds)) { - $annotationTexts = []; - foreach ($annotationContainerIds as $annotationListId) { - $annotationContainer = $this->iiif->getContainedResourceById($annotationListId); - /* @var $annotationContainer \Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface */ - foreach ($annotationContainer->getTextAnnotations(Motivation::PAINTING) as $annotation) { - if ( - $annotation->getTargetResourceId() == $iiifResource->getId() && - $annotation->getBody() != null && $annotation->getBody()->getChars() != null - ) { - $annotationTexts[] = $annotation->getBody()->getChars(); - } - } - } + $annotationTexts = $this->getAnnotationTexts($annotationContainerIds, $iiifResource->getId()); $rawText .= implode(' ', $annotationTexts); } } @@ -861,11 +845,9 @@ protected function loadLocation(string $location): bool IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']); IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']); $resource = IiifHelper::loadIiifResource($fileResource); - if ($resource != null) { - if ($resource instanceof ManifestInterface) { - $this->iiif = $resource; - return true; - } + if ($resource instanceof ManifestInterface) { + $this->iiif = $resource; + return true; } } $this->logger->error('Could not load IIIF manifest from "' . $location . '"'); @@ -919,7 +901,8 @@ protected function ensureHasFulltextIsSet(): void $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); if ($extConf['indexAnnotations'] == 1 && !empty($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING))) { foreach ($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING) as $annotationContainer) { - if (($textAnnotations = $annotationContainer->getTextAnnotations(Motivation::PAINTING)) != null) { + $textAnnotations = $annotationContainer->getTextAnnotations(Motivation::PAINTING); + if ($textAnnotations != null) { foreach ($textAnnotations as $annotation) { if ( $annotation->getBody() != null && @@ -960,6 +943,34 @@ protected function magicGetToplevelId(): string return $this->toplevelId; } + /** + * Get annotation texts. + * + * @access private + * + * @param array $annotationContainerIds + * @param string $iiifId + * + * @return array + */ + private function getAnnotationTexts($annotationContainerIds, $iiifId): array + { + $annotationTexts = []; + foreach ($annotationContainerIds as $annotationListId) { + $annotationContainer = $this->iiif->getContainedResourceById($annotationListId); + /* @var $annotationContainer \Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface */ + foreach ($annotationContainer->getTextAnnotations(Motivation::PAINTING) as $annotation) { + if ( + $annotation->getTargetResourceId() == $iiifId && + $annotation->getBody() != null && $annotation->getBody()->getChars() != null + ) { + $annotationTexts[] = $annotation->getBody()->getChars(); + } + } + } + return $annotationTexts; + } + /** * This magic method is executed after the object is deserialized * @see __sleep() @@ -975,7 +986,7 @@ public function __wakeup(): void IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']); IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']); $resource = IiifHelper::loadIiifResource($this->asJson); - if ($resource != null && $resource instanceof ManifestInterface) { + if ($resource instanceof ManifestInterface) { $this->asJson = ''; $this->iiif = $resource; $this->init(''); From 9621f726bc3c033337cdf6d8d9d7d308bb0f2a8f Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:31:28 +0100 Subject: [PATCH 06/17] [MAINTENANCE] Improvements in Indexer class (#1131) Co-authored-by: Sebastian Meyer --- Classes/Common/Indexer.php | 296 +++++++++++++++++++++++-------------- 1 file changed, 188 insertions(+), 108 deletions(-) diff --git a/Classes/Common/Indexer.php b/Classes/Common/Indexer.php index cf737f33e8..59a652db7a 100644 --- a/Classes/Common/Indexer.php +++ b/Classes/Common/Indexer.php @@ -101,7 +101,8 @@ public static function add(Document $document, DocumentRepository $documentRepos $success = true; Helper::getLanguageService()->includeLLFile('EXT:dlf/Resources/Private/Language/locallang_be.xlf'); // Handle multi-volume documents. - if ($parentId = $document->getPartof()) { + $parentId = $document->getPartof(); + if ($parentId) { // get parent document $parent = $documentRepository->findByUid($parentId); if ($parent) { @@ -149,45 +150,26 @@ public static function add(Document $document, DocumentRepository $documentRepos if (!(Environment::isCli())) { if ($success) { - Helper::addMessage( + self::addMessage( sprintf(Helper::getLanguageService()->getLL('flash.documentIndexed'), $document->getTitle(), $document->getUid()), - Helper::getLanguageService()->getLL('flash.done'), - FlashMessage::OK, - true, - 'core.template.flashMessages' + 'flash.done', + FlashMessage::OK ); } else { - Helper::addMessage( - sprintf(Helper::getLanguageService()->getLL('flash.documentNotIndexed'), $document->getTitle(), $document->getUid()), - Helper::getLanguageService()->getLL('flash.error'), - FlashMessage::ERROR, - true, - 'core.template.flashMessages' - ); + self::addErrorMessage(sprintf(Helper::getLanguageService()->getLL('flash.documentNotIndexed'), $document->getTitle(), $document->getUid())); } } return $success; } catch (\Exception $e) { - if (!(Environment::isCli())) { - Helper::addMessage( - Helper::getLanguageService()->getLL('flash.solrException') . ' ' . htmlspecialchars($e->getMessage()), - Helper::getLanguageService()->getLL('flash.error'), - FlashMessage::ERROR, - true, - 'core.template.flashMessages' - ); - } - Helper::log('Apache Solr threw exception: "' . $e->getMessage() . '"', LOG_SEVERITY_ERROR); + self::handleException($e->getMessage()); return false; } } else { if (!(Environment::isCli())) { - Helper::addMessage( + self::addMessage( Helper::getLanguageService()->getLL('flash.solrNoConnection'), - Helper::getLanguageService()->getLL('flash.warning'), - FlashMessage::WARNING, - true, - 'core.template.flashMessages' + 'flash.warning', + FlashMessage::WARNING ); } Helper::log('Could not connect to Apache Solr server', LOG_SEVERITY_ERROR); @@ -210,7 +192,7 @@ public static function add(Document $document, DocumentRepository $documentRepos public static function getIndexFieldName(string $indexName, int $pid = 0): string { // Sanitize input. - $pid = max(intval($pid), 0); + $pid = max((int) $pid, 0); if (!$pid) { Helper::log('Invalid PID ' . $pid . ' for metadata configuration', LOG_SEVERITY_ERROR); return ''; @@ -257,7 +239,7 @@ protected static function loadIndexConf(int $pid): void ) ->from('tx_dlf_metadata') ->where( - $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($pid)), + $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $pid), Helper::whereExpression('tx_dlf_metadata') ) ->execute(); @@ -339,20 +321,7 @@ protected static function processLogical(Document $document, array $logicalUnit) $solrDoc->setField('volume', $metadata['volume'][0], self::$fields['fieldboost']['volume']); // verify date formatting if(strtotime($metadata['date'][0])) { - // do not alter dates YYYY or YYYY-MM or YYYY-MM-DD - if ( - preg_match("/^[\d]{4}$/", $metadata['date'][0]) - || preg_match("/^[\d]{4}-[\d]{2}$/", $metadata['date'][0]) - || preg_match("/^[\d]{4}-[\d]{2}-[\d]{2}$/", $metadata['date'][0]) - ) { - $solrDoc->setField('date', $metadata['date'][0]); - // change date YYYYMMDD to YYYY-MM-DD - } elseif (preg_match("/^[\d]{8}$/", $metadata['date'][0])){ - $solrDoc->setField('date', date("Y-m-d", strtotime($metadata['date'][0]))); - // convert any datetime to proper ISO extended datetime format and timezone for SOLR - } elseif (preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*$/", $metadata['date'][0])) { - $solrDoc->setField('date', date('Y-m-d\TH:i:s\Z', strtotime($metadata['date'][0]))); - } + $solrDoc->setField('date', self::getFormattedDate($metadata['date'][0])); } $solrDoc->setField('record_id', $metadata['record_id'][0]); $solrDoc->setField('purl', $metadata['purl'][0]); @@ -365,26 +334,7 @@ protected static function processLogical(Document $document, array $logicalUnit) if (is_object($coordinates)) { $solrDoc->setField('geom', json_encode($coordinates->features[0])); } - $autocomplete = []; - foreach ($metadata as $index_name => $data) { - if ( - !empty($data) - && substr($index_name, -8) !== '_sorting' - ) { - $solrDoc->setField(self::getIndexFieldName($index_name, $document->getPid()), $data, self::$fields['fieldboost'][$index_name]); - if (in_array($index_name, self::$fields['sortables'])) { - // Add sortable fields to index. - $solrDoc->setField($index_name . '_sorting', $metadata[$index_name . '_sorting'][0]); - } - if (in_array($index_name, self::$fields['facets'])) { - // Add facets to index. - $solrDoc->setField($index_name . '_faceting', $data); - } - if (in_array($index_name, self::$fields['autocomplete'])) { - $autocomplete = array_merge($autocomplete, $data); - } - } - } + $autocomplete = self::processMetadata($document, $metadata, $solrDoc); // Add autocomplete values to index. if (!empty($autocomplete)) { $solrDoc->setField('autocomplete', $autocomplete); @@ -401,16 +351,7 @@ protected static function processLogical(Document $document, array $logicalUnit) $updateQuery->addDocument($solrDoc); self::$solr->service->update($updateQuery); } catch (\Exception $e) { - if (!(Environment::isCli())) { - Helper::addMessage( - Helper::getLanguageService()->getLL('flash.solrException') . '
' . htmlspecialchars($e->getMessage()), - Helper::getLanguageService()->getLL('flash.error'), - FlashMessage::ERROR, - true, - 'core.template.flashMessages' - ); - } - Helper::log('Apache Solr threw exception: "' . $e->getMessage() . '"', LOG_SEVERITY_ERROR); + self::handleException($e->getMessage()); return false; } } @@ -466,30 +407,7 @@ protected static function processPhysical(Document $document, int $page, array $ $solrDoc->setField('fulltext', $fullText); if (is_array($doc->metadataArray[$doc->toplevelId])) { - // Add faceting information to physical sub-elements if applicable. - foreach ($doc->metadataArray[$doc->toplevelId] as $index_name => $data) { - if ( - !empty($data) - && substr($index_name, -8) !== '_sorting' - ) { - - if (in_array($index_name, self::$fields['facets'])) { - // Remove appended "valueURI" from authors' names for indexing. - if ($index_name == 'author') { - $data = self::removeAppendsFromAuthor($data); - } - // Add facets to index. - $solrDoc->setField($index_name . '_faceting', $data); - } - } - // Add sorting information to physical sub-elements if applicable. - if ( - !empty($data) - && substr($index_name, -8) == '_sorting' - ) { - $solrDoc->setField($index_name , $doc->metadataArray[$doc->toplevelId][$index_name]); - } - } + self::addFaceting($doc, $solrDoc); } // Add collection information to physical sub-elements if applicable. if ( @@ -502,16 +420,7 @@ protected static function processPhysical(Document $document, int $page, array $ $updateQuery->addDocument($solrDoc); self::$solr->service->update($updateQuery); } catch (\Exception $e) { - if (!(Environment::isCli())) { - Helper::addMessage( - Helper::getLanguageService()->getLL('flash.solrException') . '
' . htmlspecialchars($e->getMessage()), - Helper::getLanguageService()->getLL('flash.error'), - FlashMessage::ERROR, - true, - 'core.template.flashMessages' - ); - } - Helper::log('Apache Solr threw exception: "' . $e->getMessage() . '"', LOG_SEVERITY_ERROR); + self::handleException($e->getMessage()); return false; } } @@ -546,6 +455,83 @@ protected static function solrConnect(int $core, int $pid = 0): bool return false; } + /** + * Process metadata: add facets, sortable fields and create autocomplete array. + * + * @static + * + * @access private + * + * @param Document $document + * @param array $metadata + * @param DocumentInterface &$solrDoc + * + * @return array empty array or autocomplete values + */ + private static function processMetadata($document, $metadata, &$solrDoc): array + { + $autocomplete = []; + foreach ($metadata as $indexName => $data) { + if ( + !empty($data) + && substr($indexName, -8) !== '_sorting' + ) { + $solrDoc->setField(self::getIndexFieldName($indexName, $document->getPid()), $data, self::$fields['fieldboost'][$indexName]); + if (in_array($indexName, self::$fields['sortables'])) { + // Add sortable fields to index. + $solrDoc->setField($indexName . '_sorting', $metadata[$indexName . '_sorting'][0]); + } + if (in_array($indexName, self::$fields['facets'])) { + // Add facets to index. + $solrDoc->setField($indexName . '_faceting', $data); + } + if (in_array($indexName, self::$fields['autocomplete'])) { + $autocomplete = array_merge($autocomplete, $data); + } + } + } + return $autocomplete; + } + + /** + * Add faceting information to physical sub-elements if applicable. + * + * @static + * + * @access private + * + * @param AbstractDocument $doc + * @param DocumentInterface &$solrDoc + * + * @return void + */ + private static function addFaceting($doc, &$solrDoc): void + { + foreach ($doc->metadataArray[$doc->toplevelId] as $indexName => $data) { + if ( + !empty($data) + && substr($indexName, -8) !== '_sorting' + ) { + + if (in_array($indexName, self::$fields['facets'])) { + // Remove appended "valueURI" from authors' names for indexing. + if ($indexName == 'author') { + $data = self::removeAppendsFromAuthor($data); + } + // Add facets to index. + $solrDoc->setField($indexName . '_faceting', $data); + } + } + // Add sorting information to physical sub-elements if applicable. + if ( + !empty($data) + && substr($indexName, -8) == '_sorting' + ) { + $solrDoc->setField($indexName, $doc->metadataArray[$doc->toplevelId][$indexName]); + } + } + } + /** * Get SOLR document with set standard fields (identical for logical and physical unit) * @@ -576,6 +562,37 @@ private static function getSolrDocument(Query $updateQuery, Document $document, return $solrDoc; } + /** + * Get formatted date without alteration. + * Possible formats: YYYY or YYYY-MM or YYYY-MM-DD. + * + * @static + * + * @access private + * + * @param string $date + * + * @return string formatted date YYYY or YYYY-MM or YYYY-MM-DD or empty string + */ + private static function getFormattedDate($date): string + { + if ( + preg_match("/^[\d]{4}$/", $date) + || preg_match("/^[\d]{4}-[\d]{2}$/", $date) + || preg_match("/^[\d]{4}-[\d]{2}-[\d]{2}$/", $date) + ) { + return $date; + // change date YYYYMMDD to YYYY-MM-DD + } elseif (preg_match("/^[\d]{8}$/", $date)) { + return date("Y-m-d", strtotime($date)); + // convert any datetime to proper ISO extended datetime format and timezone for SOLR + } elseif (preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*$/", $date)) { + return date('Y-m-d\TH:i:s\Z', strtotime($date)); + } + // date doesn't match any standard + return ''; + } + /** * Remove appended "valueURI" from authors' names for indexing. * @@ -598,6 +615,69 @@ private static function removeAppendsFromAuthor($authors) return $authors; } + /** + * Handle exception. + * + * @static + * + * @access private + * + * @param string $errorMessage + * + * @return void + */ + private static function handleException(string $errorMessage): void + { + if (!(Environment::isCli())) { + self::addErrorMessage(Helper::getLanguageService()->getLL('flash.solrException') . '
' . htmlspecialchars($errorMessage)); + } + Helper::log('Apache Solr threw exception: "' . $errorMessage . '"', LOG_SEVERITY_ERROR); + } + + /** + * Add error message only with message content. + * + * @static + * + * @access private + * + * @param string $message + * + * @return void + */ + private static function addErrorMessage(string $message): void + { + self::addMessage( + $message, + 'flash.error', + FlashMessage::ERROR + ); + } + + /** + * Add message only with changeable parameters. + * + * @static + * + * @access private + * + * @param string $message + * @param string $type + * @param int $status + * + * @return void + */ + private static function addMessage(string $message, string $type, int $status): void + { + Helper::addMessage( + $message, + Helper::getLanguageService()->getLL($type), + $status, + true, + 'core.template.flashMessages' + ); + } + /** * Prevent instantiation by hiding the constructor * From 44049f0644d1b12853cf1ac5a6541e3621bfd2a0 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:34:14 +0100 Subject: [PATCH 07/17] [MAINTENANCE] Improvements in Solr class (#1132) Co-authored-by: Sebastian Meyer --- Classes/Common/Solr/Solr.php | 49 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Classes/Common/Solr/Solr.php b/Classes/Common/Solr/Solr.php index c3bb6e0d60..52c1ba081a 100644 --- a/Classes/Common/Solr/Solr.php +++ b/Classes/Common/Solr/Solr.php @@ -34,12 +34,12 @@ * * @property array $config this holds the Solr configuration * @property-read string|null $core this holds the core name for the current instance - * @property-write int $cPid this holds the PID for the configuration + * @property-write int $configPid this holds the PID for the configuration * @property int $limit this holds the max results * @property-read int $numberOfHits this holds the number of hits for last search * @property-write array $params this holds the additional query parameters * @property-read bool $ready flag if the Solr service is instantiated successfully - * @property-read \Solarium\Client $service this holds the Solr service object + * @property-read Client $service this holds the Solr service object */ class Solr implements LoggerAwareInterface { @@ -61,7 +61,7 @@ class Solr implements LoggerAwareInterface * @access protected * @var int This holds the PID for the configuration */ - protected int $cPid = 0; + protected int $configPid = 0; /** * @access public @@ -206,7 +206,7 @@ public static function escapeQueryKeepField(string $query, int $pid): string ->from('tx_dlf_metadata') ->where( $queryBuilder->expr()->eq('tx_dlf_metadata.index_indexed', 1), - $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($pid)), + $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $pid), $queryBuilder->expr()->orX( $queryBuilder->expr()->in('tx_dlf_metadata.sys_language_uid', [-1, 0]), $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0) @@ -327,7 +327,7 @@ public static function getInstance($core = null): Solr */ public static function getNextCoreNumber(int $number = 0): int { - $number = max(intval($number), 0); + $number = max($number, 0); // Check if core already exists. $solr = self::getInstance('dlfCore' . $number); if (!$solr->ready) { @@ -364,14 +364,10 @@ protected function loadSolrConnectionInfo(): void if (!empty($config['path'])) { $config['path'] .= '/'; } - // Add "/solr" API endpoint when using Solarium <5.x - // Todo: Remove when dropping support for Solarium 4.x - if (!\Solarium\Client::checkMinimal('5.0.0')) { - $config['path'] .= 'solr/'; - } + // Set connection timeout lower than PHP's max_execution_time. - $max_execution_time = intval(ini_get('max_execution_time')) ? : 30; - $config['timeout'] = MathUtility::forceIntegerInRange($conf['solrTimeout'], 1, $max_execution_time, 10); + $maxExecutionTime = (int) ini_get('max_execution_time') ? : 30; + $config['timeout'] = MathUtility::forceIntegerInRange($conf['solrTimeout'], 1, $maxExecutionTime, 10); $this->config = $config; } } @@ -394,7 +390,8 @@ public function searchRaw(array $parameters = []): array $cacheIdentifier = Helper::digest($this->core . print_r(array_merge($this->params, $parameters), true)); $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('tx_dlf_solr'); $resultSet = []; - if (($entry = $cache->get($cacheIdentifier)) === false) { + $entry = $cache->get($cacheIdentifier); + if ($entry === false) { $selectQuery = $this->service->createSelect(array_merge($this->params, $parameters)); $result = $this->service->select($selectQuery); foreach ($result as $doc) { @@ -416,7 +413,7 @@ public function searchRaw(array $parameters = []): array * * @return string|null The core name of the current query endpoint or null if core admin endpoint */ - protected function _getCore(): ?string + protected function magicGetCore(): ?string { return $this->core; } @@ -428,7 +425,7 @@ protected function _getCore(): ?string * * @return int The max number of results */ - protected function _getLimit(): int + protected function magicGetLimit(): int { return $this->limit; } @@ -440,7 +437,7 @@ protected function _getLimit(): int * * @return int Total number of hits for last search */ - protected function _getNumberOfHits(): int + protected function magicGetNumberOfHits(): int { return $this->numberOfHits; } @@ -452,7 +449,7 @@ protected function _getNumberOfHits(): int * * @return bool Is the search instantiated successfully? */ - protected function _getReady(): bool + protected function magicGetReady(): bool { return $this->ready; } @@ -464,13 +461,13 @@ protected function _getReady(): bool * * @return Client Apache Solr service object */ - protected function _getService(): Client + protected function magicGetService(): Client { return $this->service; } /** - * This sets $this->cPid via __set() + * This sets $this->configPid via __set() * * @access protected * @@ -478,9 +475,9 @@ protected function _getService(): Client * * @return void */ - protected function _setCPid(int $value): void + protected function magicSetConfigPid(int $value): void { - $this->cPid = max(intval($value), 0); + $this->configPid = max($value, 0); } /** @@ -492,9 +489,9 @@ protected function _setCPid(int $value): void * * @return void */ - protected function _setLimit(int $value): void + protected function magicSetLimit(int $value): void { - $this->limit = max(intval($value), 0); + $this->limit = max($value, 0); } /** @@ -506,7 +503,7 @@ protected function _setLimit(int $value): void * * @return void */ - protected function _setParams(array $value): void + protected function magicSetParams(array $value): void { $this->params = $value; } @@ -522,7 +519,7 @@ protected function _setParams(array $value): void */ public function __get(string $var) { - $method = '_get' . ucfirst($var); + $method = 'magicGet' . ucfirst($var); if ( !property_exists($this, $var) || !method_exists($this, $method) @@ -560,7 +557,7 @@ public function __isset(string $var): bool */ public function __set(string $var, $value): void { - $method = '_set' . ucfirst($var); + $method = 'magicSet' . ucfirst($var); if ( !property_exists($this, $var) || !method_exists($this, $method) From 5d8251e1befb5580d2adbec3c409dadaadc35e55 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:36:36 +0100 Subject: [PATCH 08/17] [MAINTENANCE] Improvements in AbstractController class (#1133) Co-authored-by: Sebastian Meyer --- Classes/Controller/AbstractController.php | 119 ++++++++++++++-------- 1 file changed, 78 insertions(+), 41 deletions(-) diff --git a/Classes/Controller/AbstractController.php b/Classes/Controller/AbstractController.php index a545ed05d6..36f62e3388 100644 --- a/Classes/Controller/AbstractController.php +++ b/Classes/Controller/AbstractController.php @@ -100,7 +100,7 @@ protected function initialize(): void $this->viewData = [ 'pageUid' => $GLOBALS['TSFE']->id, - 'uniqueId'=> uniqid(), + 'uniqueId' => uniqid(), 'requestData' => $this->requestData ]; } @@ -127,38 +127,9 @@ protected function loadDocument(int $documentId = 0): void $doc = null; if (MathUtility::canBeInterpretedAsInteger($documentId)) { - // find document from repository by uid - $this->document = $this->documentRepository->findOneByIdAndSettings($documentId); - if ($this->document) { - $doc = AbstractDocument::getInstance($this->document->getLocation(), $this->settings, true); - } else { - $this->logger->error('Invalid UID "' . $documentId . '" or PID "' . $this->settings['storagePid'] . '" for document loading'); - } - } else if (GeneralUtility::isValidUrl($documentId)) { - - $doc = AbstractDocument::getInstance($documentId, $this->settings, true); - - if ($doc !== null) { - if ($doc->recordId) { - // find document from repository by recordId - $docFromRepository = $this->documentRepository->findOneByRecordId($doc->recordId); - if ($docFromRepository !== null) { - $this->document = $docFromRepository; - } else { - // create new dummy Document object - $this->document = GeneralUtility::makeInstance(Document::class); - } - } - - // Make sure configuration PID is set when applicable - if ($doc->cPid == 0) { - $doc->cPid = max(intval($this->settings['storagePid']), 0); - } - - $this->document->setLocation($documentId); - } else { - $this->logger->error('Invalid location given "' . $documentId . '" for document loading'); - } + $doc = $this->getDocumentByUid($documentId); + } elseif (GeneralUtility::isValidUrl($documentId)) { + $doc = $this->getDocumentByUrl($documentId); } if ($this->document !== null && $doc !== null) { @@ -191,15 +162,18 @@ protected function loadDocument(int $documentId = 0): void * * @return void */ - protected function configureProxyUrl(string &$url): void { + protected function configureProxyUrl(string &$url): void + { $this->uriBuilder->reset() ->setTargetPageUid($GLOBALS['TSFE']->id) ->setCreateAbsoluteUri(!empty($this->settings['forceAbsoluteUrl'])) - ->setArguments([ - 'eID' => 'tx_dlf_pageview_proxy', - 'url' => $url, - 'uHash' => GeneralUtility::hmac($url, 'PageViewProxy') - ]) + ->setArguments( + [ + 'eID' => 'tx_dlf_pageview_proxy', + 'url' => $url, + 'uHash' => GeneralUtility::hmac($url, 'PageViewProxy') + ] + ) ->build(); } @@ -295,7 +269,8 @@ protected function sanitizeRequestData(): void * * @return void */ - protected function setPage(): void { + protected function setPage(): void + { if (!empty($this->requestData['logicalPage'])) { $this->requestData['page'] = $this->document->getCurrentDocument()->getPhysicalPage($this->requestData['logicalPage']); // The logical page parameter should not appear again @@ -312,7 +287,8 @@ protected function setPage(): void { * * @return void */ - protected function setDefaultPage(): void { + protected function setDefaultPage(): void + { // Set default values if not set. // $this->requestData['page'] may be integer or string (physical structure @ID) if ( @@ -424,4 +400,65 @@ protected function buildSimplePagination(PaginationInterface $pagination, Pagina 'pagesG' => $pages ]; } + + /** + * Get document from repository by uid. + * + * @access private + * + * @param int $documentId The document's UID + * + * @return AbstractDocument + */ + private function getDocumentByUid(int $documentId) + { + $doc = null; + $this->document = $this->documentRepository->findOneByIdAndSettings($documentId); + + if ($this->document) { + $doc = AbstractDocument::getInstance($this->document->getLocation(), $this->settings, true); + } else { + $this->logger->error('Invalid UID "' . $documentId . '" or PID "' . $this->settings['storagePid'] . '" for document loading'); + } + + return $doc; + } + + /** + * Get document by URL. + * + * @access private + * + * @param string $documentId The document's URL + * + * @return AbstractDocument + */ + private function getDocumentByUrl(string $documentId) + { + $doc = AbstractDocument::getInstance($documentId, $this->settings, true); + + if ($doc !== null) { + if ($doc->recordId) { + // find document from repository by recordId + $docFromRepository = $this->documentRepository->findOneByRecordId($doc->recordId); + if ($docFromRepository !== null) { + $this->document = $docFromRepository; + } else { + // create new dummy Document object + $this->document = GeneralUtility::makeInstance(Document::class); + } + } + + // Make sure configuration PID is set when applicable + if ($doc->cPid == 0) { + $doc->cPid = max((int) $this->settings['storagePid'], 0); + } + + $this->document->setLocation($documentId); + } else { + $this->logger->error('Invalid location given "' . $documentId . '" for document loading'); + } + + return $doc; + } } From 72d34a47d9f2b6fd26f9dac6df659f76bd8b484e Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:42:39 +0100 Subject: [PATCH 09/17] [MAINTENANCE] Improvements in MetadataController class (#1135) Co-authored-by: Sebastian Meyer --- Classes/Controller/MetadataController.php | 132 +++++++++++++----- .../Private/Partials/Metadata/Entries.html | 8 +- .../ViewHelpers/StdWrapViewHelperTest.php | 4 +- 3 files changed, 103 insertions(+), 41 deletions(-) diff --git a/Classes/Controller/MetadataController.php b/Classes/Controller/MetadataController.php index 70666414e7..c75548c0b4 100644 --- a/Classes/Controller/MetadataController.php +++ b/Classes/Controller/MetadataController.php @@ -14,7 +14,6 @@ use Kitodo\Dlf\Common\AbstractDocument; use Kitodo\Dlf\Common\Helper; use Kitodo\Dlf\Common\IiifManifest; -use Kitodo\Dlf\Common\MetsDocument; use Kitodo\Dlf\Domain\Repository\CollectionRepository; use Kitodo\Dlf\Domain\Repository\MetadataRepository; use Kitodo\Dlf\Domain\Repository\StructureRepository; @@ -160,9 +159,11 @@ protected function printMetadata(array $metadata): void $this->view->assign('iiifData', $this->buildIiifData($metadata)); } else { // findBySettings also sorts entries by the `sorting` field - $metadataResult = $this->metadataRepository->findBySettings([ - 'is_listed' => !$this->settings['showFull'], - ]); + $metadataResult = $this->metadataRepository->findBySettings( + [ + 'is_listed' => !$this->settings['showFull'], + ] + ); foreach ($metadata as $i => $section) { @@ -172,8 +173,7 @@ protected function printMetadata(array $metadata): void $this->parseMetadata($i, $name, $value, $metadata); if (is_array($metadata[$i][$name])) { - $metadata[$i][$name] = array_values(array_filter($metadata[$i][$name], function($metadataValue) - { + $metadata[$i][$name] = array_values(array_filter($metadata[$i][$name], function ($metadataValue) { return !empty($metadataValue); })); } @@ -185,7 +185,7 @@ protected function printMetadata(array $metadata): void $this->view->assign('documentMetadataSections', $metadata); $this->view->assign('configMetadata', $metadataResult); $this->view->assign('separator', $this->settings['separator']); - $this->view->assign('metaCObjData', $this->buildMetaCObjData($metadata)); + $this->view->assign('metaConfigObjectData', $this->buildMetaConfigObjectData($metadata)); } } @@ -267,21 +267,21 @@ private function buildIiifDataGroup(string $label, string $value): array * * @return array The raw metadata array ready for output */ - private function buildMetaCObjData(array $metadata): array + private function buildMetaConfigObjectData(array $metadata): array { - $metaCObjData = []; + $metaConfigObjectData = []; foreach ($metadata as $i => $section) { - $metaCObjData[$i] = []; + $metaConfigObjectData[$i] = []; foreach ($section as $name => $value) { - $metaCObjData[$i][$name] = is_array($value) + $metaConfigObjectData[$i][$name] = is_array($value) ? implode($this->settings['separator'], $value) : $value; } } - return $metaCObjData; + return $metaConfigObjectData; } /** @@ -302,7 +302,7 @@ private function buildUrlFromMetadata(array $metadata): array $details = $this->currentDocument->getLogicalStructure($section['_id']); $buildUrl[$i]['title'] = [ 'id' => $this->document->getUid(), - 'page' => (!empty($details['points']) ? intval($details['points']) : 1), + 'page' => (!empty($details['points']) ? (int) $details['points'] : 1), 'targetPid' => (!empty($this->settings['targetPid']) ? $this->settings['targetPid'] : 0), ]; } @@ -356,34 +356,16 @@ private function parseMetadata(int $i, string $name, $value, array &$metadata) : { if ($name == 'title') { // Get title of parent document if needed. - if (empty(implode('', $value)) && $this->settings['getTitle'] && $this->document->getPartof()) { - $superiorTitle = AbstractDocument::getTitle($this->document->getPartof(), true); - if (!empty($superiorTitle)) { - $metadata[$i][$name] = ['[' . $superiorTitle . ']']; - } - } + $this->parseParentTitle($i, $value, $metadata); } elseif ($name == 'owner' && empty($value)) { // no owner is found by metadata records --> take the one associated to the document - $library = $this->document->getOwner(); - if ($library) { - $metadata[$i][$name][0] = $library->getLabel(); - } + $this->parseOwner($i, $metadata); } elseif ($name == 'type' && !empty($value)) { // Translate document type. - $structure = $this->structureRepository->findOneByIndexName($metadata[$i][$name][0]); - if ($structure) { - $metadata[$i][$name][0] = $structure->getLabel(); - } + $this->parseType($i, $metadata); } elseif ($name == 'collection' && !empty($value)) { // Check if collections isn't hidden. - $j = 0; - foreach ($value as $entry) { - $collection = $this->collectionRepository->findOneByIndexName($entry); - if ($collection) { - $metadata[$i][$name][$j] = $collection->getLabel() ? : ''; - $j++; - } - } + $this->parseCollections($i, $value, $metadata); } elseif ($name == 'language' && !empty($value)) { // Translate ISO 639 language code. foreach ($metadata[$i][$name] as &$langValue) { @@ -392,6 +374,86 @@ private function parseMetadata(int $i, string $name, $value, array &$metadata) : } } + /** + * Parse title of parent document if needed. + * + * @access private + * + * @param int $i The index of metadata array + * @param mixed $value The value of section in metadata array + * @param array $metadata The metadata array passed as reference + * + * @return void + */ + private function parseParentTitle(int $i, $value, array &$metadata) : void + { + if (empty(implode('', $value)) && $this->settings['getTitle'] && $this->document->getPartof()) { + $superiorTitle = AbstractDocument::getTitle($this->document->getPartof(), true); + if (!empty($superiorTitle)) { + $metadata[$i]['title'] = ['[' . $superiorTitle . ']']; + } + } + } + + /** + * Parse owner if no owner is found by metadata records. Take the one associated to the document. + * + * @access private + * + * @param int $i The index of metadata array + * @param array $metadata The metadata array passed as reference + * + * @return void + */ + private function parseOwner(int $i, array &$metadata) : void + { + $library = $this->document->getOwner(); + if ($library) { + $metadata[$i]['owner'][0] = $library->getLabel(); + } + } + + /** + * Parse type - translate document type. + * + * @access private + * + * @param int $i The index of metadata array + * @param array $metadata The metadata array passed as reference + * + * @return void + */ + private function parseType(int $i, array &$metadata) : void + { + $structure = $this->structureRepository->findOneByIndexName($metadata[$i]['type'][0]); + if ($structure) { + $metadata[$i]['type'][0] = $structure->getLabel(); + } + } + + /** + * Parse collections - check if collections isn't hidden. + * + * @access private + * + * @param int $i The index of metadata array + * @param mixed $value The value of section in metadata array + * @param array $metadata The metadata array passed as reference + * + * @return void + */ + private function parseCollections(int $i, $value, array &$metadata) : void + { + $j = 0; + foreach ($value as $entry) { + $collection = $this->collectionRepository->findOneByIndexName($entry); + if ($collection) { + $metadata[$i]['collection'][$j] = $collection->getLabel() ? : ''; + $j++; + } + } + } + /** * Get metadata for given id array. * diff --git a/Resources/Private/Partials/Metadata/Entries.html b/Resources/Private/Partials/Metadata/Entries.html index cdfe703d2a..0eff906e19 100644 --- a/Resources/Private/Partials/Metadata/Entries.html +++ b/Resources/Private/Partials/Metadata/Entries.html @@ -14,7 +14,7 @@ data-namespace-typo3-fluid="true"> {configObject.wrap -> kitodo:metadataWrapVariable(name: 'metadataWrap')} - + @@ -27,15 +27,15 @@ - {value} + {value} - - {configObject.label} + + {configObject.label} {wrappedValues -> f:format.raw()} diff --git a/Tests/Functional/ViewHelpers/StdWrapViewHelperTest.php b/Tests/Functional/ViewHelpers/StdWrapViewHelperTest.php index af9cdf16ad..ed24b6bab5 100644 --- a/Tests/Functional/ViewHelpers/StdWrapViewHelperTest.php +++ b/Tests/Functional/ViewHelpers/StdWrapViewHelperTest.php @@ -47,8 +47,8 @@ public function renderWithStdWrap(): void $view->setTemplateSource( ' - - Label + + Label

Title

Text

' From 31a2ae59928bd1a06848eb17d3c4b201c1057b4a Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:48:29 +0100 Subject: [PATCH 10/17] [MAINTENANCE] Improvements in CalendarController class (#1137) Co-authored-by: Sebastian Meyer --- Classes/Controller/CalendarController.php | 156 +++++++++++++--------- 1 file changed, 96 insertions(+), 60 deletions(-) diff --git a/Classes/Controller/CalendarController.php b/Classes/Controller/CalendarController.php index ddc853c4e0..4b52a68a63 100644 --- a/Classes/Controller/CalendarController.php +++ b/Classes/Controller/CalendarController.php @@ -67,7 +67,7 @@ public function mainAction(): void // Load current document. $this->loadDocument(); - if ($this->document === null) { + if ($this->isDocMissing()) { // Quit without doing anything if required variables are not set. return; } @@ -171,7 +171,7 @@ public function yearsAction(): void if (empty($yearLabel)) { // if neither order nor orderlabel is set, use the id... - $yearLabel = (string)$id; + $yearLabel = (string) $id; } $years[] = [ @@ -284,74 +284,110 @@ protected function getCalendarYear(array &$calendarData, array $calendarIssuesBy foreach ($calendarIssuesByMonth[$currentMonth] as $id => $day) { if ($id == date('j', $currentDayTime)) { $dayLinks = $id; - foreach ($day as $issue) { - $dayLinkLabel = empty($issue['title']) ? strftime('%x', $currentDayTime) : $issue['title']; - - $dayLinksText[] = [ - 'documentId' => $issue['uid'], - 'text' => $dayLinkLabel - ]; - - // Save issue for list view. - $this->allIssues[$currentDayTime][] = [ - 'documentId' => $issue['uid'], - 'text' => $dayLinkLabel - ]; - } + $dayLinksText = array_merge($dayLinksText, $this->getDayLinksText($day, $currentDayTime)); } } $dayLinkDiv = $dayLinksText; } - switch (strftime('%w', strtotime('+ ' . $k . ' Day', $firstDayOfWeek))) { - case '0': - $calendarData[$key]['week'][$j]['DAYSUN']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYSUN']['issues'] = $dayLinkDiv; - } - break; - case '1': - $calendarData[$key]['week'][$j]['DAYMON']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYMON']['issues'] = $dayLinkDiv; - } - break; - case '2': - $calendarData[$key]['week'][$j]['DAYTUE']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYTUE']['issues'] = $dayLinkDiv; - } - break; - case '3': - $calendarData[$key]['week'][$j]['DAYWED']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYWED']['issues'] = $dayLinkDiv; - } - break; - case '4': - $calendarData[$key]['week'][$j]['DAYTHU']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYTHU']['issues'] = $dayLinkDiv; - } - break; - case '5': - $calendarData[$key]['week'][$j]['DAYFRI']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYFRI']['issues'] = $dayLinkDiv; - } - break; - case '6': - $calendarData[$key]['week'][$j]['DAYSAT']['dayValue'] = strftime('%d', $currentDayTime); - if ((int) $dayLinks === (int) date('j', $currentDayTime)) { - $calendarData[$key]['week'][$j]['DAYSAT']['issues'] = $dayLinkDiv; - } - break; - } + $this->fillCalendar($calendarData[$key]['week'][$j], $currentDayTime, $dayLinks, $dayLinkDiv, $firstDayOfWeek, $k); } } } } } + /** + * Get text links for given day. + * + * @access private + * + * @param array $day all issues for given day + * @param int $currentDayTime + * + * @return array all issues for given day as text links + */ + private function getDayLinksText(array $day, int $currentDayTime): array + { + $dayLinksText = []; + foreach ($day as $issue) { + $dayLinkLabel = empty($issue['title']) ? strftime('%x', $currentDayTime) : $issue['title']; + + $dayLinksText[] = [ + 'documentId' => $issue['uid'], + 'text' => $dayLinkLabel + ]; + + // Save issue for list view. + $this->allIssues[$currentDayTime][] = [ + 'documentId' => $issue['uid'], + 'text' => $dayLinkLabel + ]; + } + return $dayLinksText; + } + + /** + * Fill calendar. + * + * @access private + * + * @param array &$calendarData calendar passed by reference + * @param int $currentDayTime + * @param string $dayLinks + * @param array $dayLinkDiv + * @param int $firstDayOfWeek + * @param int $k + * + * @return void + */ + private function fillCalendar(array &$calendarData, int $currentDayTime, string $dayLinks, array $dayLinkDiv, int $firstDayOfWeek, int $k): void + { + switch (strftime('%w', strtotime('+ ' . $k . ' Day', $firstDayOfWeek))) { + case '0': + $this->fillDay($calendarData, $currentDayTime, 'DAYSUN', $dayLinks, $dayLinkDiv); + break; + case '1': + $this->fillDay($calendarData, $currentDayTime, 'DAYMON', $dayLinks, $dayLinkDiv); + break; + case '2': + $this->fillDay($calendarData, $currentDayTime, 'DAYTUE', $dayLinks, $dayLinkDiv); + break; + case '3': + $this->fillDay($calendarData, $currentDayTime, 'DAYWED', $dayLinks, $dayLinkDiv); + break; + case '4': + $this->fillDay($calendarData, $currentDayTime, 'DAYTHU', $dayLinks, $dayLinkDiv); + break; + case '5': + $this->fillDay($calendarData, $currentDayTime, 'DAYFRI', $dayLinks, $dayLinkDiv); + break; + case '6': + $this->fillDay($calendarData, $currentDayTime, 'DAYSAT', $dayLinks, $dayLinkDiv); + break; + } + } + + /** + * Fill day. + * + * @access private + * + * @param array &$calendarData calendar passed by reference + * @param int $currentDayTime + * @param string $day + * @param string $dayLinks + * @param array $dayLinkDiv + * + * @return void + */ + private function fillDay(array &$calendarData, int $currentDayTime, string $day, string $dayLinks, array $dayLinkDiv): void + { + $calendarData[$day]['dayValue'] = strftime('%d', $currentDayTime); + if ((int) $dayLinks === (int) date('j', $currentDayTime)) { + $calendarData[$day]['issues'] = $dayLinkDiv; + } + } + /** * Build calendar for year (default) or season. * From ce7623b6565d0c48c277780b2861899da0c29c7a Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:51:08 +0100 Subject: [PATCH 11/17] [MAINTENANCE] Improvements in OaiPmhController class (#1136) Co-authored-by: Sebastian Meyer --- Classes/Controller/OaiPmhController.php | 228 ++++++++++++++++-------- 1 file changed, 153 insertions(+), 75 deletions(-) diff --git a/Classes/Controller/OaiPmhController.php b/Classes/Controller/OaiPmhController.php index 6901a24234..81c7430ef8 100644 --- a/Classes/Controller/OaiPmhController.php +++ b/Classes/Controller/OaiPmhController.php @@ -173,25 +173,25 @@ protected function getUrlParams() * Get unqualified Dublin Core data. * @see http://www.openarchives.org/OAI/openarchivesprotocol.html#dublincore * - * @access protected + * @access private * * @param array $record The full record array * * @return array The mapped metadata array */ - protected function getDcData(array $record) + private function getDublinCoreData(array $record) { $metadata = []; $metadata[] = ['dc:identifier' => $record['record_id']]; - $this->addDcData($metadata, 'dc:identifier', $record['purl']); - $this->addDcData($metadata, 'dc:identifier', $record['prod_id']); - $this->addDcData($metadata, 'dc:identifier', $record['urn']); - $this->addDcData($metadata, 'dc:title', $record['title']); - $this->addDcData($metadata, 'dc:creator', $record['author']); - $this->addDcData($metadata, 'dc:date', $record['year']); - $this->addDcData($metadata, 'dc:coverage', $record['place']); + $this->addDublinCoreData($metadata, 'dc:identifier', $record['purl']); + $this->addDublinCoreData($metadata, 'dc:identifier', $record['prod_id']); + $this->addDublinCoreData($metadata, 'dc:identifier', $record['urn']); + $this->addDublinCoreData($metadata, 'dc:title', $record['title']); + $this->addDublinCoreData($metadata, 'dc:creator', $record['author']); + $this->addDublinCoreData($metadata, 'dc:date', $record['year']); + $this->addDublinCoreData($metadata, 'dc:coverage', $record['place']); $record[] = ['dc:format' => $record['application/mets+xml']]; $record[] = ['dc:type' => $record['Text']]; @@ -202,25 +202,28 @@ protected function getDcData(array $record) $metadata[] = ['dc:relation' => $document->getRecordId()]; } } - $this->addDcData($metadata, 'dc:rights', $record['license']); - $this->addDcData($metadata, 'dc:rights', $record['terms']); - $this->addDcData($metadata, 'dc:rights', $record['restrictions']); - $this->addDcData($metadata, 'dc:rights', $record['out_of_print']); - $this->addDcData($metadata, 'dc:rights', $record['rights_info']); + $this->addDublinCoreData($metadata, 'dc:rights', $record['license']); + $this->addDublinCoreData($metadata, 'dc:rights', $record['terms']); + $this->addDublinCoreData($metadata, 'dc:rights', $record['restrictions']); + $this->addDublinCoreData($metadata, 'dc:rights', $record['out_of_print']); + $this->addDublinCoreData($metadata, 'dc:rights', $record['rights_info']); return $metadata; } /** + * Add Dublin Core data. + * * @access private * - * @param array $metadata The mapped metadata array + * @param array $metadata The mapped metadata array passed as reference * @param string $key The key to which record value should be assigned * @param string $value The key from record array * * @return void */ - private function addDcData(&$metadata, $key, $value) { + private function addDublinCoreData(&$metadata, $key, $value) + { if (!empty($value)) { $metadata[] = [$key => $value]; } @@ -363,7 +366,7 @@ protected function verbGetRecord() // Add metadata switch ($this->parameters['metadataPrefix']) { case 'oai_dc': - $document['metadata'] = $this->getDcData($document); + $document['metadata'] = $this->getDublinCoreData($document); break; case 'epicur': $document['metadata'] = $document; @@ -461,7 +464,7 @@ protected function verbListIdentifiers() return; } try { - $documentSet = $this->fetchDocumentUIDs(); + $documentSet = $this->fetchDocumentSet(); } catch (\Exception $exception) { $this->error = 'idDoesNotExist'; return; @@ -551,13 +554,13 @@ protected function verbListRecords() return; } try { - $documentSet = $this->fetchDocumentUIDs(); + $documentSet = $this->fetchDocumentSet(); } catch (\Exception $exception) { $this->error = 'idDoesNotExist'; return; } $resultSet = []; - if (is_array($documentSet)) { + if (count($documentSet) > 0) { $resultSet['elements'] = $documentSet; $resultSet['metadata'] = [ 'cursor' => 0, @@ -592,65 +595,119 @@ protected function verbListSets() * * @access protected * - * @return array|null Array of matching records + * @return array matching records or empty array if there were some errors */ - protected function fetchDocumentUIDs() + protected function fetchDocumentSet(): array { - $solr_query = ''; + $documentSet = []; + $solrQuery = ''; // Check "set" for valid value. if (!empty($this->parameters['set'])) { // For SOLR we need the index_name of the collection, // For DB Query we need the UID of the collection $result = $this->collectionRepository->getIndexNameForSolr($this->settings, $this->parameters['set']); - - if ($resArray = $result->fetchAssociative()) { + $resArray = $result->fetchAssociative(); + if ($resArray) { if ($resArray['index_query'] != "") { - $solr_query .= '(' . $resArray['index_query'] . ')'; + $solrQuery .= '(' . $resArray['index_query'] . ')'; } else { - $solr_query .= 'collection:' . '"' . $resArray['index_name'] . '"'; + $solrQuery .= 'collection:' . '"' . $resArray['index_name'] . '"'; } } else { $this->error = 'noSetHierarchy'; - return null; + return $documentSet; } } else { // If no set is specified we have to query for all collections - $solr_query .= 'collection:* NOT collection:""'; + $solrQuery .= 'collection:* NOT collection:""'; } // Check for required fields. foreach ($this->formats[$this->parameters['metadataPrefix']]['requiredFields'] as $required) { - $solr_query .= ' NOT ' . $required . ':""'; + $solrQuery .= ' NOT ' . $required . ':""'; } // toplevel="true" is always required - $solr_query .= ' AND toplevel:true'; + $solrQuery .= ' AND toplevel:true'; + + $from = $this->getFrom(); + $until = $this->getUntil($from); + + $this->checkGranularity(); + + if ($this->error === 'badArgument') { + return $documentSet; + } + + $solrQuery .= ' AND timestamp:[' . $from . ' TO ' . $until . ']'; + + $solr = Solr::getInstance($this->settings['solrcore']); + if (!$solr->ready) { + $this->logger->error('Apache Solr not available'); + return $documentSet; + } + if ((int) $this->settings['solr_limit'] > 0) { + $solr->limit = (int) $this->settings['solr_limit']; + } + // We only care about the UID in the results and want them sorted + $parameters = [ + "fields" => "uid", + "sort" => [ + "uid" => "asc" + ] + ]; + $parameters['query'] = $solrQuery; + $result = $solr->searchRaw($parameters); + if (empty($result)) { + $this->error = 'noRecordsMatch'; + return $documentSet; + } + foreach ($result as $doc) { + $documentSet[] = $doc->uid; + } + return $documentSet; + } + + /** + * Get 'from' query parameter. + * + * @access private + * + * @return string + */ + private function getFrom(): string + { $from = "*"; // Check "from" for valid value. if (!empty($this->parameters['from'])) { // Is valid format? - if ( - is_array($date_array = strptime($this->parameters['from'], '%Y-%m-%dT%H:%M:%SZ')) - || is_array($date_array = strptime($this->parameters['from'], '%Y-%m-%d')) - ) { - $timestamp = gmmktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'], - $date_array['tm_mon'] + 1, $date_array['tm_mday'], $date_array['tm_year'] + 1900); - $from = date("Y-m-d", $timestamp) . 'T' . date("H:i:s", $timestamp) . '.000Z'; + $date = $this->getDate('from'); + if (is_array($date)) { + $from = $this->getDateFromTimestamp($date, '.000Z'); } else { $this->error = 'badArgument'; - return; } } + return $from; + } + + /** + * Get 'until' query parameter. + * + * @access private + * + * @param string $from start date + * + * @return string + */ + private function getUntil(string $from): string + { $until = "*"; // Check "until" for valid value. if (!empty($this->parameters['until'])) { // Is valid format? - if ( - is_array($date_array = strptime($this->parameters['until'], '%Y-%m-%dT%H:%M:%SZ')) - || is_array($date_array = strptime($this->parameters['until'], '%Y-%m-%d')) - ) { - $timestamp = gmmktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'], - $date_array['tm_mon'] + 1, $date_array['tm_mday'], $date_array['tm_year'] + 1900); - $until = date("Y-m-d", $timestamp) . 'T' . date("H:i:s", $timestamp) . '.999Z'; + $date = $this->getDate('until'); + if (is_array($date)) { + $until = $this->getDateFromTimestamp($date, '.999Z'); if ($from != "*" && $from > $until) { $this->error = 'badArgument'; } @@ -658,7 +715,55 @@ protected function fetchDocumentUIDs() $this->error = 'badArgument'; } } - // Check "from" and "until" for same granularity. + return $until; + } + + /** + * Get date from parameter string. + * + * @access private + * + * @param string $dateType + * + * @return array|false + */ + private function getDate(string $dateType) + { + return strptime($this->parameters[$dateType], '%Y-%m-%dT%H:%M:%SZ') ?: strptime($this->parameters[$dateType], '%Y-%m-%d'); + } + + /** + * Get date from timestamp. + * + * @access private + * + * @param array $date + * @param string $end + * + * @return string + */ + private function getDateFromTimestamp(array $date, string $end): string + { + $timestamp = gmmktime( + $date['tm_hour'], + $date['tm_min'], + $date['tm_sec'], + $date['tm_mon'] + 1, + $date['tm_mday'], + $date['tm_year'] + 1900 + ); + return date("Y-m-d", $timestamp) . 'T' . date("H:i:s", $timestamp) . $end; + } + + /** + * Check "from" and "until" for same granularity. + * + * @access private + * + * @return void + */ + private function checkGranularity(): void + { if ( !empty($this->parameters['from']) && !empty($this->parameters['until']) @@ -667,33 +772,6 @@ protected function fetchDocumentUIDs() $this->error = 'badArgument'; } } - $solr_query .= ' AND timestamp:[' . $from . ' TO ' . $until . ']'; - $documentSet = []; - $solr = Solr::getInstance($this->settings['solrcore']); - if (!$solr->ready) { - $this->logger->error('Apache Solr not available'); - return $documentSet; - } - if (intval($this->settings['solr_limit']) > 0) { - $solr->limit = intval($this->settings['solr_limit']); - } - // We only care about the UID in the results and want them sorted - $parameters = [ - "fields" => "uid", - "sort" => [ - "uid" => "asc" - ] - ]; - $parameters['query'] = $solr_query; - $result = $solr->searchRaw($parameters); - if (empty($result)) { - $this->error = 'noRecordsMatch'; - return; - } - foreach ($result as $doc) { - $documentSet[] = $doc->uid; - } - return $documentSet; } /** @@ -730,7 +808,7 @@ protected function generateOutputForDocumentList(array $documentListSet) } switch ($metadataPrefix) { case 'oai_dc': - $resArray['metadata'] = $this->getDcData($resArray); + $resArray['metadata'] = $this->getDublinCoreData($resArray); break; case 'epicur': $resArray['metadata'] = $resArray; From 8c9732bfa61f5091a9d4880926bbbe800b494985 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 19:56:34 +0100 Subject: [PATCH 12/17] [MAINTENANCE] Improvements in AbstractDocument class (#1138) Co-authored-by: Sebastian Meyer --- Classes/Common/AbstractDocument.php | 69 +++++++++++++++++++---------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/Classes/Common/AbstractDocument.php b/Classes/Common/AbstractDocument.php index 2e6b443b61..5911ff2770 100644 --- a/Classes/Common/AbstractDocument.php +++ b/Classes/Common/AbstractDocument.php @@ -309,7 +309,7 @@ abstract public function getDownloadLocation(string $id): string; * @abstract * * @param string $id The "@ID" attribute of the file node (METS) or the "@id" property of the IIIF resource - * + * * @return array|null The set of file information */ abstract public function getFileInfo($id): ?array; @@ -581,7 +581,7 @@ public static function &getInstance(string $location, array $settings = [], bool } // Sanitize input. - $pid = max(intval($settings['storagePid']), 0); + $pid = max((int) $settings['storagePid'], 0); if ($documentFormat == 'METS') { $instance = new MetsDocument($pid, $location, $xml, $settings); } elseif ($documentFormat == 'IIIF') { @@ -590,7 +590,7 @@ public static function &getInstance(string $location, array $settings = [], bool $instance = new IiifManifest($pid, $location, $iiif); } - if (!is_null($instance)) { + if ($instance !== null) { self::setDocumentCache($location, $instance); } @@ -686,19 +686,7 @@ protected function getFullTextFromXml(string $id): string if (!empty($fileContent) && !empty($this->formats[$textFormat])) { $textMiniOcr = ''; if (!empty($this->formats[$textFormat]['class'])) { - $class = $this->formats[$textFormat]['class']; - // Get the raw text from class. - if ( - class_exists($class) - && ($obj = GeneralUtility::makeInstance($class)) instanceof FulltextInterface - ) { - // Load XML from file. - $ocrTextXml = Helper::getXmlFileAsString($fileContent); - $textMiniOcr = $obj->getTextAsMiniOcr($ocrTextXml); - $this->rawTextArray[$id] = $textMiniOcr; - } else { - $this->logger->warning('Invalid class/method "' . $class . '->getRawText()" for text format "' . $textFormat . '"'); - } + $textMiniOcr = $this->getRawTextFromClass($id, $fileContent, $textFormat); } $fullText = $textMiniOcr; } else { @@ -707,6 +695,38 @@ class_exists($class) return $fullText; } + /** + * Get raw text from class for given format. + * + * @access private + * + * @param $id + * @param $fileContent + * @param $textFormat + * + * @return string + */ + private function getRawTextFromClass($id, $fileContent, $textFormat): string + { + $textMiniOcr = ''; + $class = $this->formats[$textFormat]['class']; + // Get the raw text from class. + if (class_exists($class)) { + $obj = GeneralUtility::makeInstance($class); + if ($obj instanceof FulltextInterface) { + // Load XML from file. + $ocrTextXml = Helper::getXmlFileAsString($fileContent); + $textMiniOcr = $obj->getTextAsMiniOcr($ocrTextXml); + $this->rawTextArray[$id] = $textMiniOcr; + } else { + $this->logger->warning('Invalid class/method "' . $class . '->getRawText()" for text format "' . $textFormat . '"'); + } + } else { + $this->logger->warning('Class "' . $class . ' does not exists for "' . $textFormat . ' text format"'); + } + return $textMiniOcr; + } + /** * Get format of the OCR full text * @@ -744,7 +764,7 @@ public static function getTitle(int $uid, bool $recursive = false): string { $title = ''; // Sanitize input. - $uid = max(intval($uid), 0); + $uid = max($uid, 0); if ($uid) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tx_dlf_documents'); @@ -762,7 +782,8 @@ public static function getTitle(int $uid, bool $recursive = false): string ->setMaxResults(1) ->execute(); - if ($resArray = $result->fetchAssociative()) { + $resArray = $result->fetchAssociative(); + if ($resArray) { // Get title information. $title = $resArray['title']; $partof = $resArray['partof']; @@ -770,7 +791,7 @@ public static function getTitle(int $uid, bool $recursive = false): string if ( $recursive && empty($title) - && intval($partof) + && (int) $partof && $partof != $uid ) { $title = self::getTitle($partof, true); @@ -950,7 +971,8 @@ public function registerNamespaces(&$obj): void * * @return array */ - protected function initializeMetadata(string $format): array { + protected function initializeMetadata(string $format): array + { return [ 'title' => [], 'title_sorting' => [], @@ -1161,7 +1183,7 @@ protected function magicGetTableOfContents(): array */ protected function _setCPid(int $value): void { - $this->cPid = max(intval($value), 0); + $this->cPid = max($value, 0); } /** @@ -1258,7 +1280,7 @@ public function __set(string $var, $value): void */ private static function getDocumentCache(string $location) { - $cacheIdentifier = md5($location); + $cacheIdentifier = hash('md5', $location); $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('tx_dlf_doc'); $cacheHit = $cache->get($cacheIdentifier); @@ -1279,11 +1301,10 @@ private static function getDocumentCache(string $location) */ private static function setDocumentCache(string $location, AbstractDocument $currentDocument): void { - $cacheIdentifier = md5($location); + $cacheIdentifier = hash('md5', $location); $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('tx_dlf_doc'); // Save value in cache $cache->set($cacheIdentifier, $currentDocument); } - } From d0afeca9ad3edd6c746d3959983b3185aa1eac92 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 20:00:47 +0100 Subject: [PATCH 13/17] [MAINTENANCE] Improvements in Mods class (#1139) Co-authored-by: Sebastian Meyer --- Classes/Format/Mods.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Classes/Format/Mods.php b/Classes/Format/Mods.php index cc63962e91..eeb4f0b181 100644 --- a/Classes/Format/Mods.php +++ b/Classes/Format/Mods.php @@ -21,7 +21,7 @@ * * @package TYPO3 * @subpackage dlf - * + * * @access public */ class Mods implements MetadataInterface @@ -323,9 +323,10 @@ private function getPlaces(): void private function getYears(): void { // Get "year_sorting". - if (($years_sorting = $this->xml->xpath('./mods:originInfo[not(./mods:edition="[Electronic ed.]")]/mods:dateOther[@type="order" and @encoding="w3cdtf"]'))) { - foreach ($years_sorting as $year_sorting) { - $this->metadata['year_sorting'][0] = intval($year_sorting); + $yearsSorting = $this->xml->xpath('./mods:originInfo[not(./mods:edition="[Electronic ed.]")]/mods:dateOther[@type="order" and @encoding="w3cdtf"]'); + if ($yearsSorting) { + foreach ($yearsSorting as $yearSorting) { + $this->metadata['year_sorting'][0] = (int) $yearSorting; } } // Get "year" and "year_sorting" if not specified separately. @@ -339,14 +340,14 @@ private function getYears(): void foreach ($years as $year) { $this->metadata['year'][] = (string) $year; if (!$this->metadata['year_sorting'][0]) { - $year_sorting = str_ireplace('x', '5', preg_replace('/[^\d.x]/i', '', (string) $year)); + $yearSorting = str_ireplace('x', '5', preg_replace('/[^\d.x]/i', '', (string) $year)); if ( - strpos($year_sorting, '.') - || strlen($year_sorting) < 3 + strpos($yearSorting, '.') + || strlen($yearSorting) < 3 ) { - $year_sorting = ((intval(trim($year_sorting, '.')) - 1) * 100) + 50; + $yearSorting = (((int) trim($yearSorting, '.') - 1) * 100) + 50; } - $this->metadata['year_sorting'][0] = intval($year_sorting); + $this->metadata['year_sorting'][0] = (int) $yearSorting; } } } From 0933be493425aed36765c9e7207ddefbb80548ac Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 20:05:10 +0100 Subject: [PATCH 14/17] [BUGFIX] Add fallback to location if there is no record id in METS file (#1141) Co-authored-by: Sebastian Meyer --- .github/phpstan.neon | 1 + Classes/Command/IndexCommand.php | 46 +++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/.github/phpstan.neon b/.github/phpstan.neon index fa63a34816..750315ed9e 100644 --- a/.github/phpstan.neon +++ b/.github/phpstan.neon @@ -5,6 +5,7 @@ parameters: - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findByIsSortable\(\)\.#' - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByFeUserId\(\)\.#' - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByIndexName\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByLocation\(\)\.#' - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByPid\(\)\.#' - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByRecordId\(\)\.#' - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByRoot\(\)\.#' diff --git a/Classes/Command/IndexCommand.php b/Classes/Command/IndexCommand.php index f719f92add..f54e56a44f 100644 --- a/Classes/Command/IndexCommand.php +++ b/Classes/Command/IndexCommand.php @@ -168,19 +168,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else if (GeneralUtility::isValidUrl($input->getOption('doc'))) { $doc = AbstractDocument::getInstance($input->getOption('doc'), ['storagePid' => $this->storagePid], true); - if ($doc->recordId) { - $document = $this->documentRepository->findOneByRecordId($doc->recordId); - } - - if ($document === null) { - // create new Document object - $document = GeneralUtility::makeInstance(Document::class); - } - - // now there must exist a document object - if ($document) { - $document->setLocation($input->getOption('doc')); - } + $document = $this->getDocumentFromUrl($doc, $input->getOption('doc')); } if ($doc === null) { @@ -208,4 +196,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + /** + * Get document from given URL. Find it in database, if not found create the new one. + * + * @access private + * + * @param AbstractDocument $doc + * @param string $url + * + * @return Document + */ + private function getDocumentFromUrl($doc, string $url): Document + { + $document = null; + + if ($doc->recordId) { + $document = $this->documentRepository->findOneByRecordId($doc->recordId); + } else { + $document = $this->documentRepository->findOneByLocation($url); + } + + if ($document === null) { + // create new Document object + $document = GeneralUtility::makeInstance(Document::class); + } + + // now there must exist a document object + if ($document) { + $document->setLocation($url); + } + + return $document; + } } From 620718db8d3bdee82665c93b95de8ab20e9970b5 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 20:08:21 +0100 Subject: [PATCH 15/17] [MAINTENANCE] Remove usage of getDocumentType with 3D View plugin (#1144) Co-authored-by: Sebastian Meyer --- Resources/Private/Templates/View3D/Main.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Private/Templates/View3D/Main.html b/Resources/Private/Templates/View3D/Main.html index 34312f654d..5fa402fc1d 100644 --- a/Resources/Private/Templates/View3D/Main.html +++ b/Resources/Private/Templates/View3D/Main.html @@ -13,11 +13,13 @@ xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> -

- - - - - - + +

+ + + + + + +
From f9dae4ab82f382b4b63ee8b41e096e29543e67dc Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 20:11:54 +0100 Subject: [PATCH 16/17] [MAINTENANCE] Remove deprecated commands configuration (#1145) Co-authored-by: Sebastian Meyer --- Configuration/Commands.php | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 Configuration/Commands.php diff --git a/Configuration/Commands.php b/Configuration/Commands.php deleted file mode 100644 index d46d08180c..0000000000 --- a/Configuration/Commands.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * This file is part of the Kitodo and TYPO3 projects. - * - * @license GNU General Public License version 3 or later. - * For the full copyright and license information, please read the - * LICENSE.txt file that was distributed with this source code. - */ - -/** - * Commands to be executed by TYPO3, where the key of the array - * is the name of the command (to be called as the first argument after "typo3"). - * Required parameter is the "class" of the command which needs to be a subclass - * of \Symfony\Component\Console\Command\Command. - * - * This file is deprecated in TYPO3 v10 and will be removed in TYPO3 v11. - * See Deprecation: #89139 - Console Commands configuration format Commands.php - * https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.3/Deprecation-89139-ConsoleCommandsConfigurationFormatCommandsPhp.html - */ -return [ - 'kitodo:harvest' => [ - 'class' => Kitodo\Dlf\Command\HarvestCommand::class - ], - 'kitodo:index' => [ - 'class' => Kitodo\Dlf\Command\IndexCommand::class - ], - 'kitodo:reindex' => [ - 'class' => Kitodo\Dlf\Command\ReindexCommand::class - ] -]; From 93a4b45ecb28bf0edf7af3b207bcb5dbcfedce84 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 26 Jan 2024 20:34:50 +0100 Subject: [PATCH 17/17] [MAINTENANCE] Replace exits and returns in commands' execute functions (#1146) Co-authored-by: Sebastian Meyer --- Classes/Command/HarvestCommand.php | 16 ++++++++-------- Classes/Command/IndexCommand.php | 14 +++++++------- Classes/Command/ReindexCommand.php | 14 +++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Classes/Command/HarvestCommand.php b/Classes/Command/HarvestCommand.php index e62fa04dfd..b3e565538e 100644 --- a/Classes/Command/HarvestCommand.php +++ b/Classes/Command/HarvestCommand.php @@ -112,7 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->storagePid == 0) { $io->error('ERROR: No valid PID (' . $this->storagePid . ') given.'); - exit(1); + return BaseCommand::FAILURE; } if ( @@ -133,15 +133,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (empty($output_solrCores)) { $io->error('ERROR: No valid Solr core ("' . $input->getOption('solr') . '") given. No valid cores found on PID ' . $this->storagePid . ".\n"); - exit(1); + return BaseCommand::FAILURE; } else { $io->error('ERROR: No valid Solr core ("' . $input->getOption('solr') . '") given. ' . "Valid cores are (:):\n" . implode("\n", $output_solrCores) . "\n"); - exit(1); + return BaseCommand::FAILURE; } } } else { $io->error('ERROR: Required parameter --solr|-s is missing or array.'); - exit(1); + return BaseCommand::FAILURE; } if (MathUtility::canBeInterpretedAsInteger($input->getOption('lib'))) { @@ -152,11 +152,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $baseUrl = $this->owner->getOaiBase(); } else { $io->error('ERROR: Required parameter --lib|-l is not a valid UID.'); - exit(1); + return BaseCommand::FAILURE; } if (!GeneralUtility::isValidUrl($baseUrl)) { $io->error('ERROR: No valid OAI Base URL set for library with given UID ("' . $input->getOption('lib') . '").'); - exit(1); + return BaseCommand::FAILURE; } else { try { $oai = Endpoint::build($baseUrl); @@ -198,7 +198,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (empty($set)) { $io->error('ERROR: OAI interface does not provide a set with given setSpec ("' . $input->getOption('set') . '").'); - exit(1); + return BaseCommand::FAILURE; } } @@ -261,7 +261,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->success('All done!'); - return 0; + return BaseCommand::SUCCESS; } /** diff --git a/Classes/Command/IndexCommand.php b/Classes/Command/IndexCommand.php index f54e56a44f..c8dbd4ce65 100644 --- a/Classes/Command/IndexCommand.php +++ b/Classes/Command/IndexCommand.php @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->storagePid == 0) { $io->error('ERROR: No valid PID (' . $this->storagePid . ') given.'); - exit(1); + return BaseCommand::FAILURE; } if ( @@ -117,15 +117,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (empty($output_solrCores)) { $io->error('ERROR: No valid Solr core ("' . $input->getOption('solr') . '") given. No valid cores found on PID ' . $this->storagePid . ".\n"); - exit(1); + return BaseCommand::FAILURE; } else { $io->error('ERROR: No valid Solr core ("' . $input->getOption('solr') . '") given. ' . "Valid cores are (:):\n" . implode("\n", $output_solrCores) . "\n"); - exit(1); + return BaseCommand::FAILURE; } } } else { $io->error('ERROR: Required parameter --solr|-s is missing or array.'); - exit(1); + return BaseCommand::FAILURE; } if ( @@ -137,7 +137,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ) ) { $io->error('ERROR: Required parameter --doc|-d is not a valid document UID or URL.'); - exit(1); + return BaseCommand::FAILURE; } if (!empty($input->getOption('owner'))) { @@ -173,7 +173,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($doc === null) { $io->error('ERROR: Document "' . $input->getOption('doc') . '" could not be loaded.'); - exit(1); + return BaseCommand::FAILURE; } $document->setSolrcore($solrCoreUid); @@ -193,7 +193,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->success('All done!'); - return 0; + return BaseCommand::SUCCESS; } /** diff --git a/Classes/Command/ReindexCommand.php b/Classes/Command/ReindexCommand.php index 19df87ff13..5ebdc92136 100644 --- a/Classes/Command/ReindexCommand.php +++ b/Classes/Command/ReindexCommand.php @@ -103,7 +103,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->storagePid == 0) { $io->error('ERROR: No valid PID (' . $this->storagePid . ') given.'); - exit(1); + return BaseCommand::FAILURE; } if ( @@ -121,15 +121,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (empty($output_solrCores)) { $io->error('ERROR: No valid Solr core ("' . $input->getOption('solr') . '") given. No valid cores found on PID ' . $this->storagePid . ".\n"); - exit(1); + return BaseCommand::FAILURE; } else { $io->error('ERROR: No valid Solr core ("' . $input->getOption('solr') . '") given. ' . "Valid cores are (:):\n" . implode("\n", $output_solrCores) . "\n"); - exit(1); + return BaseCommand::FAILURE; } } } else { $io->error('ERROR: Required parameter --solr|-s is missing or array.'); - exit(1); + return BaseCommand::FAILURE; } if (!empty($input->getOption('owner'))) { @@ -152,13 +152,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int // "coll" may be a single integer or a comma-separated list of integers. if (empty(array_filter(GeneralUtility::intExplode(',', $input->getOption('coll'), true)))) { $io->error('ERROR: Parameter --coll|-c is not a valid comma-separated list of collection UIDs.'); - exit(1); + return BaseCommand::FAILURE; } // Get all documents of given collections. $documents = $this->documentRepository->findAllByCollectionsLimited(GeneralUtility::intExplode(',', $input->getOption('coll'), true), 0); } else { $io->error('ERROR: One of parameters --all|-a or --coll|-c must be given.'); - exit(1); + return BaseCommand::FAILURE; } foreach ($documents as $id => $document) { @@ -187,6 +187,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->success('All done!'); - return 0; + return BaseCommand::SUCCESS; } }