Skip to content

Commit

Permalink
[FEATURE] 3D viewers integration (kitodo#1259)
Browse files Browse the repository at this point in the history
  • Loading branch information
markusweigelt authored Jul 17, 2024
1 parent 94e9c0f commit 729110e
Show file tree
Hide file tree
Showing 19 changed files with 1,529 additions and 1,999 deletions.
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

0 comments on commit 729110e

Please sign in to comment.