From f900d4717039cc58239d11317a760763f5d3ee09 Mon Sep 17 00:00:00 2001 From: Benjamin Serfhos Date: Tue, 3 Jul 2018 15:25:40 +0200 Subject: [PATCH 1/6] [TASK] Upgrade compact view to use video elements --- Classes/Backend/InlineControlContainer.php | 33 +++++++++++-- Classes/Controller/CompactViewController.php | 20 +++++++- Classes/Resource/AssetProcessing.php | 2 +- Classes/Resource/BynderDriver.php | 47 ++++++++++++++----- .../Private/Templates/CompactView/Index.html | 26 +++++----- 5 files changed, 97 insertions(+), 31 deletions(-) diff --git a/Classes/Backend/InlineControlContainer.php b/Classes/Backend/InlineControlContainer.php index e5b69a5..1532fb3 100644 --- a/Classes/Backend/InlineControlContainer.php +++ b/Classes/Backend/InlineControlContainer.php @@ -11,6 +11,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Class InlineControlContainer @@ -33,7 +34,7 @@ protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfigu // Inject button before help-block if (strpos($selector, '
') > 0) { $selector = str_replace('
', $button . '
', $selector); - // Try to inject it into the form-control container + // Try to inject it into the form-control container } elseif (preg_match('/<\/div><\/div>$/i', $selector)) { $selector = preg_replace('/<\/div><\/div>$/i', $button . '
', $selector); } else { @@ -67,7 +68,10 @@ protected function renderBynderButton(array $inlineConfiguration): string $objectPrefix = $currentStructureDomObjectIdPrefix . '-' . $foreign_table; $nameObject = $currentStructureDomObjectIdPrefix; - $compactViewUrl = BackendUtility::getModuleUrl('bynder_compact_view', ['element' => 'bynder' . $this->inlineData['config'][$nameObject]['md5']]); + $compactViewUrl = BackendUtility::getModuleUrl('bynder_compact_view', [ + 'element' => 'bynder' . $this->inlineData['config'][$nameObject]['md5'], + 'assetTypes' => $this->getAssetTypesByAllowedElements($groupFieldConfiguration['appearance']['elementBrowserAllowed']) + ]); $this->requireJsModules[] = 'TYPO3/CMS/Bynder/CompactView'; $buttonText = htmlspecialchars($languageService->sL('LLL:EXT:bynder/Resources/Private/Language/locallang_be.xlf:compact_view.button')); @@ -105,4 +109,27 @@ protected function bynderStorageAvailable(): bool } return false; } -} \ No newline at end of file + + /** + * @param array $allowedElements + * @return string + */ + protected function getAssetTypesByAllowedElements($allowedElements): string + { + $assetTypes = []; + if (empty($allowedElements)) { + $assetTypes = [BynderDriver::ASSET_TYPE_IMAGE, BynderDriver::ASSET_TYPE_VIDEO]; + } else { + $allowedElements = GeneralUtility::trimExplode(',', $allowedElements); + if (in_array('png', $allowedElements)) { + $assetTypes[] = BynderDriver::ASSET_TYPE_IMAGE; + } + + if (in_array('mp4', $allowedElements)) { + $assetTypes[] = BynderDriver::ASSET_TYPE_VIDEO; + } + } + + return implode(',', $assetTypes); + } +} diff --git a/Classes/Controller/CompactViewController.php b/Classes/Controller/CompactViewController.php index ac488f4..7f89fae 100644 --- a/Classes/Controller/CompactViewController.php +++ b/Classes/Controller/CompactViewController.php @@ -8,9 +8,9 @@ * All code (c) Beech.it all rights reserved */ +use BeechIt\Bynder\Service\BynderService; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use BeechIt\Bynder\Service\BynderService; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Resource\Index\Indexer; use TYPO3\CMS\Core\Resource\ResourceStorage; @@ -55,6 +55,9 @@ class CompactViewController */ protected $bynderService; + /** + * CompactViewController constructor. + */ public function __construct() { $this->bynderService = GeneralUtility::makeInstance(BynderService::class); @@ -66,6 +69,8 @@ public function __construct() } /** + * Action: Display compact view + * * @param ServerRequestInterface $request * @param ResponseInterface $response * @return ResponseInterface @@ -78,6 +83,7 @@ public function indexAction(ServerRequestInterface $request, ResponseInterface $ 'language' => $this->getBackendUserAuthentication()->uc['lang'] ?: ($this->getBackendUserAuthentication()->user['lang'] ?: 'en_EN'), 'apiBaseUrl' => $this->bynderService->getApiBaseUrl(), 'element' => $request->getQueryParams()['element'], + 'assetTypes' => $request->getQueryParams()['assetTypes'] ]); $response->getBody()->write($this->view->render()); @@ -85,6 +91,13 @@ public function indexAction(ServerRequestInterface $request, ResponseInterface $ return $response; } + /** + * Action: Retrieve file from storage + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + */ public function getFilesAction(ServerRequestInterface $request, ResponseInterface $response) { $files = []; @@ -107,6 +120,9 @@ public function getFilesAction(ServerRequestInterface $request, ResponseInterfac return $response; } + /** + * @return ResourceStorage + */ protected function getBynderStorage(): ResourceStorage { /** @var ResourceStorage $fileStorage */ @@ -137,4 +153,4 @@ protected function getIndexer(ResourceStorage $storage) { return GeneralUtility::makeInstance(Indexer::class, $storage); } -} \ No newline at end of file +} diff --git a/Classes/Resource/AssetProcessing.php b/Classes/Resource/AssetProcessing.php index 3c25649..171efd5 100644 --- a/Classes/Resource/AssetProcessing.php +++ b/Classes/Resource/AssetProcessing.php @@ -252,4 +252,4 @@ public function __destruct() sleep(ceil(3 - $difference)); } } -} \ No newline at end of file +} diff --git a/Classes/Resource/BynderDriver.php b/Classes/Resource/BynderDriver.php index 11ce6e4..8c67bc1 100644 --- a/Classes/Resource/BynderDriver.php +++ b/Classes/Resource/BynderDriver.php @@ -8,13 +8,13 @@ * Date: 19-2-18 * All code (c) Beech.it all rights reserved */ +use BeechIt\Bynder\Exception\NotImplementedException; use BeechIt\Bynder\Service\BynderService; use Bynder\Api\Impl\AssetBankManager; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; use TYPO3\CMS\Core\Resource\Exception; use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Utility\GeneralUtility; -use BeechIt\Bynder\Exception\NotImplementedException; /** * Class BynderDriver @@ -23,6 +23,9 @@ class BynderDriver implements DriverInterface { const KEY = 'bynder'; + const ASSET_TYPE_VIDEO = 'video'; + const ASSET_TYPE_IMAGE = 'image'; + /** * @var array */ @@ -213,16 +216,29 @@ public function getPublicUrl($identifier) $format = $matches[2]; } - if (!in_array($format, ['mini', 'thul', 'webimage'])) { - $format = 'webimage'; - } - try { $fileInfo = $this->getBynderService()->getMediaInfo($identifier); - return $fileInfo['thumbnails'][$format]; + switch ($fileInfo['type']) { + case BynderDriver::ASSET_TYPE_IMAGE: + if (!in_array($format, ['mini', 'thul', 'webimage'])) { + $format = 'webimage'; + } + return $fileInfo['thumbnails'][$format]; + case BynderDriver::ASSET_TYPE_VIDEO: + $urls = array_filter($fileInfo['videoPreviewURLs'], function ($url) { + return preg_match('/mp4$/i', $url); + }); + if (empty($urls)) { + throw new Exception\FileDoesNotExistException( + 'mp4 not found in video URL\'s', + 1530626116454 + ); + } + return reset($urls); + } } catch (\Exception $exception) { throw new Exception\FileDoesNotExistException( - sprintf('Requested file "%s" coudn\'t be found', $identifier), + sprintf('Requested file "%s" couldn\'t be found', $identifier), 1519115242, $exception ); @@ -689,8 +705,17 @@ protected function extractFileInformation(array $mediaInfo, array $propertiesToE { if (empty($propertiesToExtract)) { $propertiesToExtract = [ - 'size', 'atime', 'mtime', 'ctime', 'mimetype', 'name', 'extension', - 'identifier', 'identifier_hash', 'storage', 'folder_hash' + 'size', + 'atime', + 'mtime', + 'ctime', + 'mimetype', + 'name', + 'extension', + 'identifier', + 'identifier_hash', + 'storage', + 'folder_hash' ]; } $fileInformation = []; @@ -736,7 +761,7 @@ protected function getSpecificFileInformation($mediaInfo, $property) case 'folder_hash': return $this->hashIdentifier(''); - // Metadata + // Metadata case 'title': return $mediaInfo['name']; case 'description': @@ -799,4 +824,4 @@ public function __destruct() GeneralUtility::unlink_tempfile($tempFile); } } -} \ No newline at end of file +} diff --git a/Resources/Private/Templates/CompactView/Index.html b/Resources/Private/Templates/CompactView/Index.html index 7930704..f8a5c5d 100644 --- a/Resources/Private/Templates/CompactView/Index.html +++ b/Resources/Private/Templates/CompactView/Index.html @@ -1,19 +1,17 @@ - -
- + data-folder="bynder-compactview" + data-fullScreen="true" + data-header="false" + data-language="{language}" + data-mode="multi" + data-zindex="300" +> + +
\ No newline at end of file + From 6e16b67ac1d0ceef7a4ca4862c749882bcfae807 Mon Sep 17 00:00:00 2001 From: Benjamin Serfhos Date: Wed, 4 Jul 2018 10:27:09 +0200 Subject: [PATCH 2/6] [TASK] View more extension for asset types TODO: Refactor to retrieve asset type extensions from configuration --- Classes/Backend/InlineControlContainer.php | 11 +++++++---- Resources/Private/Templates/CompactView/Index.html | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Classes/Backend/InlineControlContainer.php b/Classes/Backend/InlineControlContainer.php index 1532fb3..e3af07b 100644 --- a/Classes/Backend/InlineControlContainer.php +++ b/Classes/Backend/InlineControlContainer.php @@ -111,7 +111,7 @@ protected function bynderStorageAvailable(): bool } /** - * @param array $allowedElements + * @param string $allowedElements * @return string */ protected function getAssetTypesByAllowedElements($allowedElements): string @@ -120,9 +120,12 @@ protected function getAssetTypesByAllowedElements($allowedElements): string if (empty($allowedElements)) { $assetTypes = [BynderDriver::ASSET_TYPE_IMAGE, BynderDriver::ASSET_TYPE_VIDEO]; } else { - $allowedElements = GeneralUtility::trimExplode(',', $allowedElements); - if (in_array('png', $allowedElements)) { - $assetTypes[] = BynderDriver::ASSET_TYPE_IMAGE; + $allowedElements = GeneralUtility::trimExplode(',', strtolower($allowedElements), true); + foreach (['jpg', 'png', 'gif'] as $element) { + if (in_array($element, $allowedElements)) { + $assetTypes[] = BynderDriver::ASSET_TYPE_VIDEO; + break; + } } if (in_array('mp4', $allowedElements)) { diff --git a/Resources/Private/Templates/CompactView/Index.html b/Resources/Private/Templates/CompactView/Index.html index f8a5c5d..a4cadca 100644 --- a/Resources/Private/Templates/CompactView/Index.html +++ b/Resources/Private/Templates/CompactView/Index.html @@ -1,7 +1,7 @@
Date: Thu, 5 Jul 2018 12:40:02 +0200 Subject: [PATCH 3/6] [TASK] Use the BynderRenderer logic for OnlineMedia --- Classes/Backend/InlineControlContainer.php | 2 +- Classes/Controller/CompactViewController.php | 5 +- Classes/Resource/AssetProcessing.php | 2 + Classes/Resource/BynderDriver.php | 485 ++++++++---------- Classes/Resource/Helper/BynderHelper.php | 263 ++++++++++ .../Index}/Extractor.php | 25 +- Classes/Resource/Rendering/BynderRenderer.php | 106 ++++ Classes/Slot/PublicUrlSlot.php | 46 -- Classes/Traits/BynderHelper.php | 37 ++ Classes/Traits/BynderService.php | 24 + Classes/Traits/BynderStorage.php | 34 ++ ext_localconf.php | 22 +- 12 files changed, 688 insertions(+), 363 deletions(-) create mode 100644 Classes/Resource/Helper/BynderHelper.php rename Classes/{Metadata => Resource/Index}/Extractor.php (64%) create mode 100644 Classes/Resource/Rendering/BynderRenderer.php delete mode 100644 Classes/Slot/PublicUrlSlot.php create mode 100644 Classes/Traits/BynderHelper.php create mode 100644 Classes/Traits/BynderService.php create mode 100644 Classes/Traits/BynderStorage.php diff --git a/Classes/Backend/InlineControlContainer.php b/Classes/Backend/InlineControlContainer.php index e3af07b..48cc2b2 100644 --- a/Classes/Backend/InlineControlContainer.php +++ b/Classes/Backend/InlineControlContainer.php @@ -123,7 +123,7 @@ protected function getAssetTypesByAllowedElements($allowedElements): string $allowedElements = GeneralUtility::trimExplode(',', strtolower($allowedElements), true); foreach (['jpg', 'png', 'gif'] as $element) { if (in_array($element, $allowedElements)) { - $assetTypes[] = BynderDriver::ASSET_TYPE_VIDEO; + $assetTypes[] = BynderDriver::ASSET_TYPE_IMAGE; break; } } diff --git a/Classes/Controller/CompactViewController.php b/Classes/Controller/CompactViewController.php index 7f89fae..6b7cd3f 100644 --- a/Classes/Controller/CompactViewController.php +++ b/Classes/Controller/CompactViewController.php @@ -12,6 +12,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Index\Indexer; use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -105,9 +106,9 @@ public function getFilesAction(ServerRequestInterface $request, ResponseInterfac $fileStorage = $this->getBynderStorage(); foreach ($request->getParsedBody()['files'] ?? [] as $fileIdentifier) { $file = $fileStorage->getFile($fileIdentifier); - if ($file) { + if ($file instanceof File) { // (Re)Fetch metadata - $this->getIndexer($file->getStorage())->extractMetaData($file); + $this->getIndexer($fileStorage)->extractMetaData($file); $files[] = $file->getUid(); } } diff --git a/Classes/Resource/AssetProcessing.php b/Classes/Resource/AssetProcessing.php index 171efd5..e1e6746 100644 --- a/Classes/Resource/AssetProcessing.php +++ b/Classes/Resource/AssetProcessing.php @@ -61,6 +61,7 @@ protected function needsReprocessing($processedFile): bool */ public function processFile(FileProcessingService $fileProcessingService, DriverInterface $driver, ProcessedFile $processedFile, File $file, $taskType, array $configuration) { + return; if ($file->getStorage()->getDriverType() !== BynderDriver::KEY) { return; } @@ -69,6 +70,7 @@ public function processFile(FileProcessingService $fileProcessingService, Driver return; } + $mediaInfo = $this->getBynderService()->getMediaInfo($processedFile->getOriginalFile()->getIdentifier()); $processingConfiguration = $processedFile->getProcessingConfiguration(); diff --git a/Classes/Resource/BynderDriver.php b/Classes/Resource/BynderDriver.php index 8c67bc1..ed566e2 100644 --- a/Classes/Resource/BynderDriver.php +++ b/Classes/Resource/BynderDriver.php @@ -9,28 +9,26 @@ * All code (c) Beech.it all rights reserved */ use BeechIt\Bynder\Exception\NotImplementedException; -use BeechIt\Bynder\Service\BynderService; -use Bynder\Api\Impl\AssetBankManager; +use BeechIt\Bynder\Resource\Helper\BynderHelper; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; use TYPO3\CMS\Core\Resource\Exception; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Utility\DebuggerUtility; /** * Class BynderDriver */ class BynderDriver implements DriverInterface { + use \BeechIt\Bynder\Traits\BynderService; + const KEY = 'bynder'; const ASSET_TYPE_VIDEO = 'video'; const ASSET_TYPE_IMAGE = 'image'; - /** - * @var array - */ - protected static $tempFiles = []; - /** * @var string */ @@ -58,28 +56,8 @@ class BynderDriver implements DriverInterface protected $configuration = []; /** - * @var AssetBankManager + * Processes the configuration for this driver. */ - protected $assetBankManager; - - /** - * @var BynderService - */ - protected $bynderService; - - /** - * Creates this object. - * - * @param array $configuration - */ - public function __construct(array $configuration = []) - { - $this->capabilities = - ResourceStorage::CAPABILITY_BROWSABLE - | ResourceStorage::CAPABILITY_PUBLIC - | ResourceStorage::CAPABILITY_WRITABLE; - } - public function processConfiguration() { } @@ -123,8 +101,16 @@ public function getCapabilities() return $this->capabilities; } + /** + * Initializes this object. This is called by the storage after the driver + * has been attached. + */ public function initialize() { + $this->capabilities = + ResourceStorage::CAPABILITY_BROWSABLE + | ResourceStorage::CAPABILITY_PUBLIC + | ResourceStorage::CAPABILITY_WRITABLE; } /** @@ -155,9 +141,11 @@ public function isCaseSensitiveFileSystem() } /** + * Cleans a fileName from not allowed characters + * * @param string $fileName - * @param string $charset - * @return string + * @param string $charset Charset of the a fileName (defaults to current charset; depending on context) + * @return string the cleaned filename */ public function sanitizeFileName($fileName, $charset = '') { @@ -166,15 +154,21 @@ public function sanitizeFileName($fileName, $charset = '') } /** + * Hashes a file identifier, taking the case sensitivity of the file system + * into account. This helps mitigating problems with case-insensitive + * databases. + * * @param string $identifier * @return string */ public function hashIdentifier($identifier) { - return sha1($identifier); + return $this->hash($identifier, 'sha1'); } /** + * Returns the identifier of the root level folder of the storage. + * * @return string */ public function getRootLevelFolder() @@ -183,6 +177,8 @@ public function getRootLevelFolder() } /** + * Returns the identifier of the default folder new files should be put into. + * * @return string */ public function getDefaultFolder() @@ -210,6 +206,9 @@ public function getParentFolderIdentifierOfIdentifier($fileIdentifier) */ public function getPublicUrl($identifier) { + DebuggerUtility::var_dump(__METHOD__); + return $identifier; + $format = ''; if (preg_match('/^processed_([0-9A-Z\-]{35})_([a-z]+)/', $identifier, $matches)) { $identifier = $matches[1]; @@ -245,41 +244,63 @@ public function getPublicUrl($identifier) } } + /** + * Creates a folder, within a parent folder. + * If no parent folder is given, a root level folder will be created + * + * @param string $newFolderName + * @param string $parentFolderIdentifier + * @param bool $recursive + * @return string the Identifier of the new folder + * @throws NotImplementedException + */ public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false) { throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045381); } + /** + * Renames a folder in this storage. + * + * @param string $folderIdentifier + * @param string $newName + * @return array A map of old to new file identifiers of all affected resources + * @throws NotImplementedException + */ public function renameFolder($folderIdentifier, $newName) { throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045382); } + /** + * Removes a folder in filesystem. + * + * @param string $folderIdentifier + * @param bool $deleteRecursively + * @return bool + * @throws NotImplementedException + */ public function deleteFolder($folderIdentifier, $deleteRecursively = false) { throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045383); } + /** + * Checks if a file exists. + * * @param string $fileIdentifier * @return bool */ public function fileExists($fileIdentifier) { // We just assume that the processed file exists as this is just a CDN link - if ($this->isProcessedFile($fileIdentifier)) { - return true; - } - - try { - $asset = $this->getBynderService()->getMediaInfo($fileIdentifier); - return true; - } catch (\Exception $exception) { - return false; - } + return (!empty($fileIdentifier)); } /** + * Checks if a folder exists. + * * @param string $folderIdentifier * @return bool */ @@ -290,23 +311,29 @@ public function folderExists($folderIdentifier) } /** + * Checks if a folder contains files and (if supported) other folders. + * * @param string $folderIdentifier - * @return bool + * @return bool TRUE if there are no files and folders within $folder */ public function isFolderEmpty($folderIdentifier) { - // We just say that every folder has some content as - // Bynder doesn't know the concept of folders and we don't want - // any call like deleteFolder() - return false; + // We only know the root folder + return $folderIdentifier === $this->rootFolder; } /** - * @param string $localFilePath + * Adds a file from the local server hard disk to a given path in TYPO3s + * virtual file system. This assumes that the local file exists, so no + * further check is done here! After a successful the original file must + * not exist anymore. + * + * @param string $localFilePath (within PATH_site) * @param string $targetFolderIdentifier - * @param string $newFileName - * @param bool $removeOriginal - * @return string|void + * @param string $newFileName optional, if not given original name is used + * @param bool $removeOriginal if set the original file will be removed + * after successful operation + * @return string the identifier of the new file * @throws NotImplementedException */ public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true) @@ -315,9 +342,11 @@ public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = } /** + * Creates a new (empty) file and returns the identifier. + * * @param string $fileName * @param string $parentFolderIdentifier - * @return string|void + * @return string * @throws NotImplementedException */ public function createFile($fileName, $parentFolderIdentifier) @@ -326,10 +355,14 @@ public function createFile($fileName, $parentFolderIdentifier) } /** + * Copies a file *within* the current storage. + * Note that this is only about an inner storage copy action, + * where a file is just copied to another folder in the same storage. + * * @param string $fileIdentifier * @param string $targetFolderIdentifier * @param string $fileName - * @return string|void + * @return string the Identifier of the new file * @throws NotImplementedException */ public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName) @@ -338,9 +371,11 @@ public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, } /** + * Renames a file in this storage. + * * @param string $fileIdentifier - * @param string $newName - * @return string|void + * @param string $newName The target path (including the file name!) + * @return string The identifier of the file after renaming * @throws NotImplementedException */ public function renameFile($fileIdentifier, $newName) @@ -349,9 +384,11 @@ public function renameFile($fileIdentifier, $newName) } /** + * Replaces a file with file in local file system. + * * @param string $fileIdentifier * @param string $localFilePath - * @return bool|void + * @return bool TRUE if the operation succeeded * @throws NotImplementedException */ public function replaceFile($fileIdentifier, $localFilePath) @@ -360,8 +397,12 @@ public function replaceFile($fileIdentifier, $localFilePath) } /** + * Removes a file from the filesystem. This does not check if the file is + * still used or if it is a bad idea to delete it for some other reason + * this has to be taken care of in the upper layers (e.g. the Storage)! + * * @param string $fileIdentifier - * @return bool + * @return bool TRUE if deleting the file succeeded * @throws NotImplementedException */ public function deleteFile($fileIdentifier) @@ -401,10 +442,14 @@ public function hash($fileIdentifier, $hashAlgorithm) } /** + * Moves a file *within* the current storage. + * Note that this is only about an inner-storage move action, + * where a file is just moved to another folder in the same storage. + * * @param string $fileIdentifier * @param string $targetFolderIdentifier * @param string $newFileName - * @return string|void + * @return string * @throws NotImplementedException */ public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName) @@ -413,10 +458,12 @@ public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, } /** + * Folder equivalent to moveFileWithinStorage(). + * * @param string $sourceFolderIdentifier * @param string $targetFolderIdentifier * @param string $newFolderName - * @return array|void + * @return array All files which are affected, map of old => new file identifiers * @throws NotImplementedException */ public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) @@ -425,10 +472,12 @@ public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderId } /** + * Folder equivalent to copyFileWithinStorage(). + * * @param string $sourceFolderIdentifier * @param string $targetFolderIdentifier * @param string $newFolderName - * @return bool|void + * @return void * @throws NotImplementedException */ public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) @@ -437,28 +486,26 @@ public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderId } /** + * Returns the contents of a file. Beware that this requires to load the + * complete file into memory and also may require fetching the file from an + * external location. So this might be an expensive operation (both in terms + * of processing resources and money) for large files. + * * @param string $fileIdentifier - * @return string - * @throws Exception\FileDoesNotExistException + * @return string The file contents + * @throws NotImplementedException */ public function getFileContents($fileIdentifier) { - try { - $downloadLocation = $this->getAssetBankManager()->getMediaDownloadLocation($fileIdentifier)->wait(); - return file_get_contents($downloadLocation['s3_file']); - } catch (\Exception $exception) { - throw new Exception\FileDoesNotExistException( - sprintf('Requested file "%s" coudn\'t be found', $fileIdentifier), - 1519115242, - $exception - ); - } + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1530716278); } /** + * Sets the contents of a file to the specified value. + * * @param string $fileIdentifier * @param string $contents - * @return int|void + * @return int The number of bytes written to the file * @throws NotImplementedException */ public function setFileContents($fileIdentifier, $contents) @@ -467,21 +514,20 @@ public function setFileContents($fileIdentifier, $contents) } /** + * Checks if a file inside a folder exists + * * @param string $fileName * @param string $folderIdentifier * @return bool */ public function fileExistsInFolder($fileName, $folderIdentifier) { - if ($folderIdentifier !== $this->rootFolder) { - return false; - } - - // @todo: find a file by name instead of identifier - return false; + return !empty($fileName) && ($this->rootFolder === $folderIdentifier); } /** + * Checks if a folder inside a folder exists. + * * @param string $folderName * @param string $folderIdentifier * @return bool @@ -492,12 +538,27 @@ public function folderExistsInFolder($folderName, $folderIdentifier) return false; } + /** + * Returns a path to a local copy of a file for processing it. When changing the + * file, you have to take care of replacing the current version yourself! + * + * @param string $fileIdentifier + * @param bool $writable Set this to FALSE if you only need the file for read + * operations. This might speed up things, e.g. by using + * a cached local version. Never modify the file if you + * have set this flag! + * @return string The path to the file on the local disk + * @throws NotImplementedException + */ public function getFileForLocalProcessing($fileIdentifier, $writable = true) { - return $this->saveFileToTemporaryPath($fileIdentifier); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1530778712); } /** + * Returns the permissions of a file/folder as an array + * (keys r, w) of boolean flags + * * @param string $identifier * @return array */ @@ -515,41 +576,38 @@ public function getPermissions($identifier) * buffer before. Will be taken care of by the Storage. * * @param string $identifier - * @throws Exception\FileDoesNotExistException + * @throws NotImplementedException */ public function dumpFileContents($identifier) { - try { - $downloadLocation = $this->getAssetBankManager()->getMediaDownloadLocation($identifier)->wait(); - readfile($downloadLocation['s3_file'], 0); - } catch (\Exception $exception) { - throw new Exception\FileDoesNotExistException( - sprintf('Requested file "%s" coudn\'t be found', $identifier), - 1519115242, - $exception - ); - } + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1530716441); } /** + * Checks if a given identifier is within a container, e.g. if + * a file or folder is within another folder. + * This can e.g. be used to check for web-mounts. + * + * Hint: this also needs to return TRUE if the given identifier + * matches the container identifier to allow access to the root + * folder of a filemount. + * * @param string $folderIdentifier - * @param string $identifier - * @return bool + * @param string $identifier identifier to be checked against $folderIdentifier + * @return bool TRUE if $content is within or matches $folderIdentifier */ public function isWithin($folderIdentifier, $identifier) { - if ($folderIdentifier === $this->rootFolder) { - return true; - } else { - return false; - } + return ($folderIdentifier === $this->rootFolder); } /** + * Returns information about a file. + * * @param string $fileIdentifier - * @param array $propertiesToExtract + * @param array $propertiesToExtract Array of properties which are be extracted + * If empty all will be extracted * @return array - * @throws Exception\FileDoesNotExistException */ public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = []) { @@ -557,20 +615,14 @@ public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtr $fileIdentifier = $matches[1]; } - try { - $mediaInfo = $this->getBynderService()->getMediaInfo($fileIdentifier); - } catch (\Exception $exception) { - throw new Exception\FileDoesNotExistException( - sprintf('Requested file "%s" coudn\'t be found', $fileIdentifier), - 1519115242, - $exception - ); - } - - return $this->extractFileInformation($mediaInfo, $propertiesToExtract); + $bynderHelper = GeneralUtility::makeInstance(BynderHelper::class, 'bynder'); + $fileInfo = $bynderHelper->extractMetaData($fileIdentifier, $propertiesToExtract); + return $fileInfo; } /** + * Returns information about a folder. + * * @param string $folderIdentifier * @return array */ @@ -586,9 +638,11 @@ public function getFolderInfoByIdentifier($folderIdentifier) } /** + * Returns the identifier of a file inside the folder + * * @param string $fileName * @param string $folderIdentifier - * @return string + * @return string file identifier */ public function getFileInFolder($fileName, $folderIdentifier) { @@ -596,14 +650,20 @@ public function getFileInFolder($fileName, $folderIdentifier) } /** + * Returns a list of files inside the specified path + * * @param string $folderIdentifier * @param int $start * @param int $numberOfItems * @param bool $recursive - * @param array $filenameFilterCallbacks - * @param string $sort - * @param bool $sortRev - * @return array + * @param array $filenameFilterCallbacks callbacks for filtering the items + * @param string $sort Property name used to sort the items. + * Among them may be: '' (empty, no sorting), name, + * fileext, size, tstamp and rw. + * If a driver does not support the given property, it + * should fall back to "name". + * @param bool $sortRev TRUE to indicate reverse sorting (last to first) + * @return array of FileIdentifiers */ public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = false, array $filenameFilterCallbacks = [], $sort = '', $sortRev = false) { @@ -611,9 +671,11 @@ public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = } /** - * @param string $folderName + * Returns the identifier of a folder inside the folder + * + * @param string $folderName The name of the target folder * @param string $folderIdentifier - * @return string + * @return string folder identifier */ public function getFolderInFolder($folderName, $folderIdentifier) { @@ -621,25 +683,34 @@ public function getFolderInFolder($folderName, $folderIdentifier) } /** + * Returns a list of folders inside the specified path + * * @param string $folderIdentifier * @param int $start * @param int $numberOfItems * @param bool $recursive - * @param array $folderNameFilterCallbacks - * @param string $sort - * @param bool $sortRev - * @return array + * @param array $folderNameFilterCallbacks callbacks for filtering the items + * @param string $sort Property name used to sort the items. + * Among them may be: '' (empty, no sorting), name, + * fileext, size, tstamp and rw. + * If a driver does not support the given property, it + * should fall back to "name". + * @param bool $sortRev TRUE to indicate reverse sorting (last to first) + * @return array of Folder Identifier */ public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = false, array $folderNameFilterCallbacks = [], $sort = '', $sortRev = false) { return []; } + /** - * @param string $folderIdentifier + * Returns the number of files inside the specified path + * + * @param string $folderIdentifier * @param bool $recursive - * @param array $filenameFilterCallbacks - * @return int + * @param array $filenameFilterCallbacks callbacks for filtering the items + * @return int Number of files in folder */ public function countFilesInFolder($folderIdentifier, $recursive = false, array $filenameFilterCallbacks = []) { @@ -647,10 +718,12 @@ public function countFilesInFolder($folderIdentifier, $recursive = false, array } /** - * @param string $folderIdentifier + * Returns the number of folders inside the specified path + * + * @param string $folderIdentifier * @param bool $recursive - * @param array $folderNameFilterCallbacks - * @return int + * @param array $folderNameFilterCallbacks callbacks for filtering the items + * @return int Number of folders in folder */ public function countFoldersInFolder($folderIdentifier, $recursive = false, array $folderNameFilterCallbacks = []) { @@ -666,162 +739,4 @@ protected function isProcessedFile(string $fileIdentifier): bool return (bool)preg_match('/^processed_([0-9A-Z\-]{35})_([a-z]+)/', $fileIdentifier); } - /** - * @return AssetBankManager - * @throws \InvalidArgumentException - */ - protected function getAssetBankManager(): AssetBankManager - { - if ($this->assetBankManager === null) { - $this->assetBankManager = $this->getBynderService() - ->getBynderApi() - ->getAssetBankManager(); - } - - return $this->assetBankManager; - } - - /** - * @return BynderService - * @throws \InvalidArgumentException - */ - protected function getBynderService(): BynderService - { - if ($this->bynderService === null) { - $this->bynderService = GeneralUtility::makeInstance(BynderService::class); - } - - return $this->bynderService; - } - - /** - * Extracts information about a file from the filesystem. - * - * @param array $mediaInfo as returned from getAssetBankManager()->getMediaInfo() - * @param array $propertiesToExtract array of properties which should be returned, if empty all will be extracted - * @return array - */ - protected function extractFileInformation(array $mediaInfo, array $propertiesToExtract = []): array - { - if (empty($propertiesToExtract)) { - $propertiesToExtract = [ - 'size', - 'atime', - 'mtime', - 'ctime', - 'mimetype', - 'name', - 'extension', - 'identifier', - 'identifier_hash', - 'storage', - 'folder_hash' - ]; - } - $fileInformation = []; - foreach ($propertiesToExtract as $property) { - $fileInformation[$property] = $this->getSpecificFileInformation($mediaInfo, $property); - } - return $fileInformation; - } - - /** - * Extracts a specific FileInformation from the FileSystems. - * - * @param array $mediaInfo - * @param string $property - * - * @return bool|int|string - * @throws \InvalidArgumentException - */ - protected function getSpecificFileInformation($mediaInfo, $property) - { - switch ($property) { - case 'size': - return $mediaInfo['fileSize']; - case 'atime': - return strtotime($mediaInfo['dateModified']); - case 'mtime': - return strtotime($mediaInfo['dateModified']); - case 'ctime': - return strtotime($mediaInfo['dateCreated']); - case 'name': - return $mediaInfo['name'] . '.' . $mediaInfo['extension'][0]; - case 'mimetype': - // @todo: find beter way to determine mimetype - return $mediaInfo['type'] . '/' . $mediaInfo['extension'][0]; - case 'identifier': - return $mediaInfo['id']; - case 'extension': - return $mediaInfo['extension'][0]; - case 'storage': - return $this->storageUid; - case 'identifier_hash': - return $this->hashIdentifier($mediaInfo['id']); - case 'folder_hash': - return $this->hashIdentifier(''); - - // Metadata - case 'title': - return $mediaInfo['name']; - case 'description': - return $mediaInfo['description']; - case 'width': - return $mediaInfo['width']; - case 'height': - return $mediaInfo['height']; - case 'copyright': - return $mediaInfo['copyright']; - case 'keywords': - return implode(', ', $mediaInfo['tags'] ?? []); - default: - throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property), 1519130380); - } - } - - /** - * Save a file to a temporary path and returns that path. - * - * @param string $fileIdentifier - * @return string The temporary path - * @throws \RuntimeException - * @throws Exception\FileDoesNotExistException - */ - protected function saveFileToTemporaryPath($fileIdentifier): string - { - $temporaryPath = $this->getTemporaryPathForFile($fileIdentifier); - $result = file_put_contents($temporaryPath, $this->getFileContents($fileIdentifier)); - if ($result === false) { - throw new \RuntimeException( - 'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.', - 1519208427 - ); - } - return $temporaryPath; - } - - /** - * Returns a temporary path for a given file, including the file extension. - * - * @param string $fileIdentifier - * @return string - * @throws Exception\FileDoesNotExistException - */ - protected function getTemporaryPathForFile($fileIdentifier): string - { - list($fileExtension) = $this->getFileInfoByIdentifier($fileIdentifier, ['extension']); - $tempFile = GeneralUtility::tempnam('fal-tempfile-', '.' . $fileExtension); - - return self::$tempFiles[] = $tempFile; - } - - /** - * Cleanup temp files that a still present - */ - public function __destruct() - { - foreach (self::$tempFiles as $tempFile) { - GeneralUtility::unlink_tempfile($tempFile); - } - } } diff --git a/Classes/Resource/Helper/BynderHelper.php b/Classes/Resource/Helper/BynderHelper.php new file mode 100644 index 0000000..ed2424c --- /dev/null +++ b/Classes/Resource/Helper/BynderHelper.php @@ -0,0 +1,263 @@ +getBynderFileIdentifier($file); + if (!empty($identifier)) { + switch ($file->getMimeType()) { + case 'bynder/' . BynderDriver::ASSET_TYPE_IMAGE: + try { + $mediaInfo = $this->getBynderService()->getMediaInfo($identifier); + return $mediaInfo['thumbnails']['webimage']; + } catch (\Exception $e) { + return '/typo3conf/ext/bynder/Resources/Public/Icons/Extension.svg'; + } + break; + // TODO + // case 'bynder' . BynderDriver::ASSET_TYPE_DOCUMENT: + } + } + return null; + } + + public function generatePublicUrl($file, $relativeToCurrentScript, $width, $height): string + { + + } + + /** + * Get local absolute file path to preview image + * + * Return an empty string when no preview image is available + * + * @param File $file + * @return string + * @throws FileDoesNotExistException + */ + public function getPreviewImage(File $file): string + { + try { + return $this->downloadThumbnailToTemporaryFilePath($file); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Get meta data for OnlineMedia item + * + * See $GLOBALS[TCA][sys_file_metadata][columns] for possible fields to fill/use + * + * @param File $file + * @return array with metadata + */ + public function getMetaData(File $file): array + { + return $this->extractMetaData($this->getBynderFileIdentifier($file), ['title', 'description', 'width', 'height', 'copyright', 'keywords']); + } + + /** + * @param string $identifier + * @param array $propertiesToExtract + * @return array + */ + public function extractMetaData($identifier, $propertiesToExtract = []) + { + try { + $mediaInfo = $this->getBynderService()->getMediaInfo($identifier); + return $this->extractFileInformation($mediaInfo, $propertiesToExtract); + } catch (\Exception $e) { + } + return []; + } + + /** + * @param File $file + * @return string + */ + protected function getBynderFileIdentifier(File $file): string + { + return $file->getProperty('identifier'); + } + + /** + * Extracts information about a file from the filesystem. + * + * @param array $mediaInfo as returned from getAssetBankManager()->getMediaInfo() + * @param array $propertiesToExtract array of properties which should be returned, if empty all will be extracted + * @return array + */ + protected function extractFileInformation(array $mediaInfo, array $propertiesToExtract = []): array + { + if (empty($propertiesToExtract)) { + $propertiesToExtract = [ + 'size', + 'atime', + 'mtime', + 'ctime', + 'mimetype', + 'name', + 'extension', + 'identifier', + 'identifier_hash', + 'storage', + 'folder_hash' + ]; + } + $fileInformation = []; + foreach ($propertiesToExtract as $property) { + $fileInformation[$property] = $this->getSpecificFileInformation($mediaInfo, $property); + } + return $fileInformation; + } + + /** + * Extracts a specific FileInformation from the FileSystems. + * + * @param array $mediaInfo + * @param string $property + * + * @return bool|int|string + * @throws \InvalidArgumentException + */ + protected function getSpecificFileInformation($mediaInfo, $property) + { + switch ($property) { + case 'size': + return $mediaInfo['fileSize']; + case 'atime': + return strtotime($mediaInfo['dateModified']); + case 'mtime': + return strtotime($mediaInfo['dateModified']); + case 'ctime': + return strtotime($mediaInfo['dateCreated']); + case 'name': + return $mediaInfo['name'] . '.bynder'; + case 'mimetype': + return 'bynder/' . $mediaInfo['type']; + case 'identifier': + return $mediaInfo['id']; + case 'extension': + return 'bynder'; + case 'identifier_hash': + return sha1($mediaInfo['id']); + case 'storage': + return $this->getBynderStorage()->getUid(); + case 'folder_hash': + return sha1('bynder' . $this->getBynderStorage()->getUid()); + + // Metadata + case 'title': + return $mediaInfo['name']; + case 'description': + return $mediaInfo['description']; + case 'width': + return $mediaInfo['width']; + case 'height': + return $mediaInfo['height']; + case 'copyright': + return $mediaInfo['copyright']; + case 'keywords': + return implode(', ', $mediaInfo['tags'] ?? []); + default: + throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property), 1519130380); + } + } + + /** + * Save a file to a temporary path and returns that path. + * + * @param File $file + * @return string|null The temporary path + * @throws FileDoesNotExistException + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + protected function downloadThumbnailToTemporaryFilePath(File $file): string + { + try { + $mediaInfo = $this->getBynderService()->getMediaInfo($this->getBynderFileIdentifier($file)); + $url = $mediaInfo['thumbnails']['thul']; + $temporaryPath = $this->getTemporaryPathForFile($url); + if (!is_file($temporaryPath)) { + $report = []; + $data = GeneralUtility::getUrl($url, 0, false, $report); + if (!empty($data)) { + $result = GeneralUtility::writeFile($temporaryPath, $data); + if ($result === false) { + throw new \RuntimeException( + 'Copying file "' . $file->getIdentifier() . '" to temporary path "' . $temporaryPath . '" failed.', + 1519208427 + ); + } + } + } + } catch (\GuzzleHttp\Exception\RequestException $exception) { + throw new FileDoesNotExistException( + sprintf('Requested file " % s" coudn\'t be found', $file->getIdentifier()), + 1519115242, + $exception + ); + } + + return $temporaryPath ?? null; + } + + /** + * Returns a temporary path for a given file, including the file extension. + * + * @param string $url + * @return string + */ + protected function getTemporaryPathForFile($url): string + { + $temporaryPath = PATH_site . 'typo3temp/assets/' . BynderDriver::KEY . '/'; + if (!is_dir($temporaryPath)) { + GeneralUtility::mkdir_deep($temporaryPath); + } + $info = pathinfo($url); + return $temporaryPath . $info['filename'] . '.' . $info['extension']; + } + +} diff --git a/Classes/Metadata/Extractor.php b/Classes/Resource/Index/Extractor.php similarity index 64% rename from Classes/Metadata/Extractor.php rename to Classes/Resource/Index/Extractor.php index ee70898..3ec7f89 100644 --- a/Classes/Metadata/Extractor.php +++ b/Classes/Resource/Index/Extractor.php @@ -1,6 +1,6 @@ getOnlineMediaHelper($file) !== false; } /** @@ -65,17 +67,10 @@ public function canProcess(Resource\File $file) */ public function extractMetaData(Resource\File $file, array $previousExtractedData = []) { - $fileInfo = $file->getStorage()->getFileInfoByIdentifier( - $file->getIdentifier(), - [ - 'title', - 'description', - 'width', - 'height', - 'copyright', - 'keywords', - ] - ); - return array_merge($previousExtractedData, $fileInfo); + /** @var Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface $helper */ + $helper = Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($file); + $output = $previousExtractedData; + ArrayUtility::mergeRecursiveWithOverrule($output, ($helper !== false ? $helper->getMetaData($file) : [])); + return $output; } -} \ No newline at end of file +} diff --git a/Classes/Resource/Rendering/BynderRenderer.php b/Classes/Resource/Rendering/BynderRenderer.php new file mode 100644 index 0000000..0a2cdaa --- /dev/null +++ b/Classes/Resource/Rendering/BynderRenderer.php @@ -0,0 +1,106 @@ +getExtension() === 'bynder' + && ($file->getMimeType() === 'bynder/video') || ($file->getMimeType() === 'bynder/image')); + } + + /** + * Render for given File(Reference) HTML output + * + * @param FileInterface $file + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function render(FileInterface $file, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string + { + switch ($file->getMimeType()) { + case 'bynder/' . BynderDriver::ASSET_TYPE_IMAGE: + return $this->renderImage($file, $width, $height, $options, $usedPathsRelativeToCurrentScript); + break; + case 'bynder/' . BynderDriver::ASSET_TYPE_VIDEO: + return $this->renderVideo($file, $width, $height, $options, $usedPathsRelativeToCurrentScript); + break; + } + return ''; + } + + /** + * Render image for given File(Reference) HTML output + * + * @param FileInterface $file + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function renderImage($file, $width, $height, $options, $usedPathsRelativeToCurrentScript): string + { + if (is_callable([$file, 'getOriginalFile'])) { + $file = $file->getOriginalFile(); + } + + //return $this->getBynderHelper($file)->getPublicUrl($file, $usedPathsRelativeToCurrentScript); + return $this->getBynderHelper($file)->getPublicUrl($file, $usedPathsRelativeToCurrentScript). ' ' . $width . 'x' . $height . '?'; + } + + /** + * Render video for given File(Reference) HTML output + * + * @param FileInterface $file + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function renderVideo($file, $width, $height, $options, $usedPathsRelativeToCurrentScript): string + { + return 'video ' . $width . 'x' . $height . '?'; + } + + +} diff --git a/Classes/Slot/PublicUrlSlot.php b/Classes/Slot/PublicUrlSlot.php deleted file mode 100644 index b4d162a..0000000 --- a/Classes/Slot/PublicUrlSlot.php +++ /dev/null @@ -1,46 +0,0 @@ -getProperty('bynder_url')) { - $urlData['publicUrl'] = $resourceObject->getProperty('bynder_url'); - } - } -} \ No newline at end of file diff --git a/Classes/Traits/BynderHelper.php b/Classes/Traits/BynderHelper.php new file mode 100644 index 0000000..dcc4885 --- /dev/null +++ b/Classes/Traits/BynderHelper.php @@ -0,0 +1,37 @@ +getOnlineMediaHelper($file); + } else { + $helper = GeneralUtility::makeInstance(Helper\BynderHelper::class, 'bynder'); + } + + if ($helper instanceof Helper\BynderHelper) { + return $helper; + } + throw new InvalidObjectException('Bynder Helper cannot be initialized', 1530782854569); + } +} diff --git a/Classes/Traits/BynderService.php b/Classes/Traits/BynderService.php new file mode 100644 index 0000000..722ecae --- /dev/null +++ b/Classes/Traits/BynderService.php @@ -0,0 +1,24 @@ +bynderStorage === null) { + /** @var \TYPO3\CMS\Core\Resource\ResourceStorage $fileStorage */ + $backendUserAuthentication = $GLOBALS['BE_USER']; + foreach ($backendUserAuthentication->getFileStorages() as $fileStorage) { + if ($fileStorage->getDriverType() === 'bynder') { + return $this->bynderStorage = $fileStorage; + } + } + throw new \InvalidArgumentException('Missing Bynder file storage'); + } + return $this->bynderStorage; + } +} diff --git a/ext_localconf.php b/ext_localconf.php index 556939f..50948eb 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -23,18 +23,6 @@ // Register slot to use Bynder API for processed file $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class); -$signalSlotDispatcher->connect( - TYPO3\CMS\Core\Resource\ResourceStorage::class, - \TYPO3\CMS\Core\Resource\Service\FileProcessingService::SIGNAL_PreFileProcess, - \BeechIt\Bynder\Resource\AssetProcessing::class, - 'processFile' -); -$signalSlotDispatcher->connect( - \TYPO3\CMS\Core\Resource\ResourceStorage::class, - \TYPO3\CMS\Core\Resource\ResourceStorage::SIGNAL_PreGeneratePublicUrl, - \BeechIt\Bynder\Slot\PublicUrlSlot::class, - 'getPublicUrl' -); $signalSlotDispatcher->connect( \TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class, 'afterExtensionInstall', @@ -43,6 +31,12 @@ ); unset($signalSlotDispatcher); +$GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers']['bynder'] = \BeechIt\Bynder\Resource\Helper\BynderHelper::class; + +$rendererRegistry = \TYPO3\CMS\Core\Resource\Rendering\RendererRegistry::getInstance(); +$rendererRegistry->registerRendererClass(\BeechIt\Bynder\Resource\Rendering\BynderRenderer::class); +unset($rendererRegistry); + // Register hooks to post/delete usage registration $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = \BeechIt\Bynder\Hook\DataHandlerHook::class; @@ -65,10 +59,10 @@ // Register the extractor to fetch metadata from Bynder $extractorRegistry = \TYPO3\CMS\Core\Resource\Index\ExtractorRegistry::getInstance(); -$extractorRegistry->registerExtractionService(\BeechIt\Bynder\Metadata\Extractor::class); +$extractorRegistry->registerExtractionService(\BeechIt\Bynder\Resource\Index\Extractor::class); unset($extractorRegistry); if (!\TYPO3\CMS\Core\Core\Bootstrap::usesComposerClassLoading()) { require_once(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('bynder') . 'Resources/Private/PHP/autoload.php'); -} \ No newline at end of file +} From 34b643bfd94348a6e129a2ec57782a7e83fce9f7 Mon Sep 17 00:00:00 2001 From: Benjamin Serfhos Date: Fri, 6 Jul 2018 18:24:02 +0200 Subject: [PATCH 4/6] [WIP] Add different rendering option --- Classes/Backend/ImageManipulationElement.php | 4 + Classes/Backend/InlineControlContainer.php | 31 +-- Classes/Controller/CompactViewController.php | 11 +- Classes/Resource/BynderDriver.php | 1 - Classes/Resource/Helper/BynderHelper.php | 36 +-- .../Rendering/BynderImageRenderer.php | 216 ++++++++++++++++++ Classes/Resource/Rendering/BynderRenderer.php | 106 --------- .../Rendering/BynderVideoRenderer.php | 108 +++++++++ Classes/Service/BynderService.php | 106 +-------- Classes/Traits/BynderService.php | 2 - Classes/Utility/ConfigurationUtility.php | 118 ++++++++-- README.md | 2 +- ext_conf_template.txt | 9 +- ext_localconf.php | 3 +- 14 files changed, 471 insertions(+), 282 deletions(-) create mode 100644 Classes/Resource/Rendering/BynderImageRenderer.php delete mode 100644 Classes/Resource/Rendering/BynderRenderer.php create mode 100644 Classes/Resource/Rendering/BynderVideoRenderer.php diff --git a/Classes/Backend/ImageManipulationElement.php b/Classes/Backend/ImageManipulationElement.php index e584202..989a419 100644 --- a/Classes/Backend/ImageManipulationElement.php +++ b/Classes/Backend/ImageManipulationElement.php @@ -16,6 +16,10 @@ */ class ImageManipulationElement extends \TYPO3\CMS\Backend\Form\Element\ImageManipulationElement { + /** + * @return array + * @throws \TYPO3\CMS\Core\Imaging\ImageManipulation\InvalidConfigurationException + */ public function render() { $resultArray = $this->initializeResultArray(); diff --git a/Classes/Backend/InlineControlContainer.php b/Classes/Backend/InlineControlContainer.php index 48cc2b2..b124d27 100644 --- a/Classes/Backend/InlineControlContainer.php +++ b/Classes/Backend/InlineControlContainer.php @@ -8,10 +8,10 @@ * All code (c) Beech.it all rights reserved */ use BeechIt\Bynder\Resource\BynderDriver; +use BeechIt\Bynder\Utility\ConfigurationUtility; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Resource\ResourceStorage; -use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Class InlineControlContainer @@ -64,13 +64,14 @@ protected function renderBynderButton(array $inlineConfiguration): string $foreign_table = $inlineConfiguration['foreign_table']; $allowed = $groupFieldConfiguration['allowed']; + $allowedAssetTypes = ConfigurationUtility::getAssetTypesByAllowedElements($groupFieldConfiguration['appearance']['elementBrowserAllowed']); $currentStructureDomObjectIdPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); $objectPrefix = $currentStructureDomObjectIdPrefix . '-' . $foreign_table; $nameObject = $currentStructureDomObjectIdPrefix; $compactViewUrl = BackendUtility::getModuleUrl('bynder_compact_view', [ 'element' => 'bynder' . $this->inlineData['config'][$nameObject]['md5'], - 'assetTypes' => $this->getAssetTypesByAllowedElements($groupFieldConfiguration['appearance']['elementBrowserAllowed']) + 'assetTypes' => implode(',', $allowedAssetTypes) ]); $this->requireJsModules[] = 'TYPO3/CMS/Bynder/CompactView'; @@ -109,30 +110,4 @@ protected function bynderStorageAvailable(): bool } return false; } - - /** - * @param string $allowedElements - * @return string - */ - protected function getAssetTypesByAllowedElements($allowedElements): string - { - $assetTypes = []; - if (empty($allowedElements)) { - $assetTypes = [BynderDriver::ASSET_TYPE_IMAGE, BynderDriver::ASSET_TYPE_VIDEO]; - } else { - $allowedElements = GeneralUtility::trimExplode(',', strtolower($allowedElements), true); - foreach (['jpg', 'png', 'gif'] as $element) { - if (in_array($element, $allowedElements)) { - $assetTypes[] = BynderDriver::ASSET_TYPE_IMAGE; - break; - } - } - - if (in_array('mp4', $allowedElements)) { - $assetTypes[] = BynderDriver::ASSET_TYPE_VIDEO; - } - } - - return implode(',', $assetTypes); - } } diff --git a/Classes/Controller/CompactViewController.php b/Classes/Controller/CompactViewController.php index 6b7cd3f..cfdca96 100644 --- a/Classes/Controller/CompactViewController.php +++ b/Classes/Controller/CompactViewController.php @@ -8,7 +8,7 @@ * All code (c) Beech.it all rights reserved */ -use BeechIt\Bynder\Service\BynderService; +use BeechIt\Bynder\Utility\ConfigurationUtility; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; @@ -23,6 +23,7 @@ */ class CompactViewController { + /** * Fluid Standalone View * @@ -51,18 +52,12 @@ class CompactViewController */ protected $layoutRootPaths = ['EXT:bynder/Resources/Private/Layouts/CompactView']; - /** - * @var BynderService - */ - protected $bynderService; /** * CompactViewController constructor. */ public function __construct() { - $this->bynderService = GeneralUtility::makeInstance(BynderService::class); - $this->view = GeneralUtility::makeInstance(StandaloneView::class); $this->view->setPartialRootPaths($this->partialRootPaths); $this->view->setTemplateRootPaths($this->templateRootPaths); @@ -82,7 +77,7 @@ public function indexAction(ServerRequestInterface $request, ResponseInterface $ $this->view->assignMultiple([ 'language' => $this->getBackendUserAuthentication()->uc['lang'] ?: ($this->getBackendUserAuthentication()->user['lang'] ?: 'en_EN'), - 'apiBaseUrl' => $this->bynderService->getApiBaseUrl(), + 'apiBaseUrl' => ConfigurationUtility::getApiBaseUrl(), 'element' => $request->getQueryParams()['element'], 'assetTypes' => $request->getQueryParams()['assetTypes'] ]); diff --git a/Classes/Resource/BynderDriver.php b/Classes/Resource/BynderDriver.php index ed566e2..17badf2 100644 --- a/Classes/Resource/BynderDriver.php +++ b/Classes/Resource/BynderDriver.php @@ -738,5 +738,4 @@ protected function isProcessedFile(string $fileIdentifier): bool { return (bool)preg_match('/^processed_([0-9A-Z\-]{35})_([a-z]+)/', $fileIdentifier); } - } diff --git a/Classes/Resource/Helper/BynderHelper.php b/Classes/Resource/Helper/BynderHelper.php index ed2424c..8f8358f 100644 --- a/Classes/Resource/Helper/BynderHelper.php +++ b/Classes/Resource/Helper/BynderHelper.php @@ -5,8 +5,10 @@ use BeechIt\Bynder\Resource\BynderDriver; use BeechIt\Bynder\Traits\BynderService; use BeechIt\Bynder\Traits\BynderStorage; +use BeechIt\Bynder\Utility\ConfigurationUtility; use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Utility\DebuggerUtility; @@ -17,10 +19,13 @@ */ class BynderHelper extends \TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\AbstractOnlineMediaHelper { - use BynderService; use BynderStorage; + const DERIVATIVES_WEB_IMAGE = 'webimage'; + const DERIVATIVES_MINI = 'mini'; + const DERIVATIVES_THUMBNAIL = 'thul'; + /** * Try to transform given URL to a File * @@ -33,6 +38,16 @@ public function transformUrlToFile($url, Folder $targetFolder): File DebuggerUtility::var_dump(__METHOD__); } + /** + * @param string $identifier + * @return array + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + public function getBynderMediaInfo($identifier): array + { + return $this->getBynderService()->getMediaInfo($identifier); + } + /** * Get public url * @@ -49,22 +64,18 @@ public function getPublicUrl(File $file, $relativeToCurrentScript = false): stri switch ($file->getMimeType()) { case 'bynder/' . BynderDriver::ASSET_TYPE_IMAGE: try { - $mediaInfo = $this->getBynderService()->getMediaInfo($identifier); + $mediaInfo = $this->getBynderMediaInfo($identifier); return $mediaInfo['thumbnails']['webimage']; } catch (\Exception $e) { - return '/typo3conf/ext/bynder/Resources/Public/Icons/Extension.svg'; + return ConfigurationUtility::getUnavailableImage($relativeToCurrentScript); } break; // TODO // case 'bynder' . BynderDriver::ASSET_TYPE_DOCUMENT: } } - return null; - } - - public function generatePublicUrl($file, $relativeToCurrentScript, $width, $height): string - { + return ''; } /** @@ -106,7 +117,7 @@ public function getMetaData(File $file): array public function extractMetaData($identifier, $propertiesToExtract = []) { try { - $mediaInfo = $this->getBynderService()->getMediaInfo($identifier); + $mediaInfo = $mediaInfo = $this->getBynderMediaInfo($identifier); return $this->extractFileInformation($mediaInfo, $propertiesToExtract); } catch (\Exception $e) { } @@ -114,10 +125,10 @@ public function extractMetaData($identifier, $propertiesToExtract = []) } /** - * @param File $file + * @param FileInterface $file * @return string */ - protected function getBynderFileIdentifier(File $file): string + protected function getBynderFileIdentifier(FileInterface $file): string { return $file->getProperty('identifier'); } @@ -214,7 +225,7 @@ protected function getSpecificFileInformation($mediaInfo, $property) * @throws FileDoesNotExistException * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ - protected function downloadThumbnailToTemporaryFilePath(File $file): string + protected function downloadThumbnailToTemporaryFilePath(File $file, $type = 'thul'): string { try { $mediaInfo = $this->getBynderService()->getMediaInfo($this->getBynderFileIdentifier($file)); @@ -259,5 +270,4 @@ protected function getTemporaryPathForFile($url): string $info = pathinfo($url); return $temporaryPath . $info['filename'] . '.' . $info['extension']; } - } diff --git a/Classes/Resource/Rendering/BynderImageRenderer.php b/Classes/Resource/Rendering/BynderImageRenderer.php new file mode 100644 index 0000000..693cfa0 --- /dev/null +++ b/Classes/Resource/Rendering/BynderImageRenderer.php @@ -0,0 +1,216 @@ +getExtension() === 'bynder' && ($file->getMimeType() === 'bynder/image')); + } + + /** + * Render for given File(Reference) HTML output + * + * @param FileInterface $file + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function render(FileInterface $file, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string + { + if (!($file instanceof File) && is_callable([$file, 'getOriginalFile'])) { + $originalFile = $file->getOriginalFile(); + } else { + $originalFile = $file; + } + + return $this->renderImageTag( + $file, + $this->getBynderHelper($originalFile)->getPublicUrl($originalFile), + $width, $height + ); + } + + /** + * Render image for given File(Reference) HTML output + * + * @param FileInterface $file + * @param string $source + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function renderImageTag($file, $source, $width, $height): string + { + $tag = GeneralUtility::makeInstance(\TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder::class); + $tag->setTagName('img'); + + $tag->addAttribute('src', $source); + $tag->addAttribute('width', $width); + $tag->addAttribute('height', $height); + + $alt = $file->getProperty('alternative'); + $title = $file->getProperty('title'); + + // The alt-attribute is mandatory to have valid html-code, therefore add it even if it is empty + if ($alt) { + $tag->addAttribute('alt', $alt); + } + if ($title) { + $tag->addAttribute('title', $title); + } + + return $tag->render(); + } + + /** + * Calculate the best suitable/available dimensions for the requested file configuration + * + * @param array $configuration + * @param int $orgWidth + * @param int $orgHeight + * @param string $otfBaseUrl + * @param array $derivatives + * @return array + */ + protected function calculateImagelInfo(array $configuration, $width, $height, string $otfBaseUrl, array $derivatives): array + { + $width = $configuration['width'] ?? $configuration['maxWidth'] ?? 0; + $height = $configuration['height'] ?? $configuration['maxHeight'] ?? 0; + + $keepRatio = true; + $crop = false; + $otf = $otfBaseUrl !== ''; + + // When width and height are set and non of them have a 'm' suffix we don't keep existing ratio + if ($width && $height && strpos($width . $height, 'm') < 0) { + $keepRatio = false; + } + + // When width and height are set and one of then have a 'c' suffix we don't keep existing ratio and allow cropping + if ($width && $height && strpos($width . $height, 'c') >= 0) { + $keepRatio = false; + $crop = true; + } + + $width = (int)$width; + $height = (int)$height; + + if (!$keepRatio && $width > $orgWidth) { + $height = $this->calculateRelativeDimension($width, $height, $orgWidth); + $width = $orgWidth; + } elseif (!$keepRatio && $height > $orgHeight) { + $width = $this->calculateRelativeDimension($height, $width, $orgHeight); + $height = $orgHeight; + } elseif ($keepRatio && $width > $orgWidth) { + $height = $orgWidth / $width * $height; + } elseif ($keepRatio && $height > $orgHeight) { + $height = $orgHeight / $height * $width; + } elseif ($width === 0 && $height > 0) { + $height = $this->calculateRelativeDimension($orgWidth, $orgHeight, $width); + } elseif ($width === 0 && $height > 0) { + $width = $this->calculateRelativeDimension($orgHeight, $orgWidth, $height); + } + + if ($otf) { + return [ + 'type' => 'otf', + 'width' => $width ?: $orgWidth, + 'height' => $height ?: $orgHeight, + 'url' => $otfBaseUrl . '?' . http_build_query([ + 'w' => $width ?: '', + 'h' => $height ?: '', + 'crop' => $crop ? 'true' : 'false' + ]), + ]; + } + + $default = [ + 'type' => 'webimage', + 'width' => $width, + 'height' => $height, + 'url' => $derivatives['webimage'], + ]; + if ($height === 0 && $width === 0) { + return $default; + } + if ($width <= 80) { + return [ + 'type' => 'mini', + 'width' => $width, + 'height' => $width, // derivative/image is square + 'url' => $derivatives['mini'], + ]; + } elseif ($width <= 250) { + return [ + 'type' => 'thul', + 'width' => $width, + 'height' => $height, + 'url' => $derivatives['thul'], + ]; + } + + return $default; + } + + /** + * Calculate relative dimension + * + * For instance you have the original width, height and new width. + * And want to calculate the new height with the same ratio as the original dimensions + * + * @param int $orgA + * @param int $orgB + * @param int $newA + * @return int + */ + protected function calculateRelativeDimension(int $orgA, int $orgB, int $newA): int + { + if ($newA === 0) { + return $orgB; + } + + return (int)($orgB / ($orgA / $newA)); + } + + +} diff --git a/Classes/Resource/Rendering/BynderRenderer.php b/Classes/Resource/Rendering/BynderRenderer.php deleted file mode 100644 index 0a2cdaa..0000000 --- a/Classes/Resource/Rendering/BynderRenderer.php +++ /dev/null @@ -1,106 +0,0 @@ -getExtension() === 'bynder' - && ($file->getMimeType() === 'bynder/video') || ($file->getMimeType() === 'bynder/image')); - } - - /** - * Render for given File(Reference) HTML output - * - * @param FileInterface $file - * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c - * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c - * @param array $options - * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() - * @return string - */ - public function render(FileInterface $file, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string - { - switch ($file->getMimeType()) { - case 'bynder/' . BynderDriver::ASSET_TYPE_IMAGE: - return $this->renderImage($file, $width, $height, $options, $usedPathsRelativeToCurrentScript); - break; - case 'bynder/' . BynderDriver::ASSET_TYPE_VIDEO: - return $this->renderVideo($file, $width, $height, $options, $usedPathsRelativeToCurrentScript); - break; - } - return ''; - } - - /** - * Render image for given File(Reference) HTML output - * - * @param FileInterface $file - * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c - * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c - * @param array $options - * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() - * @return string - */ - public function renderImage($file, $width, $height, $options, $usedPathsRelativeToCurrentScript): string - { - if (is_callable([$file, 'getOriginalFile'])) { - $file = $file->getOriginalFile(); - } - - //return $this->getBynderHelper($file)->getPublicUrl($file, $usedPathsRelativeToCurrentScript); - return $this->getBynderHelper($file)->getPublicUrl($file, $usedPathsRelativeToCurrentScript). ' ' . $width . 'x' . $height . '?'; - } - - /** - * Render video for given File(Reference) HTML output - * - * @param FileInterface $file - * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c - * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c - * @param array $options - * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() - * @return string - */ - public function renderVideo($file, $width, $height, $options, $usedPathsRelativeToCurrentScript): string - { - return 'video ' . $width . 'x' . $height . '?'; - } - - -} diff --git a/Classes/Resource/Rendering/BynderVideoRenderer.php b/Classes/Resource/Rendering/BynderVideoRenderer.php new file mode 100644 index 0000000..6cc731e --- /dev/null +++ b/Classes/Resource/Rendering/BynderVideoRenderer.php @@ -0,0 +1,108 @@ +getExtension() === 'bynder' && $file->getMimeType() === 'bynder/video'); + } + + /** + * Render for given File(Reference) HTML output + * + * @param FileInterface $file + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function render(FileInterface $file, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string + { + return $this->renderVideo($file, $width, $height, $options, $usedPathsRelativeToCurrentScript); + } + + /** + * Render video for given File(Reference) HTML output + * + * @param FileInterface $source + * @param int|string $width in video, always 0 + * @param int|string $height in video, always 0 + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript + * @return string + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + * @throws \TYPO3\CMS\Extbase\Object\InvalidObjectException + */ + public function renderVideo($source, $width, $height, $options, $usedPathsRelativeToCurrentScript): string + { + $sources = []; + + try { + if ($source instanceof File) { + $file = $source; + } elseif (is_callable([$source, 'getOriginalFile'])) { + $file = $source->getOriginalFile(); + } + + $mediaInfo = $this->getBynderHelper($file)->getBynderMediaInfo($file->getIdentifier()); + foreach ((array)$mediaInfo['videoPreviewURLs'] as $url) { + switch (pathinfo($url, PATHINFO_EXTENSION)) { + case 'webm': + $sources[$url] = ''; + break; + case 'mp4': + $sources[$url] = ''; + break; + } + } + }catch (\Exception $e){ + // Catch all exceptions as these should never crash the frontend website + } + + if (empty($sources)) { + return ''; + } else { + return ''; + } + } + + +} diff --git a/Classes/Service/BynderService.php b/Classes/Service/BynderService.php index 94abc95..7ebe57f 100644 --- a/Classes/Service/BynderService.php +++ b/Classes/Service/BynderService.php @@ -7,7 +7,7 @@ * Date: 19-2-18 * All code (c) Beech.it all rights reserved */ -use BeechIt\Bynder\Exception\InvalidExtensionConfigurationException; +use BeechIt\Bynder\Utility\ConfigurationUtility; use Bynder\Api\BynderApiFactory; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; @@ -25,36 +25,6 @@ class BynderService implements SingletonInterface */ protected $bynderIntegrationId = '8517905e-6c2f-47c3-96ca-0312027bbc95'; - /** - * @var string - */ - protected $apiBaseUrl; - - /** - * @var string - */ - protected $otfBaseUrl; - - /** - * @var string - */ - protected $oAuthConsumerKey; - - /** - * @var string - */ - protected $oAuthConsumerSecret; - - /** - * @var string - */ - protected $oAuthTokenKey; - - /** - * @var string - */ - protected $oAuthTokenSecret; - /** * @var \Bynder\Api\Impl\BynderApi */ @@ -65,85 +35,13 @@ class BynderService implements SingletonInterface */ protected $cache; - public function __construct() - { - $extensionConfiguration = \BeechIt\Bynder\Utility\ConfigurationUtility::getExtensionConfiguration(); - - $this->apiBaseUrl = $extensionConfiguration['url'] ?? ''; - $this->otfBaseUrl = $extensionConfiguration['otf_base_url'] ?? ''; - $this->oAuthConsumerKey = $extensionConfiguration['consumer_key'] ?? null; - $this->oAuthConsumerSecret = $extensionConfiguration['consumer_secret'] ?? null; - $this->oAuthTokenKey = $extensionConfiguration['token_key'] ?? null; - $this->oAuthTokenSecret = $extensionConfiguration['token_secret'] ?? null; - - if (empty($this->apiBaseUrl) || empty($this->oAuthConsumerKey) || empty($this->oAuthConsumerSecret) || empty($this->oAuthTokenKey) || empty($this->oAuthTokenSecret)) { - throw new InvalidExtensionConfigurationException('Make sure all Bynder oAuth settings are set in extension manager', 1519051718); - } - } - /** * @return \Bynder\Api\Impl\BynderApi * @throws \InvalidArgumentException */ public function getBynderApi() { - return BynderApiFactory::create( - [ - 'consumerKey' => $this->oAuthConsumerKey, - 'consumerSecret' => $this->oAuthConsumerSecret, - 'token' => $this->oAuthTokenKey, - 'tokenSecret' => $this->oAuthTokenSecret, - 'baseUrl' => $this->apiBaseUrl - ] - ); - } - - /** - * @return string - */ - public function getApiBaseUrl(): string - { - return $this->apiBaseUrl; - } - - /** - * @return string - */ - public function getOtfBaseUrl(): string - { - return $this->otfBaseUrl; - } - - /** - * @return string - */ - public function getOAuthConsumerKey(): string - { - return $this->oAuthConsumerKey; - } - - /** - * @return string - */ - public function getOAuthConsumerSecret(): string - { - return $this->oAuthConsumerSecret; - } - - /** - * @return string - */ - public function getOAuthTokenKey(): string - { - return $this->oAuthTokenKey; - } - - /** - * @return string - */ - public function getOAuthTokenSecret(): string - { - return $this->oAuthTokenSecret; + return BynderApiFactory::create(ConfigurationUtility::getBynderApiFactoryCredentials()); } /** diff --git a/Classes/Traits/BynderService.php b/Classes/Traits/BynderService.php index 722ecae..937073b 100644 --- a/Classes/Traits/BynderService.php +++ b/Classes/Traits/BynderService.php @@ -19,6 +19,4 @@ protected function getBynderService(): Service\BynderService { return GeneralUtility::makeInstance(Service\BynderService::class); } - - } diff --git a/Classes/Utility/ConfigurationUtility.php b/Classes/Utility/ConfigurationUtility.php index 4d6a52f..4112c09 100644 --- a/Classes/Utility/ConfigurationUtility.php +++ b/Classes/Utility/ConfigurationUtility.php @@ -2,8 +2,11 @@ namespace BeechIt\Bynder\Utility; +use BeechIt\Bynder\Exception\InvalidExtensionConfigurationException; +use BeechIt\Bynder\Resource\BynderDriver; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility as CoreConfigurationUtility; @@ -16,29 +19,112 @@ class ConfigurationUtility const EXTENSION = 'bynder'; /** + * @param string $allowedElements * @return array */ - public static function getExtensionConfiguration(): array + public static function getAssetTypesByAllowedElements($allowedElements): array { - $objectManager = GeneralUtility::makeInstance(ObjectManager::class); - if (class_exists(CoreConfigurationUtility::class)) { - $configuration = $objectManager->get(CoreConfigurationUtility::class)->getCurrentConfiguration('bynder'); - $extensionConfiguration = []; - foreach ($configuration as $key => $value) { - $extensionConfiguration[$key] = $value['value']; - } + $assetTypes = []; + if (empty($allowedElements)) { + $assetTypes = [BynderDriver::ASSET_TYPE_IMAGE, BynderDriver::ASSET_TYPE_VIDEO]; } else { - $extensionConfiguration = $objectManager->get(ExtensionConfiguration::class)->get('bynder'); - } + $allowedElements = GeneralUtility::trimExplode(',', strtolower($allowedElements), true); + $allowed = [ + BynderDriver::ASSET_TYPE_IMAGE => ((self::getExtensionConfiguration())['asset_type_image'] ?? 'jpg,png,gif'), + BynderDriver::ASSET_TYPE_VIDEO => ((self::getExtensionConfiguration())['asset_type_video'] ?? 'mp4,mov') + ]; - if (isset($extensionConfiguration['url'])) { - $extensionConfiguration['url'] = static::cleanUrl($extensionConfiguration['url']); + foreach (array_filter($allowed) as $key => $elements) { + foreach (GeneralUtility::trimExplode(',', $elements, true) as $element) { + if (in_array($element, $allowedElements)) { + $assetTypes[] = $key; + break; + } + } + } } - $extensionConfiguration['otf_base_url'] = $extensionConfiguration['otf_base_url'] ?? $extensionConfiguration['otfBaseUrl'] ?? null; - if (isset($extensionConfiguration['otf_base_url'])) { - $extensionConfiguration['otf_base_url'] = static::cleanUrl($extensionConfiguration['otf_base_url']); + + return $assetTypes; + } + + /** + * @param boolean $relativeToCurrentScript + * @return string + */ + public static function getUnavailableImage($relativeToCurrentScript = false): string + { + $path = GeneralUtility::getFileAbsFileName( + (self::getExtensionConfiguration())['image_unavailable'] ?? + 'EXT:bynder/Resources/Public/Icons/ImageUnavailable.svg' + ); + + return ($relativeToCurrentScript) ? PathUtility::getAbsoluteWebPath($path) : str_replace(PATH_site, '', $path); + } + + /** + * @return array + * @throws InvalidExtensionConfigurationException + */ + public static function getBynderApiFactoryCredentials(): array + { + $credentials = [ + 'baseUrl' => static::getApiBaseUrl(), + 'consumerKey' => ((self::getExtensionConfiguration())['consumer_key'] ?? ''), + 'consumerSecret' => ((self::getExtensionConfiguration())['consumer_secret'] ?? ''), + 'token' => ((self::getExtensionConfiguration())['token_key'] ?? ''), + 'tokenSecret' => ((self::getExtensionConfiguration())['token_secret'] ?? ''), + ]; + return $credentials; + } + + /** + * @return string + * @throws InvalidExtensionConfigurationException + */ + public static function getApiBaseUrl(): string + { + return static::cleanUrl((self::getExtensionConfiguration())['url']); + } + + /** + * @return array + * @throws InvalidExtensionConfigurationException + */ + public static function getExtensionConfiguration(): array + { + static $configuration; + if ($configuration === null) { + $configuration = [ + 'url' => '', + 'otf_base_url' => '', + 'consumer_key' => '', + 'consumer_secret' => '', + 'token_key' => '', + 'token_secret' => '', + 'image_unavailable' => 'EXT:bynder/Resources/Public/Icons/ImageUnavailable.svg', + 'asset_type_image' => 'jpg,png,gif', + 'asset_type_video' => 'mp4' + ]; + + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + if (class_exists(CoreConfigurationUtility::class)) { + $currentConfiguration = $objectManager->get(CoreConfigurationUtility::class)->getCurrentConfiguration('bynder'); + $configuration = []; + foreach ($currentConfiguration as $key => $value) { + $configuration[$key] = $value['value']; + } + } else { + \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($configuration, (array)$objectManager->get(ExtensionConfiguration::class)->get('bynder')); + } + + if (empty($configuration['url']) || + empty($configuration['consumer_key']) || empty($configuration['consumer_secret']) || + empty($configuration['token_key']) || empty($configuration['token_secret']) + ) { + throw new InvalidExtensionConfigurationException('Make sure all Bynder oAuth settings are set in extension manager', 1519051718); + } } - return $extensionConfiguration; + return $configuration; } /** diff --git a/README.md b/README.md index 2d3a013..9b2c138 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Next got the the extension configuration of EXT:bynder and fill in the needed ur | token_secret | Bynder OAuth token secret | Yes | | | image_unavailable | Displayed image when file is not retrievable like when the file status is deleted or unpublished | *No* | EXT:bynder/Resources/Public/Icons/ImageUnavailable.svg | -(1) See: https://help.bynder.com/Modules/Asset-Bank/Modify-public-derivatives-on-the-fly.htm?Highlight=on-the-fly#prereq +(1) See: https://help.bynder.com/Modules/Asset-Library/Modify-public-derivatives-on-the-fly.htm ### How to contribute diff --git a/ext_conf_template.txt b/ext_conf_template.txt index 577448f..29b006c 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -1,7 +1,7 @@ -# cat=basic/API; type=string; label=Bynder Url (domain.bynder.com) +# cat=basic/API; type=string; label=Bynder Url: bynder.yourwebsite.com url = -# cat=basic/API; type=string; label=OnTheFly derivative's Url (optional);(https://help.bynder.com/Modules/Asset-Bank/Modify-public-derivatives-on-the-fly.htm?Highlight=on-the-fly#prereq) +# cat=basic/API; type=string; label=OnTheFly derivative's Url (optional): yourdomain.bynder.com; (https://help.bynder.com/Modules/Asset-Bank/Modify-public-derivatives-on-the-fly.htm?Highlight=on-the-fly#prereq) otf_base_url = # cat=basic/API; type=string; label=Bynder OAuth consumer (key) @@ -19,3 +19,8 @@ token_secret = # cat=basic/API; type=string; label=Image Unavailable: Displayed image when file is not retrievable like when the file status is deleted or unpublished image_unavailable = EXT:bynder/Resources/Public/Icons/ImageUnavailable.svg +# cat=basic/assets; type=string; label=File extensions for images; Used for images in your Bynder environment +asset_type_image = jpg,png,gif + +# cat=basic/assets; type=string; label=File extensions for videos; Extensions used for video interaction in your Bynder environment +asset_type_video = mp4,mov diff --git a/ext_localconf.php b/ext_localconf.php index 50948eb..77708bb 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -34,7 +34,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers']['bynder'] = \BeechIt\Bynder\Resource\Helper\BynderHelper::class; $rendererRegistry = \TYPO3\CMS\Core\Resource\Rendering\RendererRegistry::getInstance(); -$rendererRegistry->registerRendererClass(\BeechIt\Bynder\Resource\Rendering\BynderRenderer::class); +$rendererRegistry->registerRendererClass(\BeechIt\Bynder\Resource\Rendering\BynderVideoRenderer::class); +$rendererRegistry->registerRendererClass(\BeechIt\Bynder\Resource\Rendering\BynderImageRenderer::class); unset($rendererRegistry); // Register hooks to post/delete usage registration From a5824d3f5112a0876044761ecbe40462813ccde3 Mon Sep 17 00:00:00 2001 From: Benjamin Serfhos Date: Mon, 16 Jul 2018 16:05:07 +0200 Subject: [PATCH 5/6] [TASK] Finetune workflow for internal images --- .php_cs | 13 - Classes/Backend/InlineControlContainer.php | 2 +- Classes/Exception/BynderException.php | 2 +- ...InvalidExtensionConfigurationException.php | 2 +- Classes/Exception/NotImplementedException.php | 2 +- Classes/Hook/DataHandlerHook.php | 2 +- Classes/Resource/BynderDriver.php | 1 - Classes/Resource/Helper/BynderHelper.php | 89 ++++-- .../Rendering/BynderImageRenderer.php | 258 +++++++++++------- .../Rendering/BynderVideoRenderer.php | 83 +++--- Classes/Service/BynderService.php | 1 - Classes/Service/TagBuilderService.php | 63 +++++ Classes/Slot/InstallSlot.php | 2 +- Classes/Utility/ConfigurationUtility.php | 20 ++ Configuration/Backend/AjaxRoutes.php | 2 +- Configuration/Backend/Routes.php | 2 +- Resources/Private/Language/locallang_be.xlf | 6 +- Resources/Public/JavaScript/video-js.min.js | 12 + Resources/Public/Styles/video-js.min.css | 1 + ext_emconf.php | 2 +- 20 files changed, 384 insertions(+), 181 deletions(-) delete mode 100644 .php_cs create mode 100644 Classes/Service/TagBuilderService.php create mode 100644 Resources/Public/JavaScript/video-js.min.js create mode 100644 Resources/Public/Styles/video-js.min.css diff --git a/.php_cs b/.php_cs deleted file mode 100644 index e68b9be..0000000 --- a/.php_cs +++ /dev/null @@ -1,13 +0,0 @@ -in('./Classes', './Tests'); - -return PhpCsFixer\Config::create() - ->setRules([ - '@PSR2' => true, - 'single_blank_line_at_eof' => false, - 'single_quote' => true, - 'array_syntax' => ['syntax' => 'short'], - 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], - ]) - ->setFinder($finder); \ No newline at end of file diff --git a/Classes/Backend/InlineControlContainer.php b/Classes/Backend/InlineControlContainer.php index b124d27..0c1e79d 100644 --- a/Classes/Backend/InlineControlContainer.php +++ b/Classes/Backend/InlineControlContainer.php @@ -34,7 +34,7 @@ protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfigu // Inject button before help-block if (strpos($selector, '
') > 0) { $selector = str_replace('
', $button . '
', $selector); - // Try to inject it into the form-control container + // Try to inject it into the form-control container } elseif (preg_match('/<\/div><\/div>$/i', $selector)) { $selector = preg_replace('/<\/div><\/div>$/i', $button . '
', $selector); } else { diff --git a/Classes/Exception/BynderException.php b/Classes/Exception/BynderException.php index abcdcee..8b45e05 100644 --- a/Classes/Exception/BynderException.php +++ b/Classes/Exception/BynderException.php @@ -10,4 +10,4 @@ abstract class BynderException extends \Exception { -} \ No newline at end of file +} diff --git a/Classes/Exception/InvalidExtensionConfigurationException.php b/Classes/Exception/InvalidExtensionConfigurationException.php index 20aa69d..d68e1bf 100644 --- a/Classes/Exception/InvalidExtensionConfigurationException.php +++ b/Classes/Exception/InvalidExtensionConfigurationException.php @@ -10,4 +10,4 @@ class InvalidExtensionConfigurationException extends BynderException { -} \ No newline at end of file +} diff --git a/Classes/Exception/NotImplementedException.php b/Classes/Exception/NotImplementedException.php index d28aaa7..e89fe57 100644 --- a/Classes/Exception/NotImplementedException.php +++ b/Classes/Exception/NotImplementedException.php @@ -10,4 +10,4 @@ class NotImplementedException extends BynderException { -} \ No newline at end of file +} diff --git a/Classes/Hook/DataHandlerHook.php b/Classes/Hook/DataHandlerHook.php index f35e21c..25819bd 100644 --- a/Classes/Hook/DataHandlerHook.php +++ b/Classes/Hook/DataHandlerHook.php @@ -134,4 +134,4 @@ protected function getUsageReference(string $table, int $uid): string return $table; } } -} \ No newline at end of file +} diff --git a/Classes/Resource/BynderDriver.php b/Classes/Resource/BynderDriver.php index 17badf2..76f0398 100644 --- a/Classes/Resource/BynderDriver.php +++ b/Classes/Resource/BynderDriver.php @@ -12,7 +12,6 @@ use BeechIt\Bynder\Resource\Helper\BynderHelper; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; use TYPO3\CMS\Core\Resource\Exception; -use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Utility\DebuggerUtility; diff --git a/Classes/Resource/Helper/BynderHelper.php b/Classes/Resource/Helper/BynderHelper.php index 8f8358f..a7ed0d0 100644 --- a/Classes/Resource/Helper/BynderHelper.php +++ b/Classes/Resource/Helper/BynderHelper.php @@ -2,6 +2,7 @@ namespace BeechIt\Bynder\Resource\Helper; +use BeechIt\Bynder\Exception\NotImplementedException; use BeechIt\Bynder\Resource\BynderDriver; use BeechIt\Bynder\Traits\BynderService; use BeechIt\Bynder\Traits\BynderStorage; @@ -10,6 +11,7 @@ use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Utility\DebuggerUtility; @@ -17,25 +19,50 @@ * Class BynderVideoHelper * @package BeechIt\Bynder\Resource\Helper */ -class BynderHelper extends \TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\AbstractOnlineMediaHelper +class BynderHelper extends \TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\AbstractOnlineMediaHelper implements SingletonInterface { use BynderService; use BynderStorage; + /** + * Used derivatives from Bynder API + */ const DERIVATIVES_WEB_IMAGE = 'webimage'; const DERIVATIVES_MINI = 'mini'; const DERIVATIVES_THUMBNAIL = 'thul'; + /** + * Used derivative + * @var string + */ + protected $derivative = self::DERIVATIVES_THUMBNAIL; + + /** + * @param string $derivative + * @return $this + * @throws NotImplementedException + */ + public function setDerivative(string $derivative) + { + if (in_array($derivative, [self::DERIVATIVES_THUMBNAIL, self::DERIVATIVES_MINI, self::DERIVATIVES_WEB_IMAGE])) { + $this->derivative = $derivative; + } else { + throw new NotImplementedException('Invalid derivative retrieved', 1531746896); + } + return $this; + } + /** * Try to transform given URL to a File * * @param string $url * @param Folder $targetFolder * @return File + * @throws NotImplementedException */ public function transformUrlToFile($url, Folder $targetFolder): File { - DebuggerUtility::var_dump(__METHOD__); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1531749725); } /** @@ -43,7 +70,7 @@ public function transformUrlToFile($url, Folder $targetFolder): File * @return array * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ - public function getBynderMediaInfo($identifier): array + public function getBynderMediaInfo(string $identifier): array { return $this->getBynderService()->getMediaInfo($identifier); } @@ -65,12 +92,12 @@ public function getPublicUrl(File $file, $relativeToCurrentScript = false): stri case 'bynder/' . BynderDriver::ASSET_TYPE_IMAGE: try { $mediaInfo = $this->getBynderMediaInfo($identifier); - return $mediaInfo['thumbnails']['webimage']; + return $mediaInfo['thumbnails'][$this->derivative]; } catch (\Exception $e) { return ConfigurationUtility::getUnavailableImage($relativeToCurrentScript); } break; - // TODO + // @TODO WHEN IMPLEMENTED BY BYNDER COMPACT VIEW // case 'bynder' . BynderDriver::ASSET_TYPE_DOCUMENT: } } @@ -90,7 +117,7 @@ public function getPublicUrl(File $file, $relativeToCurrentScript = false): stri public function getPreviewImage(File $file): string { try { - return $this->downloadThumbnailToTemporaryFilePath($file); + return $this->downloadThumbnailToTemporaryFilePath($file, $this->derivative); } catch (\Exception $e) { return ''; } @@ -221,26 +248,29 @@ protected function getSpecificFileInformation($mediaInfo, $property) * Save a file to a temporary path and returns that path. * * @param File $file + * @param string $type * @return string|null The temporary path * @throws FileDoesNotExistException * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ - protected function downloadThumbnailToTemporaryFilePath(File $file, $type = 'thul'): string + protected function downloadThumbnailToTemporaryFilePath(File $file, $type = self::DERIVATIVES_THUMBNAIL): string { try { $mediaInfo = $this->getBynderService()->getMediaInfo($this->getBynderFileIdentifier($file)); - $url = $mediaInfo['thumbnails']['thul']; - $temporaryPath = $this->getTemporaryPathForFile($url); - if (!is_file($temporaryPath)) { - $report = []; - $data = GeneralUtility::getUrl($url, 0, false, $report); - if (!empty($data)) { - $result = GeneralUtility::writeFile($temporaryPath, $data); - if ($result === false) { - throw new \RuntimeException( - 'Copying file "' . $file->getIdentifier() . '" to temporary path "' . $temporaryPath . '" failed.', - 1519208427 - ); + $url = $mediaInfo['thumbnails'][$type]; + if (!empty($url)) { + $temporaryPath = $this->getTemporaryPathForFile($url); + if (!is_file($temporaryPath)) { + $report = []; + $data = GeneralUtility::getUrl($url, 0, false, $report); + if (!empty($data)) { + $result = GeneralUtility::writeFile($temporaryPath, $data); + if ($result === false) { + throw new \RuntimeException( + 'Copying file "' . $file->getIdentifier() . '" to temporary path "' . $temporaryPath . '" failed.', + 1519208427 + ); + } } } } @@ -255,6 +285,27 @@ protected function downloadThumbnailToTemporaryFilePath(File $file, $type = 'thu return $temporaryPath ?? null; } + /** + * @param File $file + * @param string $width + * @param string $height + * @return string + * @throws \BeechIt\Bynder\Exception\InvalidExtensionConfigurationException + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + public function getOnTheFlyPublicUrl(File $file, $width, $height): string + { + $mediaInfo = $this->getBynderMediaInfo($file->getIdentifier()); + if (filter_var($mediaInfo['isPublic'], FILTER_VALIDATE_BOOLEAN) === true) { + return ConfigurationUtility::getOnTheFlyBaseUrl() . $file->getIdentifier() . '?' . http_build_query([ + 'w' => (int)$width, + 'h' => (int)$height, + 'crop' => (bool)strpos($width . $height, 'c') + ]); + } + return ''; + } + /** * Returns a temporary path for a given file, including the file extension. * diff --git a/Classes/Resource/Rendering/BynderImageRenderer.php b/Classes/Resource/Rendering/BynderImageRenderer.php index 693cfa0..439bd6f 100644 --- a/Classes/Resource/Rendering/BynderImageRenderer.php +++ b/Classes/Resource/Rendering/BynderImageRenderer.php @@ -2,19 +2,26 @@ namespace BeechIt\Bynder\Resource\Rendering; -use BeechIt\Bynder\Traits\BynderHelper; +use BeechIt\Bynder\Exception\BynderException; +use BeechIt\Bynder\Resource\Helper\BynderHelper; +use BeechIt\Bynder\Service\TagBuilderService; +use BeechIt\Bynder\Traits\BynderHelper as BynderHelperTrait; +use BeechIt\Bynder\Utility\ConfigurationUtility; +use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; +use TYPO3\CMS\Core\Resource\FileReference; use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Extbase\Service\ImageService; /** * Class BynderRenderer */ class BynderImageRenderer implements FileRendererInterface { - - use BynderHelper; + use BynderHelperTrait; /** * Returns the priority of the renderer @@ -29,7 +36,7 @@ class BynderImageRenderer implements FileRendererInterface */ public function getPriority(): int { - return 10; + return 15; } /** @@ -52,82 +59,120 @@ public function canRender(FileInterface $file): bool * @param array $options * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() * @return string + * @throws \BeechIt\Bynder\Exception\InvalidExtensionConfigurationException + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + * @throws \TYPO3\CMS\Extbase\Object\InvalidObjectException */ public function render(FileInterface $file, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string { - if (!($file instanceof File) && is_callable([$file, 'getOriginalFile'])) { - $originalFile = $file->getOriginalFile(); - } else { - $originalFile = $file; - } - return $this->renderImageTag( $file, - $this->getBynderHelper($originalFile)->getPublicUrl($originalFile), - $width, $height + $this->getProcessedPublicImageLocation($file, $width, $height, $options), + $width, + $height, + $options ); } /** - * Render image for given File(Reference) HTML output - * * @param FileInterface $file - * @param string $source - * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c - * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c - * @param array $options - * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @param $width + * @param $height + * @param $info * @return string + * @throws \BeechIt\Bynder\Exception\InvalidExtensionConfigurationException + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + * @throws \TYPO3\CMS\Extbase\Object\InvalidObjectException */ - public function renderImageTag($file, $source, $width, $height): string + protected function getProcessedPublicImageLocation(FileInterface $file, $width, $height, $info) { - $tag = GeneralUtility::makeInstance(\TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder::class); - $tag->setTagName('img'); - - $tag->addAttribute('src', $source); - $tag->addAttribute('width', $width); - $tag->addAttribute('height', $height); + if (!($file instanceof File) && is_callable([$file, 'getOriginalFile'])) { + $originalFile = $file->getOriginalFile(); + } else { + $originalFile = $file; + } - $alt = $file->getProperty('alternative'); - $title = $file->getProperty('title'); + list($width, $height) = $this->getCalculatedSizes($originalFile->getProperty('width'), $originalFile->getProperty('height'), $width, $height); - // The alt-attribute is mandatory to have valid html-code, therefore add it even if it is empty - if ($alt) { - $tag->addAttribute('alt', $alt); + try { + if (ConfigurationUtility::isOnTheFlyConfigured() && $url = $this->getBynderHelper($originalFile)->getOnTheFlyPublicUrl($originalFile, $width, $height)) { + return $url; + } else { + return $this->processPublicImageLocationLocally($originalFile, $width, $height, $info); + } + } catch (BynderException $e) { + // Never throw on own exceptions, just return unavailable image + return ConfigurationUtility::getUnavailableImage(); } - if ($title) { - $tag->addAttribute('title', $title); + } + + /** + * @param File $file + * @param integer $width + * @param integer $height + * @param array $info + * @return string + * @throws \TYPO3\CMS\Extbase\Object\InvalidObjectException + * @throws \BeechIt\Bynder\Exception\NotImplementedException + */ + protected function processPublicImageLocationLocally(File $file, $width, $height, $info) + { + // When width and height are set and non of them have a 'm' suffix we don't keep existing ratio + $derivative = BynderHelper::DERIVATIVES_WEB_IMAGE; + if ($width && $height) { + if ($width <= 80) { + $derivative = BynderHelper::DERIVATIVES_MINI; + } elseif ($width <= 250) { + $derivative = BynderHelper::DERIVATIVES_THUMBNAIL; + } } + $bynderHelper = $this->getBynderHelper($file); + // Set required derivative + $bynderHelper->setDerivative($derivative); - return $tag->render(); + /** + * Now do the same logic as MediaViewHelper. + */ + $cropVariant = $info['cropVariant'] ?: 'default'; + $cropString = $file instanceof FileReference ? $file->getProperty('crop') : ''; + $cropVariantCollection = CropVariantCollection::create((string)$cropString); + $cropArea = $cropVariantCollection->getCropArea($cropVariant); + $processingInstructions = [ + 'width' => $width, + 'height' => $height, + 'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($file), + ]; + $imageService = $this->getImageService(); + $processedImage = $imageService->applyProcessingInstructions($file, $processingInstructions); + $imageUri = $imageService->getImageUri($processedImage); + /** + * The logic from MediaViewHelper is ended here. + */ + + // Restore derivative to thumbnail + $bynderHelper->setDerivative(BynderHelper::DERIVATIVES_THUMBNAIL); + return $imageUri; } /** - * Calculate the best suitable/available dimensions for the requested file configuration - * - * @param array $configuration - * @param int $orgWidth - * @param int $orgHeight - * @param string $otfBaseUrl - * @param array $derivatives + * @param integer $originalWidth + * @param integer $originalHeight + * @param integer|string $width + * @param integer|string $height * @return array */ - protected function calculateImagelInfo(array $configuration, $width, $height, string $otfBaseUrl, array $derivatives): array + protected function getCalculatedSizes($originalWidth, $originalHeight, $width, $height) { - $width = $configuration['width'] ?? $configuration['maxWidth'] ?? 0; - $height = $configuration['height'] ?? $configuration['maxHeight'] ?? 0; - $keepRatio = true; $crop = false; - $otf = $otfBaseUrl !== ''; // When width and height are set and non of them have a 'm' suffix we don't keep existing ratio - if ($width && $height && strpos($width . $height, 'm') < 0) { + if ($width && $height && strpos($width . $height, 'm') === false) { $keepRatio = false; } // When width and height are set and one of then have a 'c' suffix we don't keep existing ratio and allow cropping - if ($width && $height && strpos($width . $height, 'c') >= 0) { + if ($width && $height && strpos($width . $height, 'c') !== false) { $keepRatio = false; $crop = true; } @@ -135,66 +180,62 @@ protected function calculateImagelInfo(array $configuration, $width, $height, st $width = (int)$width; $height = (int)$height; - if (!$keepRatio && $width > $orgWidth) { - $height = $this->calculateRelativeDimension($width, $height, $orgWidth); - $width = $orgWidth; - } elseif (!$keepRatio && $height > $orgHeight) { - $width = $this->calculateRelativeDimension($height, $width, $orgHeight); - $height = $orgHeight; - } elseif ($keepRatio && $width > $orgWidth) { - $height = $orgWidth / $width * $height; - } elseif ($keepRatio && $height > $orgHeight) { - $height = $orgHeight / $height * $width; + if (!$keepRatio && $width > $originalWidth) { + $height = $this->calculateRelativeDimension($width, $height, $originalWidth); + $width = $originalWidth; + } elseif (!$keepRatio && $height > $originalHeight) { + $width = $this->calculateRelativeDimension($height, $width, $originalHeight); + $height = $originalHeight; + } elseif ($keepRatio && $width > $originalWidth) { + $height = $originalWidth / $width * $height; + } elseif ($keepRatio && $height > $originalHeight) { + $height = $originalHeight / $height * $width; } elseif ($width === 0 && $height > 0) { - $height = $this->calculateRelativeDimension($orgWidth, $orgHeight, $width); + $height = $this->calculateRelativeDimension($originalWidth, $originalHeight, $width); } elseif ($width === 0 && $height > 0) { - $width = $this->calculateRelativeDimension($orgHeight, $orgWidth, $height); + $width = $this->calculateRelativeDimension($originalHeight, $originalWidth, $height); } - - if ($otf) { - return [ - 'type' => 'otf', - 'width' => $width ?: $orgWidth, - 'height' => $height ?: $orgHeight, - 'url' => $otfBaseUrl . '?' . http_build_query([ - 'w' => $width ?: '', - 'h' => $height ?: '', - 'crop' => $crop ? 'true' : 'false' - ]), - ]; + if ($crop === true) { + return [$width . 'c', $height . 'c']; + } else { + return [$width, $height]; } + } - $default = [ - 'type' => 'webimage', - 'width' => $width, - 'height' => $height, - 'url' => $derivatives['webimage'], - ]; - if ($height === 0 && $width === 0) { - return $default; + /** + * Render image for given File(Reference) HTML output + * + * @param FileInterface $file + * @param string $source + * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c + * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c + * @param array $options + * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() + * @return string + */ + public function renderImageTag($file, $source, $width, $height, $options, $usedPathsRelativeToCurrentScript = false): string + { + $tagBuilderService = $this->getTagBuilderService(); + $tag = $tagBuilderService->getTagBuilder('img'); + $tagBuilderService->initializeAbstractTagBasedAttributes($tag, $options); + + $tag->addAttribute('src', ($usedPathsRelativeToCurrentScript ? $source : $source)); + $tag->addAttribute('width', $width); + $tag->addAttribute('height', $height); + + // The alt-attribute is mandatory to have valid html-code, therefore add it even if it is empty + if ($tag->hasAttribute('alt') === false) { + $tag->addAttribute('alt', $file->getProperty('alternative')); } - if ($width <= 80) { - return [ - 'type' => 'mini', - 'width' => $width, - 'height' => $width, // derivative/image is square - 'url' => $derivatives['mini'], - ]; - } elseif ($width <= 250) { - return [ - 'type' => 'thul', - 'width' => $width, - 'height' => $height, - 'url' => $derivatives['thul'], - ]; + if ($tag->hasAttribute('title') === false) { + $tag->addAttribute('title', $file->getProperty('title')); } - return $default; + return $tag->render(); } /** * Calculate relative dimension - * * For instance you have the original width, height and new width. * And want to calculate the new height with the same ratio as the original dimensions * @@ -205,12 +246,27 @@ protected function calculateImagelInfo(array $configuration, $width, $height, st */ protected function calculateRelativeDimension(int $orgA, int $orgB, int $newA): int { - if ($newA === 0) { - return $orgB; - } - - return (int)($orgB / ($orgA / $newA)); + return ($newA === 0) ? $orgB : (int)($orgB / ($orgA / $newA)); } + /** + * Return an instance of ImageService + * + * @return ImageService + */ + protected function getImageService() + { + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + return $objectManager->get(ImageService::class); + } + /** + * Return an instance of TagBuilderService + * + * @return TagBuilderService + */ + protected function getTagBuilderService() + { + return GeneralUtility::makeInstance(TagBuilderService::class); + } } diff --git a/Classes/Resource/Rendering/BynderVideoRenderer.php b/Classes/Resource/Rendering/BynderVideoRenderer.php index 6cc731e..5944dad 100644 --- a/Classes/Resource/Rendering/BynderVideoRenderer.php +++ b/Classes/Resource/Rendering/BynderVideoRenderer.php @@ -4,17 +4,18 @@ use BeechIt\Bynder\Traits\BynderHelper; use BeechIt\Bynder\Utility\ConfigurationUtility; +use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface; -use TYPO3\CMS\Extbase\Utility\DebuggerUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Utility\LocalizationUtility; /** * Class BynderRenderer */ class BynderVideoRenderer implements FileRendererInterface { - use BynderHelper; /** @@ -30,7 +31,7 @@ class BynderVideoRenderer implements FileRendererInterface */ public function getPriority(): int { - return 50; + return 15; } /** @@ -47,62 +48,72 @@ public function canRender(FileInterface $file): bool /** * Render for given File(Reference) HTML output * - * @param FileInterface $file + * @param FileInterface $source * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c * @param array $options * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl() * @return string + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + * @throws \TYPO3\CMS\Extbase\Object\InvalidObjectException */ - public function render(FileInterface $file, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string + public function render(FileInterface $source, $width, $height, array $options = [], $usedPathsRelativeToCurrentScript = false): string { - return $this->renderVideo($file, $width, $height, $options, $usedPathsRelativeToCurrentScript); + if ($source instanceof File) { + $file = $source; + } elseif (is_callable([$source, 'getOriginalFile'])) { + $file = $source->getOriginalFile(); + } + + return $this->getEmbedCode($file); } /** - * Render video for given File(Reference) HTML output + * Include Video.JS javascript libraries and configuration * - * @param FileInterface $source - * @param int|string $width in video, always 0 - * @param int|string $height in video, always 0 - * @param array $options - * @param bool $usedPathsRelativeToCurrentScript + * @return void + */ + protected function addVideoScripts() + { + $this->getPageRenderer()->addCssFile('EXT:bynder/Resources/Public/Styles/video-js.min.css'); + $this->getPageRenderer()->addJsInlineCode('video-js', 'window.VIDEOJS_NO_DYNAMIC_STYLE = true;'); + $this->getPageRenderer()->addJsFooterFile('EXT:bynder/Resources/Public/JavaScript/video-js.min.js'); + } + + /** + * Generate Video.JS embed code + * + * @param File $file * @return string - * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException - * @throws \TYPO3\CMS\Extbase\Object\InvalidObjectException */ - public function renderVideo($source, $width, $height, $options, $usedPathsRelativeToCurrentScript): string + protected function getEmbedCode(File $file): string { $sources = []; - try { - if ($source instanceof File) { - $file = $source; - } elseif (is_callable([$source, 'getOriginalFile'])) { - $file = $source->getOriginalFile(); - } - $mediaInfo = $this->getBynderHelper($file)->getBynderMediaInfo($file->getIdentifier()); foreach ((array)$mediaInfo['videoPreviewURLs'] as $url) { - switch (pathinfo($url, PATHINFO_EXTENSION)) { - case 'webm': - $sources[$url] = ''; - break; - case 'mp4': - $sources[$url] = ''; - break; - } + $sources[] = ''; } - }catch (\Exception $e){ + } catch (\Exception $e) { // Catch all exceptions as these should never crash the frontend website } - - if (empty($sources)) { - return ''; + if (!empty($sources)) { + $this->addVideoScripts(); + return ''; } else { - return ''; + return ''; } } - + /** + * @return PageRenderer + */ + protected function getPageRenderer(): PageRenderer + { + return GeneralUtility::makeInstance(PageRenderer::class); + } } diff --git a/Classes/Service/BynderService.php b/Classes/Service/BynderService.php index 7ebe57f..d1bca72 100644 --- a/Classes/Service/BynderService.php +++ b/Classes/Service/BynderService.php @@ -11,7 +11,6 @@ use Bynder\Api\BynderApiFactory; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; -use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; diff --git a/Classes/Service/TagBuilderService.php b/Classes/Service/TagBuilderService.php new file mode 100644 index 0000000..13671f7 --- /dev/null +++ b/Classes/Service/TagBuilderService.php @@ -0,0 +1,63 @@ +addAttributes($arguments['additionalAttributes']); + } + + if ($arguments['data'] && is_array($arguments['data'])) { + foreach ($arguments['data'] as $dataAttributeKey => $dataAttributeValue) { + $tagBuilder->addAttribute('data-' . $dataAttributeKey, $dataAttributeValue); + } + } + $this->initializeUniversalTagAttributes($tagBuilder, $arguments); + return $tagBuilder; + } + + /** + * @param TagBuilder $tagBuilder + * @param array $arguments + * @param array $universalTagAttributes + * @return TagBuilder + */ + public function initializeUniversalTagAttributes( + TagBuilder $tagBuilder, + array $arguments, + array $universalTagAttributes = ['class', 'dir', 'id', 'lang', 'style', 'title', 'accesskey', 'tabindex', 'onclick'] + ): TagBuilder { + foreach ($universalTagAttributes as $attributeName) { + if ($arguments[$attributeName] && $arguments[$attributeName] !== '') { + $tagBuilder->addAttribute($attributeName, $arguments[$attributeName]); + } + } + return $tagBuilder; + } +} diff --git a/Classes/Slot/InstallSlot.php b/Classes/Slot/InstallSlot.php index a2b8c14..6896bd1 100644 --- a/Classes/Slot/InstallSlot.php +++ b/Classes/Slot/InstallSlot.php @@ -73,4 +73,4 @@ public function createBynderFileStorage(string $extensionKey, InstallUtility $in ->getConnectionForTable('sys_filemounts'); $dbConnection->insert('sys_filemounts', $field_values); } -} \ No newline at end of file +} diff --git a/Classes/Utility/ConfigurationUtility.php b/Classes/Utility/ConfigurationUtility.php index 4112c09..1d66f33 100644 --- a/Classes/Utility/ConfigurationUtility.php +++ b/Classes/Utility/ConfigurationUtility.php @@ -21,6 +21,7 @@ class ConfigurationUtility /** * @param string $allowedElements * @return array + * @throws InvalidExtensionConfigurationException */ public static function getAssetTypesByAllowedElements($allowedElements): array { @@ -50,6 +51,7 @@ public static function getAssetTypesByAllowedElements($allowedElements): array /** * @param boolean $relativeToCurrentScript * @return string + * @throws InvalidExtensionConfigurationException */ public static function getUnavailableImage($relativeToCurrentScript = false): string { @@ -86,6 +88,24 @@ public static function getApiBaseUrl(): string return static::cleanUrl((self::getExtensionConfiguration())['url']); } + /** + * @return string + * @throws InvalidExtensionConfigurationException + */ + public static function getOnTheFlyBaseUrl(): string + { + return static::cleanUrl((self::getExtensionConfiguration())['otf_base_url']); + } + + /** + * @return boolean + * @throws InvalidExtensionConfigurationException + */ + public static function isOnTheFlyConfigured(): bool + { + return !empty((self::getExtensionConfiguration())['otf_base_url']); + } + /** * @return array * @throws InvalidExtensionConfigurationException diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php index 1c5e0ac..964178a 100644 --- a/Configuration/Backend/AjaxRoutes.php +++ b/Configuration/Backend/AjaxRoutes.php @@ -9,4 +9,4 @@ 'path' => '/bynder/compactview/getfiles', 'target' => \BeechIt\Bynder\Controller\CompactViewController::class . '::getFilesAction' ], -]; \ No newline at end of file +]; diff --git a/Configuration/Backend/Routes.php b/Configuration/Backend/Routes.php index d47f03d..9067eb8 100644 --- a/Configuration/Backend/Routes.php +++ b/Configuration/Backend/Routes.php @@ -9,4 +9,4 @@ 'path' => '/bynder/compactview', 'target' => \BeechIt\Bynder\Controller\CompactViewController::class . '::indexAction' ], -]; \ No newline at end of file +]; diff --git a/Resources/Private/Language/locallang_be.xlf b/Resources/Private/Language/locallang_be.xlf index 049ca76..99ae9fa 100644 --- a/Resources/Private/Language/locallang_be.xlf +++ b/Resources/Private/Language/locallang_be.xlf @@ -12,6 +12,10 @@ Please make sure you have access to the Bynder file mount. This is needed to use the Bynder file picker + + supports HTML5 video]]> + - \ No newline at end of file + diff --git a/Resources/Public/JavaScript/video-js.min.js b/Resources/Public/JavaScript/video-js.min.js new file mode 100644 index 0000000..06f5f44 --- /dev/null +++ b/Resources/Public/JavaScript/video-js.min.js @@ -0,0 +1,12 @@ +/** + * @license + * Video.js 7.0.5 + * Copyright Brightcove, Inc. + * Available under Apache License Version 2.0 + * + * + * Includes vtt.js + * Available under Apache License Version 2.0 + * + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.videojs=t()}(this,function(){var d="7.0.5",e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e,t){return e(t={exports:{}},t.exports),t.exports}var i,g="undefined"!=typeof window?window:"undefined"!=typeof e?e:"undefined"!=typeof self?self:{},r={},n=Object.freeze({default:r}),a=n&&r||n,s="undefined"!=typeof e?e:"undefined"!=typeof window?window:{};"undefined"!=typeof document?i=document:(i=s["__GLOBAL_DOCUMENT_CACHE@4"])||(i=s["__GLOBAL_DOCUMENT_CACHE@4"]=a);var p=i,o=void 0,u="info",l=[],c=function(e,t){var i=o.levels[u],r=new RegExp("^("+i+")$");if("log"!==e&&t.unshift(e.toUpperCase()+":"),l&&l.push([].concat(t)),t.unshift("VIDEOJS:"),g.console){var n=g.console[e];n||"debug"!==e||(n=g.console.info||g.console.log),n&&i&&r.test(e)&&n[Array.isArray(t)?"apply":"call"](g.console,t)}};(o=function(){for(var e=arguments.length,t=Array(e),i=0;i',i=n.firstChild,n.setAttribute("style","display:none; position:absolute;"),p.body.appendChild(n));for(var a={},s=0;sx',e=t.firstChild.href}return e},ei=function(e){if("string"==typeof e){var t=/^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i.exec(e);if(t)return t.pop().toLowerCase()}return""},ti=function(e){var t=g.location,i=Jt(e);return(":"===i.protocol?t.protocol:i.protocol)+i.host!==t.protocol+t.host},ii=Object.freeze({parseUrl:Jt,getAbsoluteURL:Zt,getFileExtension:ei,isCrossOrigin:ti}),ri=function(e){var t=ni.call(e);return"[object Function]"===t||"function"==typeof e&&"[object RegExp]"!==t||"undefined"!=typeof window&&(e===window.setTimeout||e===window.alert||e===window.confirm||e===window.prompt)},ni=Object.prototype.toString;var ai=Object.freeze({default:ri,__moduleExports:ri}),si=t(function(e,t){(t=e.exports=function(e){return e.replace(/^\s*|\s*$/g,"")}).left=function(e){return e.replace(/^\s*/,"")},t.right=function(e){return e.replace(/\s*$/,"")}}),oi=si.left,ui=si.right,li=Object.freeze({default:si,__moduleExports:si,left:oi,right:ui}),ci=ai&&ri||ai,hi=function(e,t,i){if(!ci(t))throw new TypeError("iterator must be a function");arguments.length<3&&(i=this);"[object Array]"===di.call(e)?function(e,t,i){for(var r=0,n=e.length;r=e?t.push(n):n.startTime===n.endTime&&n.startTime<=e&&n.startTime+.5>=e&&t.push(n)}if(o=!1,t.length!==this.activeCues_.length)o=!0;else for(var a=0;a","‎":"‎","‏":"‏"," ":" "},Gi={c:"span",i:"i",b:"b",u:"u",ruby:"ruby",rt:"rt",v:"span",lang:"span"},Xi={v:"title",lang:"lang"},Yi={rt:"ruby"};function $i(a,i){function e(){if(!i)return null;var e,t=i.match(/^([^<]*)(<[^>]*>?)?/);return e=t[1]?t[1]:t[2],i=i.substr(e.length),e}function t(e){return zi[e]}function r(e){for(;f=e.match(/&(amp|lt|gt|lrm|rlm|nbsp);/);)e=e.replace(f[0],t);return e}function n(e,t){var i=Gi[e];if(!i)return null;var r=a.document.createElement(i);r.localName=i;var n=Xi[e];return n&&t&&(r[n]=t.trim()),r}for(var s,o,u,l=a.document.createElement("div"),c=l,h=[];null!==(s=e());)if("<"!==s[0])c.appendChild(a.document.createTextNode(r(s)));else{if("/"===s[1]){h.length&&h[h.length-1]===s.substr(2).replace(">","")&&(h.pop(),c=c.parentNode);continue}var d,p=Hi(s.substr(1,s.length-2));if(p){d=a.document.createProcessingInstruction("timestamp",p),c.appendChild(d);continue}var f=s.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);if(!f)continue;if(!(d=n(f[1],f[3])))continue;if(o=c,Yi[(u=d).localName]&&Yi[u.localName]!==o.localName)continue;f[2]&&(d.className=f[2].substr(1).replace("."," ")),h.push(f[1]),c.appendChild(d),c=d}return l}var Ki=[[1470,1470],[1472,1472],[1475,1475],[1478,1478],[1488,1514],[1520,1524],[1544,1544],[1547,1547],[1549,1549],[1563,1563],[1566,1610],[1645,1647],[1649,1749],[1765,1766],[1774,1775],[1786,1805],[1807,1808],[1810,1839],[1869,1957],[1969,1969],[1984,2026],[2036,2037],[2042,2042],[2048,2069],[2074,2074],[2084,2084],[2088,2088],[2096,2110],[2112,2136],[2142,2142],[2208,2208],[2210,2220],[8207,8207],[64285,64285],[64287,64296],[64298,64310],[64312,64316],[64318,64318],[64320,64321],[64323,64324],[64326,64449],[64467,64829],[64848,64911],[64914,64967],[65008,65020],[65136,65140],[65142,65276],[67584,67589],[67592,67592],[67594,67637],[67639,67640],[67644,67644],[67647,67669],[67671,67679],[67840,67867],[67872,67897],[67903,67903],[67968,68023],[68030,68031],[68096,68096],[68112,68115],[68117,68119],[68121,68147],[68160,68167],[68176,68184],[68192,68223],[68352,68405],[68416,68437],[68440,68466],[68472,68479],[68608,68680],[126464,126467],[126469,126495],[126497,126498],[126500,126500],[126503,126503],[126505,126514],[126516,126519],[126521,126521],[126523,126523],[126530,126530],[126535,126535],[126537,126537],[126539,126539],[126541,126543],[126545,126546],[126548,126548],[126551,126551],[126553,126553],[126555,126555],[126557,126557],[126559,126559],[126561,126562],[126564,126564],[126567,126570],[126572,126578],[126580,126583],[126585,126588],[126590,126590],[126592,126601],[126603,126619],[126625,126627],[126629,126633],[126635,126651],[1114109,1114109]];function Qi(e){for(var t=0;t=i[0]&&e<=i[1])return!0}return!1}function Ji(){}function Zi(e,t,i){Ji.call(this),this.cue=t,this.cueDiv=$i(e,t.text);var r={color:"rgba(255, 255, 255, 1)",backgroundColor:"rgba(0, 0, 0, 0.8)",position:"relative",left:0,right:0,top:0,bottom:0,display:"inline",writingMode:""===t.vertical?"horizontal-tb":"lr"===t.vertical?"vertical-lr":"vertical-rl",unicodeBidi:"plaintext"};this.applyStyles(r,this.cueDiv),this.div=e.document.createElement("div"),r={direction:function(e){var t=[],i="";if(!e||!e.childNodes)return"ltr";function n(e,t){for(var i=t.childNodes.length-1;0<=i;i--)e.push(t.childNodes[i])}function a(e){if(!e||!e.length)return null;var t=e.pop(),i=t.textContent||t.innerText;if(i){var r=i.match(/^.*(\n|\r)/);return r?r[e.length=0]:i}return"ruby"===t.tagName?a(e):t.childNodes?(n(e,t),a(e)):void 0}for(n(t,e);i=a(t);)for(var r=0;rh&&(c=c<0?-1:1,c*=Math.ceil(h/l)*l),n<0&&(c+=""===r.vertical?o.height:o.width,a=a.reverse()),i.move(d,c)}else{var p=i.lineHeight/o.height*100;switch(r.lineAlign){case"middle":n-=p/2;break;case"end":n-=p}switch(r.vertical){case"":t.applyStyles({top:t.formatStyle(n,"%")});break;case"rl":t.applyStyles({left:t.formatStyle(n,"%")});break;case"lr":t.applyStyles({right:t.formatStyle(n,"%")})}a=["+y","-x","+x","-y"],i=new er(t)}var f=function(e,t){for(var i,r=new er(e),n=1,a=0;ae.left&&this.tope.top},er.prototype.overlapsAny=function(e){for(var t=0;t=e.top&&this.bottom<=e.bottom&&this.left>=e.left&&this.right<=e.right},er.prototype.overlapsOppositeAxis=function(e,t){switch(t){case"+x":return this.lefte.right;case"+y":return this.tope.bottom}},er.prototype.intersectPercentage=function(e){return Math.max(0,Math.min(this.right,e.right)-Math.max(this.left,e.left))*Math.max(0,Math.min(this.bottom,e.bottom)-Math.max(this.top,e.top))/(this.height*this.width)},er.prototype.toCSSCompatValues=function(e){return{top:this.top-e.top,bottom:e.bottom-this.bottom,left:this.left-e.left,right:e.right-this.right,height:this.height,width:this.width}},er.getSimpleBoxPosition=function(e){var t=e.div?e.div.offsetHeight:e.tagName?e.offsetHeight:0,i=e.div?e.div.offsetWidth:e.tagName?e.offsetWidth:0,r=e.div?e.div.offsetTop:e.tagName?e.offsetTop:0;return{left:(e=e.div?e.div.getBoundingClientRect():e.tagName?e.getBoundingClientRect():e).left,right:e.right,top:e.top||r,height:e.height||t,bottom:e.bottom||r+(e.height||t),width:e.width||i}},ir.StringDecoder=function(){return{decode:function(e){if(!e)return"";if("string"!=typeof e)throw new Error("Error - expected string data.");return decodeURIComponent(encodeURIComponent(e))}}},ir.convertCueToDOMTree=function(e,t){return e&&t?$i(e,t):null};ir.processCues=function(r,n,e){if(!r||!n||!e)return null;for(;e.firstChild;)e.removeChild(e.firstChild);var a=r.document.createElement("div");if(a.style.position="absolute",a.style.left="0",a.style.right="0",a.style.top="0",a.style.bottom="0",a.style.margin="1.5%",e.appendChild(a),function(e){for(var t=0;t