Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Remove client-side paging redundancies #938

Draft
wants to merge 78 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
0f473ef
Add explanation about `addHighlightField` method
Jul 27, 2022
6ce1255
Add Basic JSON Representation for Document and First Navigation Button
Aug 22, 2022
0eaeeb0
Refactor: Format code
Aug 22, 2022
30da335
Refactor: Move JS from template to new Navigation.js
Aug 22, 2022
c2fa5d9
Make loaded document a global variable
Aug 22, 2022
be80aab
Fix naming pageBack vs. pageStepBack
Aug 22, 2022
0e84acf
Handle all forward/back navigation buttons
Aug 22, 2022
3f92a0a
Enable/disable nav buttons on client, + refactor
Aug 22, 2022
d490a10
Create WIP documentation page
Aug 23, 2022
e41ba10
Include page object in pageChanged event, start typing
Aug 23, 2022
51301f2
Update metadata table on page change
Aug 23, 2022
20ec213
Add Data Structure for Client Side Fulltext Reload
Aug 23, 2022
48c1be1
Adapt Fulltext Datastructure to Metadata and Document
Aug 23, 2022
3126339
Update navigation select
beatrycze-volk Aug 23, 2022
86f2c7a
Update links for navigation buttons
beatrycze-volk Aug 23, 2022
cbff479
Fix reference to fulltextControl
Aug 23, 2022
691cb46
Set navigation select value on page change
Aug 23, 2022
dfe0392
Fix: First load data in `Doc::getLogicalSectionsOnPage`
Aug 23, 2022
ac06bcf
Start implementing history/browser navigation
Aug 23, 2022
f7624a8
Refactor: In document JSON, store pages in subkey
Aug 23, 2022
63aa6c5
Refactor: Wrap image URL & MIME type into subkey
Aug 23, 2022
33f0206
Extract Doc::getPageLink()
Aug 24, 2022
6fbf835
List download URLs in document JSON
Aug 24, 2022
1d21c59
Update page download links on page change
Aug 24, 2022
c7149f8
Refactor: Avoid manual JSON encoding
Aug 24, 2022
fbf571f
Include all file groups/URLs in document JSON
Aug 24, 2022
7f7f50c
Update image download links on page change
Aug 24, 2022
b976676
Start implementing client-side double page mode
Aug 24, 2022
d1c302e
Simplify: Remove pageObj and target from event detail
Aug 24, 2022
a71742d
Simplify: Merge `pageChanged` and `configChanged` events
Aug 24, 2022
b241a59
Fix back buttons when using route enhancers
Aug 24, 2022
5923985
Generalize URL generation to work with slugs
Aug 24, 2022
de9eeb0
Fix pdf and image download links
Aug 25, 2022
5ad4b40
TOC: Add option to render full table of contents
Jul 5, 2022
a615e96
Add client side navigation from table of contents
Aug 25, 2022
b40dccf
Add separate marker for metadata list
Aug 25, 2022
029aa23
Start implementing dynamic TOC expansion
Aug 25, 2022
474e40d
Update URL on page change
Aug 25, 2022
2f85789
Optimize document JSON generation for METS
Aug 25, 2022
2f535f7
Fix disabling of lastPage navigation button
Aug 25, 2022
6b00732
Cache pageview layers
Aug 25, 2022
6286c63
Update Page URL Template for Use with Page Grid
Aug 26, 2022
7dd4391
Add document plugin for JSON representation
Aug 26, 2022
3b03dc1
Move document initialisation to document plugin
Aug 26, 2022
70d24e7
Move document handling to dlfController via event
Aug 26, 2022
cb2f04b
Start moving getVisiblePages to dlfController
Aug 26, 2022
dbfa82a
Use `getVisiblePages()` of `dlfController`
Aug 27, 2022
0030c3c
Refactor: Extract `dlfController::eventTarget`
Aug 27, 2022
85213aa
Refactor: Don't dispatch `stateChanged` manually
Aug 27, 2022
5beac28
Refactor: Dissolve global `tx_dlf_loaded`
Aug 27, 2022
d2e57fd
[EXT] Reinstate registry
Aug 27, 2022
0f4d27e
[EXT] Memoize `MetsDocument::getStructureDepth()`
Aug 27, 2022
9277a7c
Fetch prerendererd metadata dynamically
Aug 27, 2022
155043e
Convert `TODO` to `TODO(client-side)`
Aug 27, 2022
024a677
Update and extend documentation
Aug 29, 2022
6a4c8d0
Update .editorconfig (4 spaces for JS) to reflect actual use
Aug 29, 2022
f1598ee
Extend documentation
Aug 30, 2022
69703f1
Add and update notes about URL generation
Aug 30, 2022
aa320e4
Fix Codacy warnings
beatrycze-volk Jan 20, 2023
4374ffe
Fix toArray method in Doc class
beatrycze-volk Feb 3, 2023
c0ceca2
Adjust DocumentController
beatrycze-volk Feb 6, 2023
8392ccf
Move nonProxyMimeTypes to config
beatrycze-volk Feb 6, 2023
d226b02
Implement getAllFiles for IIIFManifest
beatrycze-volk Feb 28, 2023
cdd288d
Remove unused manifest variable
beatrycze-volk Feb 28, 2023
e03c0f8
Fix intend in css
beatrycze-volk Mar 8, 2023
c8e28fb
Improve Metadata script
beatrycze-volk Mar 8, 2023
40f536b
Improve Navigation script
beatrycze-volk Mar 8, 2023
d31dbd9
Assign fulltextEntry directly
beatrycze-volk Mar 28, 2023
41301cf
Fix comment
beatrycze-volk Mar 28, 2023
48e96b6
Allow console logs
beatrycze-volk Mar 29, 2023
4704970
Activate full text highlight if full text is active
beatrycze-volk Mar 30, 2023
8f8ae48
Fix errors marked by PHPStan
beatrycze-volk Oct 25, 2023
3ac619d
Fix Codacy errors
beatrycze-volk Oct 25, 2023
1f62fc1
Fix error: Direct use of $GLOBALS Superglobal detected
beatrycze-volk Nov 20, 2023
3bdcb82
Fix documentation build
beatrycze-volk Aug 2, 2024
d647e5f
Fix PHPStan warnings
beatrycze-volk Aug 2, 2024
cd32212
Remove initDoc to avoid redundancy between Document and PageView cont…
beatrycze-volk Apr 3, 2023
fea6f47
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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ trim_trailing_whitespace = true

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

# JSON-Files
[*.json]
Expand Down
2 changes: 2 additions & 0 deletions .github/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ parameters:
- '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByUid\(\)\.#'
- '#Call to an undefined method Psr\\Http\\Message\\RequestFactoryInterface::request\(\)\.#'
- '#Call to an undefined method Solarium\\Core\\Query\\DocumentInterface::setField\(\)\.#'
- '#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\\IiifResourceInterface::getHeight\(\)\.#'
- '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\IiifResourceInterface::getWidth\(\)\.#'
- '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\IiifResourceInterface::getPossibleTextAnnotationContainers\(\)\.#'
Expand Down
2 changes: 1 addition & 1 deletion Classes/Command/BaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,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]);
$parent = AbstractDocument::getInstance($doc->parentHref, 0, ['storagePid' => $this->storagePid]);

if ($parent->recordId) {
$parentDocument = $this->documentRepository->findOneByRecordId($parent->recordId);
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 @@ -168,11 +168,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 BaseCommand::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 @@ -204,7 +204,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: 146 additions & 6 deletions Classes/Common/AbstractDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ abstract class AbstractDocument
*/
protected int $cPid = 0;

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

/**
* @access public
* @static
Expand Down Expand Up @@ -515,6 +521,27 @@ abstract protected function prepareMetadataArray(int $cPid): 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 @@ -523,12 +550,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 @@ -576,14 +604,14 @@ public static function &getInstance(string $location, array $settings = [], bool
// Sanitize input.
$pid = array_key_exists('storagePid', $settings) ? max((int) $settings['storagePid'], 0) : 0;
if ($documentFormat == 'METS') {
$instance = new MetsDocument($pid, $location, $xml, $settings);
$instance = new MetsDocument($pid, $location, $pageId, $xml, $settings);
} elseif ($documentFormat == 'IIIF') {
// TODO: Parameter $preloadedDocument of class Kitodo\Dlf\Common\IiifManifest constructor expects SimpleXMLElement|Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface, Ubl\Iiif\Presentation\Common\Model\AbstractIiifEntity|null given.
// @phpstan-ignore-next-line
$instance = new IiifManifest($pid, $location, $iiif);
$instance = new IiifManifest($pid, $location, $pageId, $iiif);
}

if ($instance !== null) {
if ($instance) {
self::setDocumentCache($location, $instance);
}

Expand Down Expand Up @@ -1140,7 +1168,7 @@ protected function magicGetRootId(): int
if ($this->parentId) {
// 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, ['storagePid' => $this->pid]);
$parent = self::getInstance($this->parentId, $this->pageId, ['storagePid' => $this->pid]);
$this->rootId = $parent->rootId;
}
$this->rootIdLoaded = true;
Expand Down Expand Up @@ -1188,14 +1216,16 @@ protected function _setCPid(int $value): void
*
* @param int $pid If > 0, then only document with this PID gets loaded
* @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(int $pid, string $location, $preloadedDocument, array $settings = [])
protected function __construct(int $pid, string $location, int $pageId, $preloadedDocument, array $settings = [])
{
$this->pid = $pid;
$this->pageId = $pageId;
$this->setPreloadedDocument($preloadedDocument);
$this->init($location, $settings);
$this->establishRecordId($pid);
Expand Down Expand Up @@ -1301,4 +1331,114 @@ private static function setDocumentCache(string $location, AbstractDocument $cur
// Save value in cache
$cache->set($cacheIdentifier, $currentDocument);
}

/**
* 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 @@ -419,6 +419,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 @@ -854,8 +904,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
Loading