Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
c998a21
Add explanation about `addHighlightField` method
Jul 27, 2022
79dbeb2
Add Basic JSON Representation for Document and First Navigation Button
Aug 22, 2022
d0d2d6b
Refactor: Format code
Aug 22, 2022
4a912d1
Refactor: Move JS from template to new Navigation.js
Aug 22, 2022
c099889
Make loaded document a global variable
Aug 22, 2022
b227b62
Handle all forward/back navigation buttons
Aug 22, 2022
5e90506
Enable/disable nav buttons on client, + refactor
Aug 22, 2022
6d34044
Create WIP documentation page
Aug 23, 2022
3a74fba
Include page object in pageChanged event, start typing
Aug 23, 2022
8ccccc2
Update metadata table on page change
Aug 23, 2022
7fab624
Add Data Structure for Client Side Fulltext Reload
Aug 23, 2022
9a9326e
Adapt Fulltext Datastructure to Metadata and Document
Aug 23, 2022
9c8a441
Update navigation select
beatrycze-volk Aug 23, 2022
87c9c99
Update links for navigation buttons
beatrycze-volk Aug 23, 2022
a94ae3d
Fix reference to fulltextControl
Aug 23, 2022
04e2919
Set navigation select value on page change
Aug 23, 2022
76ab62c
Fix: First load data in `Doc::getLogicalSectionsOnPage`
Aug 23, 2022
ce3d1b1
Start implementing history/browser navigation
Aug 23, 2022
c5caec2
Refactor: In document JSON, store pages in subkey
Aug 23, 2022
5c99a3d
Refactor: Wrap image URL & MIME type into subkey
Aug 23, 2022
bf8538b
Extract Doc::getPageLink()
Aug 24, 2022
657e2d1
List download URLs in document JSON
Aug 24, 2022
bebad1f
Update page download links on page change
Aug 24, 2022
2b55cc6
Refactor: Avoid manual JSON encoding
Aug 24, 2022
42a78d9
Include all file groups/URLs in document JSON
Aug 24, 2022
2e25344
Update image download links on page change
Aug 24, 2022
bd29251
Start implementing client-side double page mode
Aug 24, 2022
6431ac8
Simplify: Remove pageObj and target from event detail
Aug 24, 2022
76ce6fe
Simplify: Merge `pageChanged` and `configChanged` events
Aug 24, 2022
e2f28f0
Fix back buttons when using route enhancers
Aug 24, 2022
984b511
Generalize URL generation to work with slugs
Aug 24, 2022
7ec78a8
Fix pdf and image download links
Aug 25, 2022
3db5bae
Add client side navigation from table of contents
Aug 25, 2022
4a6769c
Add separate marker for metadata list
Aug 25, 2022
b7ee1f2
Start implementing dynamic TOC expansion
Aug 25, 2022
27b0204
Update URL on page change
Aug 25, 2022
e51505e
Optimize document JSON generation for METS
Aug 25, 2022
2772b76
Fix disabling of lastPage navigation button
Aug 25, 2022
2cf90ce
Cache pageview layers
Aug 25, 2022
316ca7d
Update Page URL Template for Use with Page Grid
Aug 26, 2022
cc37160
Add document plugin for JSON representation
Aug 26, 2022
c358075
Move document initialisation to document plugin
Aug 26, 2022
63cec80
Move document handling to dlfController via event
Aug 26, 2022
ecc94e5
Start moving getVisiblePages to dlfController
Aug 26, 2022
49f25ef
Use `getVisiblePages()` of `dlfController`
Aug 27, 2022
cd2c260
Refactor: Extract `dlfController::eventTarget`
Aug 27, 2022
4aa3cc2
Refactor: Don't dispatch `stateChanged` manually
Aug 27, 2022
1a662e1
Refactor: Dissolve global `tx_dlf_loaded`
Aug 27, 2022
26dbe0c
[EXT] Memoize `MetsDocument::getStructureDepth()`
Aug 27, 2022
b9d78eb
Fetch prerendererd metadata dynamically
Aug 27, 2022
a38e921
Convert `TODO` to `TODO(client-side)`
Aug 27, 2022
28a78a1
Update and extend documentation
Aug 29, 2022
6aa38b5
Extend documentation
Aug 30, 2022
4f4c2fd
Add and update notes about URL generation
Aug 30, 2022
949b89d
Fix Codacy warnings
beatrycze-volk Jan 20, 2023
f5724d1
Fix toArray method in Doc class
beatrycze-volk Feb 3, 2023
9cb28be
Adjust DocumentController
beatrycze-volk Feb 6, 2023
9142202
Move nonProxyMimeTypes to config
beatrycze-volk Feb 6, 2023
db15094
Implement getAllFiles for IIIFManifest
beatrycze-volk Feb 28, 2023
3718ad9
Remove unused manifest variable
beatrycze-volk Feb 28, 2023
860cc21
Fix intend in css
beatrycze-volk Mar 8, 2023
5a9a1c4
Improve Metadata script
beatrycze-volk Mar 8, 2023
2570550
Improve Navigation script
beatrycze-volk Mar 8, 2023
be7d6a1
Assign fulltextEntry directly
beatrycze-volk Mar 28, 2023
5a42f7b
Fix comment
beatrycze-volk Mar 28, 2023
ab80f63
Allow console logs
beatrycze-volk Mar 29, 2023
9e9a5fc
Activate full text highlight if full text is active
beatrycze-volk Mar 30, 2023
efce0a0
Fix errors marked by PHPStan
beatrycze-volk Oct 25, 2023
511b5d2
Fix Codacy errors
beatrycze-volk Oct 25, 2023
fd124b2
Fix error: Direct use of $GLOBALS Superglobal detected
beatrycze-volk Nov 20, 2023
9851461
Fix documentation build
beatrycze-volk Aug 2, 2024
e1435bf
Fix PHPStan warnings
beatrycze-volk Aug 2, 2024
db4acb1
Fix return for `getStructureDepth()` function
beatrycze-volk Feb 11, 2025
803717c
Fix Codacy warnings
beatrycze-volk Feb 12, 2025
80d0740
Display toolbox initialization only once
beatrycze-volk Apr 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

# TS/JS-Files
[*.{ts,js}]
indent_size = 2

# JSON-Files
[*.json]
indent_style = tab

# ReST-Files
[*.rst]
indent_size = 3
Expand All @@ -37,10 +29,6 @@ indent_size = 2
[*.{typoscript,tsconfig}]
indent_size = 2

# XLF-Files
[*.xlf]
indent_style = tab

# SQL-Files
[*.sql]
indent_style = tab
Expand Down
2 changes: 2 additions & 0 deletions .github/phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
parameters:
ignoreErrors:
- '#Call to an undefined method Psr\\Http\\Message\\RequestFactoryInterface::request\(\)\.#'
- '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\CanvasInterface::getImages\(\)\.#'
- '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\CanvasInterface::getOtherContent\(\)\.#'
- '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\ManifestInterface::getOriginalJsonArray\(\)\.#'
- '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\RangeInterface::getMemberRangesAndRanges\(\)\.#'
level: 5
Expand Down
6 changes: 3 additions & 3 deletions Classes/Command/BaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ protected function getParentDocumentUidForSaving(Document $document, bool $softC

if ($doc !== null && !empty($doc->parentHref)) {
// find document object by record_id of parent
$parent = AbstractDocument::getInstance($doc->parentHref, ['storagePid' => $this->storagePid], true);
$parent = AbstractDocument::getInstance($doc->parentHref, 0, ['storagePid' => $this->storagePid], true);

if ($parent->recordId) {
$parentDocument = $this->documentRepository->findOneByRecordId($parent->recordId);
Expand Down Expand Up @@ -326,7 +326,7 @@ protected function getParentDocumentUidForSaving(Document $document, bool $softC
* Add collections.
*
* @access private
*
*
* @param Document &$document
* @param array $collections
*
Expand Down Expand Up @@ -378,7 +378,7 @@ private function addCollections(Document &$document, array $collections): void
* more than 255 characters.
*
* @access private
*
*
* @param array $metadataAuthor
*
* @return string
Expand Down
2 changes: 1 addition & 1 deletion Classes/Command/DeleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ private function getDocument($input): ?Document
if (MathUtility::canBeInterpretedAsInteger($input->getOption('doc'))) {
$document = $this->documentRepository->findByUid($input->getOption('doc'));
} elseif (GeneralUtility::isValidUrl($input->getOption('doc'))) {
$doc = AbstractDocument::getInstance($input->getOption('doc'), ['storagePid' => $this->storagePid], true);
$doc = AbstractDocument::getInstance($input->getOption('doc'), 0, ['storagePid' => $this->storagePid], true);

if ($doc->recordId) {
$document = $this->documentRepository->findOneByRecordId($doc->recordId);
Expand Down
2 changes: 1 addition & 1 deletion Classes/Command/HarvestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$docLocation = $baseLocation . http_build_query($params);
// ...index the document...
$document = null;
$doc = AbstractDocument::getInstance($docLocation, ['storagePid' => $this->storagePid], true);
$doc = AbstractDocument::getInstance($docLocation, 0, ['storagePid' => $this->storagePid], true);

if ($doc === null) {
$io->warning('WARNING: Document "' . $docLocation . '" could not be loaded. Skip to next document.');
Expand Down
4 changes: 2 additions & 2 deletions Classes/Command/IndexCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io->error('ERROR: Document with UID "' . $input->getOption('doc') . '" could not be found on PID ' . $this->storagePid . ' .');
return Command::FAILURE;
} else {
$doc = AbstractDocument::getInstance($document->getLocation(), ['storagePid' => $this->storagePid], true);
$doc = AbstractDocument::getInstance($document->getLocation(), 0, ['storagePid' => $this->storagePid], true);
}

} else if (GeneralUtility::isValidUrl($input->getOption('doc'))) {
$doc = AbstractDocument::getInstance($input->getOption('doc'), ['storagePid' => $this->storagePid], true);
$doc = AbstractDocument::getInstance($input->getOption('doc'), 0, ['storagePid' => $this->storagePid], true);

$document = $this->getDocumentFromUrl($doc, $input->getOption('doc'));
}
Expand Down
2 changes: 1 addition & 1 deletion Classes/Command/ReindexCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

foreach ($documents as $id => $document) {
$doc = AbstractDocument::getInstance($document->getLocation(), ['storagePid' => $this->storagePid], true);
$doc = AbstractDocument::getInstance($document->getLocation(), 0, ['storagePid' => $this->storagePid], true);

if ($doc === null) {
$io->warning('WARNING: Document "' . $document->getLocation() . '" could not be loaded. Skip to next document.');
Expand Down
152 changes: 147 additions & 5 deletions Classes/Common/AbstractDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ abstract class AbstractDocument
*/
protected int $configPid = 0;

/**
* @access protected
* @var int This holds the page ID for the requests
*/
protected int $pageId = 0;

/**
* @access public
* @static
Expand Down Expand Up @@ -518,6 +524,27 @@ abstract protected function prepareMetadataArray(): void;
*/
abstract protected function setPreloadedDocument($preloadedDocument): bool;

/**
* Get information about all files contained in the document, or null if this information is not available.
*
* Returns an associative array of the following form:
*
* ```php
* [
* '#FILE_ID' => [
* 'url' => '...',
* 'mimetype' => '...',
* ],
* // ...
* ]
* ```
*
* @access public
*
* @return array
*/
abstract public function getAllFiles(): array;

/**
* This is a singleton class, thus an instance must be created by this method
*
Expand All @@ -526,12 +553,13 @@ abstract protected function setPreloadedDocument($preloadedDocument): bool;
* @static
*
* @param string $location The URL of XML file or the IRI of the IIIF resource
* @param int $pageId
* @param array $settings
* @param bool $forceReload Force reloading the document instead of returning the cached instance
*
* @return AbstractDocument|null Instance of this class, either MetsDocument or IiifManifest
*/
public static function &getInstance(string $location, array $settings = [], bool $forceReload = false)
public static function &getInstance(string $location, int $pageId = 0, array $settings = [], bool $forceReload = false)
{
// Create new instance depending on format (METS or IIIF) ...
$documentFormat = null;
Expand Down Expand Up @@ -570,9 +598,9 @@ public static function &getInstance(string $location, array $settings = [], bool
}

if ($documentFormat == 'METS') {
$instance = new MetsDocument($location, $xml, $settings);
$instance = new MetsDocument($location, $pageId, $xml, $settings);
} elseif ($iiif instanceof IiifResourceInterface) {
$instance = new IiifManifest($location, $iiif, $settings);
$instance = new IiifManifest($location, $pageId, $iiif);
}

if ($instance !== null) {
Expand Down Expand Up @@ -995,7 +1023,9 @@ protected function magicGetRootId(): int
{
if (!$this->rootIdLoaded) {
if ($this->parentId) {
$parent = self::getInstance((string) $this->parentId, ['storagePid' => $this->configPid]);
// TODO: Parameter $location of static method AbstractDocument::getInstance() expects string, int<min, -1>|int<1, max> given.
// @phpstan-ignore-next-line
$parent = self::getInstance($this->parentId, $this->pageId, ['storagePid' => $this->pid]);
$this->rootId = $parent->rootId;
}
$this->rootIdLoaded = true;
Expand Down Expand Up @@ -1044,17 +1074,19 @@ protected function _setConfigPid(int $value): void
* @access protected
*
* @param string $location The location URL of the XML file to parse
* @param int $pageId
* @param \SimpleXMLElement|IiifResourceInterface $preloadedDocument Either null or the \SimpleXMLElement
* or IiifResourceInterface that has been loaded to determine the basic document format.
*
* @return void
*/
protected function __construct(string $location, $preloadedDocument, array $settings = [])
protected function __construct(string $location, int $pageId, $preloadedDocument, array $settings = [])
{
// Note: Any change here might require an update in function __sleep
// of class MetsDocument and class IiifManifest, too.
$storagePid = array_key_exists('storagePid', $settings) ? max((int) $settings['storagePid'], 0) : 0;
$this->configPid = $storagePid;
$this->pageId = $pageId;
$this->useGroupsConfiguration = UseGroupsConfiguration::getInstance();
$this->setPreloadedDocument($preloadedDocument);
$this->init($location, $settings);
Expand Down Expand Up @@ -1120,4 +1152,114 @@ public function __set(string $var, $value): void
$this->$method($value);
}
}

/**
* Get IDs of logical structures that a page belongs to, indexed by depth.
*
* @param int $pageNo
* @return array
*/
public function getLogicalSectionsOnPage($pageNo)
{
$this->magicGetSmLinks();
$this->magicGetPhysicalStructure();

$ids = [];
if (!empty($this->physicalStructure[$pageNo]) && !empty($this->smLinks['p2l'][$this->physicalStructure[$pageNo]])) {
foreach ($this->smLinks['p2l'][$this->physicalStructure[$pageNo]] as $logId) {
$depth = $this->getStructureDepth($logId);
$ids[$depth][] = $logId;
}
}
ksort($ids);
reset($ids);
return $ids;
}

/**
* Get URL of download file of specified page, or the empty string if there is no such link.
*
* @param int $pageNumber
* @return string
*/
public function getPageLink($pageNumber)
{
$extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
$fileGrpsDownload = GeneralUtility::trimExplode(',', $extConf['files']['fileGrpDownload']);
// Get image link.
foreach ($fileGrpsDownload as $fileGrpDownload) {
if (!empty($this->physicalStructureInfo[$this->physicalStructure[$pageNumber]]['files'][$fileGrpDownload])) {
return $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[$pageNumber]]['files'][$fileGrpDownload]);
}
}
return '';
}

public function toArray($uriBuilder, array $config = [])
{
$this->magicGetSmLinks();
$this->magicGetPhysicalStructure();

$proxyFileGroups = $config['proxyFileGroups'] ?? [];
$forceAbsoluteUrl = $config['forceAbsoluteUrl'] ?? false;
$minPage = $config['minPage'] ?? 1;
$maxPage = $config['maxPage'] ?? $this->numPages;

$result = [
'pages' => [],
'query' => [
'minPage' => $minPage
]
];

$allFiles = $this->getAllFiles();

for ($page = $minPage; $page <= $maxPage; $page++) {
$pageEntry = [
'logSections' => array_merge(...$this->getLogicalSectionsOnPage($page)),
'files' => [],
];

foreach ($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'] as $fileGrp => $fileId) {
if (!$allFiles) {
$file = [
'url' => $this->getFileLocation($fileId),
'mimetype' => $this->getFileMimeType($fileId),
];
} else {
$file = $allFiles[$fileId] ?? null;
if ($file === null) {
continue;
}
}

$extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
$nonProxyMimeTypes = GeneralUtility::trimExplode(',', $extConf['nonProxyMimeTypes']);

// Only deliver static images via the internal PageViewProxy.
// (For IIP and IIIF, the viewer needs to build and access a separate metadata URL, see `getMetadataURL`.)
if (in_array($fileGrp, $proxyFileGroups) && !in_array($file['mimetype'], $nonProxyMimeTypes)) {
// Configure @action URL for form.
$file['url'] = $uriBuilder
->reset()
->setTargetPageUid($this->pageId)
->setCreateAbsoluteUri($forceAbsoluteUrl)
->setArguments(
[
'eID' => 'tx_dlf_pageview_proxy',
'url' => $file['url'],
'uHash' => GeneralUtility::hmac($file['url'], 'PageViewProxy')
]
)
->build();
}

$pageEntry['files'][$fileGrp] = $file;
}

$result['pages'][] = $pageEntry;
}

return $result;
}
}
53 changes: 51 additions & 2 deletions Classes/Common/IiifManifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,56 @@ public function getFileMimeType(string $id): string
return $format;
}

/**
* @see AbstractDocument::getAllFiles()
*/
public function getAllFiles(): array
{
$files = [];
$canvases = $this->iiif->getDefaultCanvases();
foreach ($canvases as $canvas) {
$images = $canvas->getImages();
foreach ($images as $image) {
$resource = $image->getResource();
$fileId = $resource->getId();
if (empty($fileId)) {
continue;
}

$mimetype = $this->getFileMimeType($fileId);
if (empty($mimetype)) {
continue;
}

$files[$fileId] = [
'url' => $fileId,
'mimetype' => $mimetype,
];
}

$otherFiles = $canvas->getOtherContent();
foreach ($otherFiles as $otherFile) {
$fileId = $$otherFile->getId();
if (empty($fileId)) {
continue;
}

$mimetype = $this->getFileMimeType($fileId);
if (empty($mimetype)) {
continue;
}

// in IIIF id is URL
$files[$fileId] = [
'url' => $fileId,
'mimetype' => $mimetype,
];
}

}
return $files;
}

/**
* @see AbstractDocument::getLogicalStructure()
*/
Expand Down Expand Up @@ -784,8 +834,7 @@ protected function ensureHasFulltextIsSet(): void
* https://digi.ub.uni-heidelberg.de/diglit/iiif/hirsch_hamburg1933_04_25/list/0001.json
*/
if (!$this->hasFulltextSet && $this->iiif instanceof ManifestInterface) {
$manifest = $this->iiif;
$canvases = $manifest->getDefaultCanvases();
$canvases = $this->iiif->getDefaultCanvases();
foreach ($canvases as $canvas) {
if (
!empty($canvas->getSeeAlsoUrlsForFormat("application/alto+xml")) ||
Expand Down
2 changes: 1 addition & 1 deletion Classes/Common/Indexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static function add(Document $document, DocumentRepository $documentRepos
$parent = $documentRepository->findByUid($parentId);
if ($parent) {
// get XML document of parent
$doc = AbstractDocument::getInstance($parent->getLocation(), ['storagePid' => $parent->getPid()], true);
$doc = AbstractDocument::getInstance($parent->getLocation(), 0, ['storagePid' => $parent->getPid()], true);
if ($doc !== null) {
$parent->setCurrentDocument($doc);
$success = self::add($parent, $documentRepository);
Expand Down
Loading