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] 3D viewers integration #1259

Merged
merged 31 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cb04a34
Initial commit of embedded 3d viewer implementation
markusweigelt May 31, 2024
18fe909
Merge branch 'kitodo:master' into 3d-viewer-integration
markusweigelt May 31, 2024
2ec8fee
Fix unnecessary change
markusweigelt May 31, 2024
cc6188e
Fix loading of METS/MODS
markusweigelt Jun 5, 2024
151ae7d
Improve embedded 3d viewer
markusweigelt Jun 5, 2024
7df1935
Remove unused files and add logging to embedded 3d viewer
markusweigelt Jun 7, 2024
22c1f1b
Add error handling
markusweigelt Jun 7, 2024
beeacbd
Merge branch 'kitodo:master' into 3d-viewer-integration
markusweigelt Jun 10, 2024
d2754fe
Adjust documentation and improve implemenation
markusweigelt Jun 11, 2024
34047a3
Minor changes
markusweigelt Jun 11, 2024
2a58f0b
Add extension viewer configuration and select viewer by model format
markusweigelt Jun 11, 2024
851772f
Update documentation
markusweigelt Jun 11, 2024
51a67e9
Remove converted model cause currently no example exist
markusweigelt Jun 12, 2024
c265eb3
Controller improvements and error handling of embedded 3d viewer
markusweigelt Jun 12, 2024
2612969
Update documentation
markusweigelt Jun 12, 2024
e6fb3f9
Improve documentation
markusweigelt Jun 12, 2024
1ed92fc
Codacy fixes
markusweigelt Jun 12, 2024
56b2155
Codacy fixes
markusweigelt Jun 12, 2024
3d3dcaf
Merge branch 'master' into 3d-viewer-integration
markusweigelt Jun 26, 2024
76779d9
Update Embedded3DViewer.php
markusweigelt Jun 26, 2024
15fe6bf
Merge branch 'kitodo:master' into 3d-viewer-integration
markusweigelt Jul 16, 2024
7f16533
Add model-viewer as default viewer
markusweigelt Jul 16, 2024
37a0186
Merge branch 'kitodo:master' into 3d-viewer-integration
markusweigelt Jul 16, 2024
0ccdd99
Mode default viewer implementation to separate method and fix phpstan…
markusweigelt Jul 16, 2024
8c6b662
Fixes of Codacy errors
markusweigelt Jul 16, 2024
bc31f94
Fix Codacy issues and improve wording of Html title
markusweigelt Jul 16, 2024
63b149a
Adjust parameter type in comment
markusweigelt Jul 16, 2024
b45d8c6
Adjust parameter type in comment
markusweigelt Jul 16, 2024
ceae089
Improve wording in extension config documentation
markusweigelt Jul 16, 2024
6cdd9ed
Update documentation
markusweigelt Jul 16, 2024
46608c7
Replace tags with HTML entities
markusweigelt Jul 16, 2024
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
5 changes: 2 additions & 3 deletions Classes/Controller/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -479,14 +479,13 @@ private function getDocumentByUrl(string $documentId)
$doc = AbstractDocument::getInstance($documentId, $this->settings, true);

if ($doc !== null) {
$this->document = GeneralUtility::makeInstance(Document::class);

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);
}
}

Expand Down
66 changes: 39 additions & 27 deletions Classes/Controller/View3DController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,47 +21,59 @@
*/
class View3DController extends AbstractController
{

const MIDDLEWARE_DLF_EMBEDDED_3D_VIEWER_PREFIX = '/?middleware=dlf/embedded3DViewer';

/**
* @access public
*
* @return void
*/
public function mainAction(): void
{

if (!empty($this->requestData['model'])) {
$this->view->assign('is3DViewer', $this->is3dViewer($this->requestData['model']));
$embedded3DViewerUrl = $this->buildEmbedded3dViewerUrl($this->requestData['model']);
if (!empty($this->requestData['viewer'])) {
$embedded3DViewerUrl .= '&viewer=' . $this->requestData['viewer'];
}
$this->view->assign('embedded3DViewerUrl', $embedded3DViewerUrl);
return;
}

// Load current document.
$this->loadDocument();
if (
$this->isDocMissingOrEmpty()
|| $this->document->getCurrentDocument()->metadataArray['LOG_0001']['type'][0] != 'object'
!($this->isDocMissingOrEmpty()
|| $this->document->getCurrentDocument()->metadataArray['LOG_0001']['type'][0] != 'object')
) {
// Quit without doing anything if required variables are not set.
return;
} else {
$model = trim($this->document->getCurrentDocument()->getFileLocation($this->document->getCurrentDocument()->physicalStructureInfo[$this->document->getCurrentDocument()->physicalStructure[1]]['files']['DEFAULT']));
$this->view->assign('3d', $model);

$modelConverted = trim($this->document->getCurrentDocument()->getFileLocation($this->document->getCurrentDocument()->physicalStructureInfo[$this->document->getCurrentDocument()->physicalStructure[1]]['files']['CONVERTED']));
$xml = $this->requestData['id'];
$this->view->assign('is3DViewer', $this->is3dViewer($model));
$this->view->assign('embedded3DViewerUrl', $this->buildEmbedded3dViewerUrl($model));
}
}

$settingsParts = explode("/", $model);
$fileName = end($settingsParts);
$path = substr($model, 0, strrpos($model, $fileName));
$modelSettings = $path . "metadata/" . $fileName . "_viewer";
/**
* Checks if the 3D viewer can be rendered.
*
* @return bool True if the 3D viewer can be rendered
*/
private function is3dViewer($model): bool
{
return !empty($model);
}

if (!empty($modelConverted)) {
$model = $modelConverted;
}
/**
* Builds the embedded 3D viewer url.
*
* @param string $model The model url
* @return string The embedded 3D viewer url
*/
public function buildEmbedded3dViewerUrl(string $model): string
{
return self::MIDDLEWARE_DLF_EMBEDDED_3D_VIEWER_PREFIX . '&model=' . $model;
}

if ($this->settings['useInternalProxy']) {
$this->configureProxyUrl($model);
$this->configureProxyUrl($xml);
$this->configureProxyUrl($modelSettings);
}

$this->view->assign('model', $model);
$this->view->assign('xml', $xml);
$this->view->assign('settings', $modelSettings);
$this->view->assign('proxy', $this->settings['useInternalProxy']);
}
}
}
4 changes: 2 additions & 2 deletions Classes/Format/Mods.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private function getHolderFromXml(array $holders, int $i): void
* Get holder from XML display form.
*
* @access private
*
*
* @param array $holders
* @param int $i
*
Expand All @@ -284,7 +284,7 @@ private function getHolderFromXml(array $holders, int $i): void
private function getHolderFromXmlDisplayForm(array $holders, int $i): void
{
// Check if there is a display form.
$displayForms = $holders[$i]->getDisplayForm();
$displayForms = $holders[$i]->getDisplayForms();
if ($displayForms) {
$this->metadata['holder'][$i] = $displayForms[0]->getValue();
}
Expand Down
235 changes: 235 additions & 0 deletions Classes/Middleware/Embedded3DViewer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
<?php

namespace Kitodo\Dlf\Middleware;

/**
* (c) Kitodo. Key to digital objects e.V. <[email protected]>
*
* 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.
*/

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Frontend\Controller\ErrorController;

/**
* Middleware for embedding custom 3D Viewer implementation of the 'dlf' extension.
*
* @package TYPO3
* @subpackage dlf
* @access public
*/
class Embedded3DViewer implements MiddlewareInterface
{
use LoggerAwareTrait;

const VIEWER_FOLDER = "dlf_3d_viewers";
const VIEWER_CONFIG_YML = "dlf-3d-viewer.yml";
const EXT_KEY = "dlf";

/**
* The main method of the middleware.
*
* @access public
*
* @param ServerRequestInterface $request for processing
* @param RequestHandlerInterface $handler for processing
*
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
// parameters are sent by POST --> use getParsedBody() instead of getQueryParams()
$parameters = $request->getQueryParams();
// Return if not this middleware
if (!isset($parameters['middleware']) || ($parameters['middleware'] != 'dlf/embedded3DViewer')) {
return $response;
}

if (empty($parameters['model'])) {
return $this->warningResponse('Model url is missing.', $request);
}

$modelInfo = PathUtility::pathinfo($parameters['model']);
$modelFormat = $modelInfo["extension"];
if (empty($modelFormat)) {
return $this->warningResponse('Model path "' . $parameters['model'] . '" has no extension format', $request);
}

if (empty($parameters['viewer'])) {
// determine viewer from extension configuration
$viewer = $this->getViewerByExtensionConfiguration($modelFormat);
} else {
$viewer = $parameters['viewer'];
}

if (empty($viewer)) {
return $this->renderDefaultViewer($parameters['model']);
}

/** @var StorageRepository $storageRepository */
$storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
$defaultStorage = $storageRepository->getDefaultStorage();

if (!$defaultStorage->hasFolder(self::VIEWER_FOLDER)) {
return $this->errorResponse('Required folder "' . self::VIEWER_FOLDER . '" was not found in the default storage "' . $defaultStorage->getName() . '"', $request);
}

$viewerModules = $defaultStorage->getFolder(self::VIEWER_FOLDER);
if (!$viewerModules->hasFolder($viewer)) {
return $this->errorResponse('Viewer folder "' . $viewer . '" was not found under the folder "' . self::VIEWER_FOLDER . '"', $request);
}

$viewerFolder = $viewerModules->getSubfolder($viewer);
if (!$viewerFolder->hasFile(self::VIEWER_CONFIG_YML)) {
return $this->errorResponse('Viewer folder "' . $viewer . '" does not contain a file named "' . self::VIEWER_CONFIG_YML . '"', $request);
}

/** @var YamlFileLoader $yamlFileLoader */
$yamlFileLoader = GeneralUtility::makeInstance(YamlFileLoader::class);
$viewerConfigPath = $defaultStorage->getName() . "/" . self::VIEWER_FOLDER . "/" . $viewer . "/";
$config = $yamlFileLoader->load($viewerConfigPath . self::VIEWER_CONFIG_YML)["viewer"];

if (!isset($config["supportedModelFormats"]) || empty($config["supportedModelFormats"])) {
return $this->errorResponse('Required key "supportedModelFormats" does not exist in the file "' . self::VIEWER_CONFIG_YML . '" of viewer "' . $viewer . '" or has no value', $request);
}

if (array_search(strtolower($modelFormat), array_map('strtolower', $config["supportedModelFormats"])) === false) {
return $this->warningResponse('Viewer "' . $viewer . '" does not support the model format "' . $modelFormat . '"', $request);
}

$html = $this->getViewerHtml($config, $viewerConfigPath, $viewerFolder, $parameters['model'], $modelInfo);
return new HtmlResponse($html);
}

/**
* Build the error response.
*
* Logs the given message as error and return internal error response.
*
* @param string $message
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws \TYPO3\CMS\Core\Error\Http\InternalServerErrorException
*/
public function errorResponse(string $message, ServerRequestInterface $request): ResponseInterface
{
/** @var ErrorController $errorController */
$errorController = GeneralUtility::makeInstance(ErrorController::class);
$this->logger->error($message);
return $errorController->internalErrorAction($request, $message);
}

/**
* Build the warning response.
*
* Logs the given message as warning and return page not found response.
*
* @param string $message
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
*/
public function warningResponse(string $message, ServerRequestInterface $request): ResponseInterface
{
/** @var ErrorController $errorController */
$errorController = GeneralUtility::makeInstance(ErrorController::class);
$this->logger->warning($message);
return $errorController->pageNotFoundAction($request, $message);
}

/**
* Determines the viewer based on the extension configuration and the given model format.
*
* @param $modelFormat string The model format
* @return string The 3D viewer
*/
private function getViewerByExtensionConfiguration($modelFormat): string
{
$extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::EXT_KEY, '3dviewer');
$viewerModelFormatMappings = explode(";", $extConf['viewerModelFormatMapping']);
foreach ($viewerModelFormatMappings as $viewerModelFormatMapping) {
$explodedViewerModelMapping = explode(":", $viewerModelFormatMapping);
if (count($explodedViewerModelMapping) == 2) {
$viewer = trim($explodedViewerModelMapping[0]);
$viewerModelFormats = array_map('trim', explode(",", $explodedViewerModelMapping[1]));
if (in_array($modelFormat, $viewerModelFormats)) {
return $viewer;
}
}
}

return $extConf['defaultViewer'] ?? "";
}

/**
* @param string $viewerUrl
* @param string $html
* @param string $modelUrl
* @param array $modelInfo
* @return string
*/
public function replacePlaceholders(string $viewerUrl, string $html, $modelUrl, array $modelInfo): string
{
$html = str_replace("{{viewerPath}}", $viewerUrl, $html);
$html = str_replace("{{modelUrl}}", $modelUrl, $html);
$html = str_replace("{{modelPath}}", $modelInfo["dirname"], $html);
return str_replace("{{modelResource}}", $modelInfo["basename"], $html);
}

/**
* @param $model
* @return HtmlResponse
* @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException
*/
public function renderDefaultViewer($model): HtmlResponse
{
/** @var ResourceFactory $resourceFactory */
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$html = $resourceFactory->retrieveFileOrFolderObject('EXT:dlf/Resources/Private/Templates/View3D/Standalone.html')->getContents();
$file = $resourceFactory->retrieveFileOrFolderObject('EXT:dlf/Resources/Public/JavaScript/3DViewer/model-viewer-3.5.0.min.js');
$html = str_replace('{{modelViewerJS}}', $file->getPublicUrl(), $html);
$html = str_replace("{{modelUrl}}", $model, $html);
return new HtmlResponse($html);
}

/**
* @param array $config
* @param string $viewerConfigPath
* @param Folder $viewerFolder
* @param string $modelUrl
* @param array $modelInfo
* @return string
*/
public function getViewerHtml(array $config, string $viewerConfigPath, Folder $viewerFolder, string $modelUrl, array $modelInfo): string
{
$htmlFile = "index.html";
if (isset($config["base"]) && !empty($config["base"])) {
$htmlFile = $config["base"];
}

$viewerUrl = $viewerConfigPath;
if (isset($config["url"]) && !empty($config["url"])) {
$viewerUrl = rtrim($config["url"]);
}

$html = $viewerFolder->getFile($htmlFile)->getContents();
return $this->replacePlaceholders($viewerUrl, $html, $modelUrl, $modelInfo);
}
}
6 changes: 6 additions & 0 deletions Configuration/RequestMiddlewares.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,11 @@
'typo3/cms-frontend/prepare-tsfe-rendering'
]
],
'dlf/embedded3DViewer' => [
'target' => \Kitodo\Dlf\Middleware\Embedded3DViewer::class,
'after' => [
'typo3/cms-frontend/prepare-tsfe-rendering'
]
]
],
];
1 change: 1 addition & 0 deletions Configuration/TypoScript/setup.typoscript
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ config {

page {
includeCSS {
3dviewer = EXT:dlf/Resources/Public/Stylesheets/3d-viewer.css
jPlayer = EXT:dlf/Resources/Public/JavaScript/jPlayer/blue.monday/css/jplayer.blue.monday.min.css
openLayers = EXT:dlf/Resources/Public/JavaScript/OpenLayers/openlayers.css
}
Expand Down
Loading