diff --git a/Classes/Backend/ImageManipulationElement.php b/Classes/Backend/ImageManipulationElement.php index e584202..e6d7be2 100644 --- a/Classes/Backend/ImageManipulationElement.php +++ b/Classes/Backend/ImageManipulationElement.php @@ -7,16 +7,15 @@ * Date: 21-2-18 * All code (c) Beech.it all rights reserved */ + use BeechIt\Bynder\Resource\BynderDriver; /** - * Class ImageManipulationElement - * * Override of ImageManipulationElement to hide cropper for Bynder files */ class ImageManipulationElement extends \TYPO3\CMS\Backend\Form\Element\ImageManipulationElement { - public function render() + public function render(): array { $resultArray = $this->initializeResultArray(); @@ -25,6 +24,7 @@ public function render() // Early return in case of Bynder file (or no file) return $resultArray; } + return parent::render(); } } diff --git a/Classes/Backend/InlineControlContainer.php b/Classes/Backend/InlineControlContainer.php index d3b0629..65b4ee8 100644 --- a/Classes/Backend/InlineControlContainer.php +++ b/Classes/Backend/InlineControlContainer.php @@ -11,22 +11,18 @@ use BeechIt\Bynder\Resource\BynderDriver; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Imaging\Icon; -use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Utility\GeneralUtility; /** - * Class InlineControlContainer - * * Override core InlineControlContainer to inject Bynder button */ class InlineControlContainer extends \TYPO3\CMS\Backend\Form\Container\InlineControlContainer { - /** * @param array $inlineConfiguration * @return string */ - protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration) + protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration): string { $selector = parent::renderPossibleRecordsSelectorTypeGroupDB($inlineConfiguration); @@ -35,7 +31,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 { @@ -101,7 +97,7 @@ protected function renderBynderButton(array $inlineConfiguration): string */ protected function bynderStorageAvailable(): bool { - /** @var ResourceStorage $fileStorage */ + /** @var \TYPO3\CMS\Core\Resource\ResourceStorage $fileStorage */ foreach ($this->getBackendUserAuthentication()->getFileStorages() as $fileStorage) { if ($fileStorage->getDriverType() === BynderDriver::KEY) { return true; diff --git a/Classes/Controller/CompactViewController.php b/Classes/Controller/CompactViewController.php index ce9f5c6..de8d00a 100644 --- a/Classes/Controller/CompactViewController.php +++ b/Classes/Controller/CompactViewController.php @@ -13,18 +13,25 @@ use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\JsonResponse; +use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Index\Indexer; use TYPO3\CMS\Core\Resource\ResourceStorageInterface; use TYPO3Fluid\Fluid\View\ViewInterface; class CompactViewController { - /** @var \TYPO3Fluid\Fluid\View\ViewInterface */ - protected $view; + /** @var \TYPO3\CMS\Core\Resource\ResourceStorageInterface */ + private $bynderStorage; /** @var \BeechIt\Bynder\Service\BynderService */ protected $bynderService; + /** @var \TYPO3\CMS\Core\Resource\Index\Indexer */ + private $indexer; + + /** @var \TYPO3Fluid\Fluid\View\ViewInterface */ + protected $view; + public function __construct( ResourceStorageInterface $bynderStorage, BynderService $bynderService, @@ -57,7 +64,7 @@ public function getFilesAction(ServerRequestInterface $request): ResponseInterfa $error = ''; foreach ($request->getParsedBody()['files'] ?? [] as $fileIdentifier) { $file = $this->bynderStorage->getFile($fileIdentifier); - if ($file) { + if ($file instanceof File) { // (Re)Fetch metadata $this->indexer->extractMetaData($file); $files[] = $file->getUid(); diff --git a/Classes/EventListener/PersistBynderFileStorage.php b/Classes/EventListener/PersistBynderFileStorage.php new file mode 100644 index 0000000..2c0956f --- /dev/null +++ b/Classes/EventListener/PersistBynderFileStorage.php @@ -0,0 +1,84 @@ +executionTime = $GLOBALS['EXEC_TIME'] ?? time(); + $this->storageRepository = $storageRepository; + } + + /** + * @param \TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent $event + * @return void + */ + public function __invoke(AfterPackageActivationEvent $event): void + { + if ($event->getPackageKey() !== 'bynder') { + return; + } + + if ($this->storageRepository->findByStorageType(BynderDriver::KEY) !== []) { + return; + } + + $this->createBynderStorage(); + } + + /** + * @return void + */ + private function createBynderStorage(): void + { + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + + // Create Bynder storage + $field_values = [ + 'pid' => 0, + 'tstamp' => $this->executionTime, + 'crdate' => $this->executionTime, + 'name' => 'Bynder', + 'description' => 'Automatically created during the installation of EXT:bynder', + 'driver' => BynderDriver::KEY, + 'configuration' => '', + 'is_online' => 1, + 'is_browsable' => 1, + 'is_public' => 1, + 'is_writable' => 0, + 'is_default' => 0, + // We use the processed file folder of the default storage as fallback + 'processingfolder' => '1:/_processed_/', + ]; + + $dbConnection = $connectionPool->getConnectionForTable('sys_file_storage'); + $dbConnection->insert('sys_file_storage', $field_values); + $storageUid = (int)$dbConnection->lastInsertId('sys_file_storage'); + + // Create file mount (for the editors) + $field_values = [ + 'pid' => 0, + 'tstamp' => $this->executionTime, + 'title' => 'Bynder', + 'description' => 'Automatically created during the installation of EXT:bynder', + 'path' => '', + 'base' => $storageUid, + ]; + + $dbConnection = $connectionPool->getConnectionForTable('sys_filemounts'); + $dbConnection->insert('sys_filemounts', $field_values); + } +} diff --git a/Classes/Resource/AssetProcessing.php b/Classes/EventListener/ProcessBynderAsset.php similarity index 55% rename from Classes/Resource/AssetProcessing.php rename to Classes/EventListener/ProcessBynderAsset.php index 41c457c..4a354e4 100644 --- a/Classes/Resource/AssetProcessing.php +++ b/Classes/EventListener/ProcessBynderAsset.php @@ -1,79 +1,45 @@ isNew() - || (!$processedFile->usesOriginalFile() && !$processedFile->exists()) - || $processedFile->isOutdated(); + $this->bynderService = $bynderService; } /** - * Create url for scalled/cropped versions of Bynder assets - * - * @param FileProcessingService $fileProcessingService - * @param DriverInterface $driver - * @param ProcessedFile $processedFile - * @param File $file - * @param $taskType - * @param array $configuration - * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + * @param \TYPO3\CMS\Core\Resource\Event\BeforeFileProcessingEvent $event + * @return void */ - public function processFile(FileProcessingService $fileProcessingService, DriverInterface $driver, ProcessedFile $processedFile, File $file, $taskType, array $configuration) + public function __invoke(\TYPO3\CMS\Core\Resource\Event\BeforeFileProcessingEvent $event): void { + $file = $event->getFile(); if ($file->getStorage()->getDriverType() !== BynderDriver::KEY) { return; } + $processedFile = $event->getProcessedFile(); if (!$this->needsReprocessing($processedFile)) { return; } - try { - $mediaInfo = $this->getBynderService()->getMediaInfo($processedFile->getOriginalFile()->getIdentifier()); + $mediaInfo = $this->bynderService->getMediaInfo($file->getIdentifier()); } catch (ClientException $e) { $mediaInfo = [ 'isPublic' => false, @@ -81,13 +47,15 @@ public function processFile(FileProcessingService $fileProcessingService, Driver 'thul' => '', 'webimage' => '', 'mini' => '', - ] + ], ]; + } catch (NoSuchCacheException $e) { + return; } - $processingConfiguration = $processedFile->getProcessingConfiguration(); + $processingConfiguration = $event->getConfiguration(); // The CONTEXT_IMAGEPREVIEW task only gives max dimensions - if ($taskType === ProcessedFile::CONTEXT_IMAGEPREVIEW) { + if ($event->getTaskType() === ProcessedFile::CONTEXT_IMAGEPREVIEW) { if (!empty($processingConfiguration['width'])) { $processingConfiguration['width'] .= 'm'; } @@ -98,56 +66,57 @@ public function processFile(FileProcessingService $fileProcessingService, Driver $fileInfo = $this->getThumbnailInfo( $processingConfiguration, - (int)$processedFile->getOriginalFile()->getProperty('width'), - (int)$processedFile->getOriginalFile()->getProperty('height'), - $mediaInfo['isPublic'] ? $this->getBynderService()->getOTFBaseUrl() . $processedFile->getOriginalFile()->getIdentifier() : '', + (int)$file->getProperty('width'), + (int)$file->getProperty('height'), $mediaInfo['thumbnails'] ); - // Fetch OTF url to trigger generation of the derivative - if ($fileInfo['type'] === 'otf') { - GeneralUtility::getUrl($fileInfo['url']); - self::$lastRequestedOtfAsset = microtime(true); - } - $processedFile->setUsesOriginalFile(); $checksum = $processedFile->getTask()->getConfigurationChecksum(); $processedFile->setIdentifier('processed_' . $file->getIdentifier() . '_' . $fileInfo['type'] . '_' . $checksum); // Update existing processed file - $processedFile->updateProperties( - [ - 'bynder' => true, - 'width' => $fileInfo['width'], - 'height' => $fileInfo['height'], - 'checksum' => $checksum, - 'bynder_url' => $fileInfo['url'], - ] - ); + $processedFile->updateProperties([ + 'bynder' => true, + 'width' => $fileInfo['width'], + 'height' => $fileInfo['height'], + 'checksum' => $checksum, + 'bynder_url' => $fileInfo['url'], + ]); // Persist processed file like done in FileProcessingService::process() $processedFileRepository = GeneralUtility::makeInstance(ProcessedFileRepository::class); $processedFileRepository->add($processedFile); + + $event->setProcessedFile($processedFile); + } + + /** + * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile + * @return bool + */ + protected function needsReprocessing(ProcessedFile $processedFile): bool + { + return $processedFile->isNew() + || (!$processedFile->usesOriginalFile() && !$processedFile->exists()) + || $processedFile->isOutdated(); } /** * 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 array $configuration + * @param int $orgWidth + * @param int $orgHeight + * @param array $derivatives * @return array */ - protected function getThumbnailInfo(array $configuration, int $orgWidth, int $orgHeight, string $otfBaseUrl, array $derivatives): array + protected function getThumbnailInfo(array $configuration, int $orgWidth, int $orgHeight, array $derivatives): array { $rawWidth = $configuration['width'] ?? $configuration['maxWidth'] ?? 0; $rawHeight = $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 ($rawWidth && $rawHeight && strpos($rawWidth . $rawWidth, 'm') < 0) { @@ -157,7 +126,6 @@ protected function getThumbnailInfo(array $configuration, int $orgWidth, int $or // When width and height are set and one of then have a 'c' suffix we don't keep existing ratio and allow cropping if ($rawWidth && $rawHeight && strpos($rawWidth . $rawWidth, 'c') >= 0) { $keepRatio = false; - $crop = true; } $width = (int)$rawWidth; @@ -179,19 +147,6 @@ protected function getThumbnailInfo(array $configuration, int $orgWidth, int $or $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, @@ -223,12 +178,12 @@ protected function getThumbnailInfo(array $configuration, int $orgWidth, int $or /** * Calculate relative dimension * - * For instance you have the original width, height and new width. + * 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 + * @param int $orgA + * @param int $orgB + * @param int $newA * @return int */ protected function calculateRelativeDimension(int $orgA, int $orgB, int $newA): int @@ -239,31 +194,4 @@ protected function calculateRelativeDimension(int $orgA, int $orgB, int $newA): return (int)($orgB / ($orgA / $newA)); } - - /** - * @return BynderService - * @throws \InvalidArgumentException - */ - protected function getBynderService(): BynderService - { - if ($this->bynderService === null) { - $this->bynderService = GeneralUtility::makeInstance(BynderService::class); - } - - return $this->bynderService; - } - - /** - * Bynder needs some time to process the OTF images - */ - public function __destruct() - { - if (!self::$lastRequestedOtfAsset) { - return; - } - $difference = microtime(true) - (float)self::$lastRequestedOtfAsset; - if ($difference < 3) { - sleep(ceil(3 - $difference)); - } - } } diff --git a/Classes/EventListener/PublicBynderUrl.php b/Classes/EventListener/PublicBynderUrl.php new file mode 100644 index 0000000..f578d47 --- /dev/null +++ b/Classes/EventListener/PublicBynderUrl.php @@ -0,0 +1,40 @@ +getResource(); + $relativeToCurrentScript = $event->isRelativeToCurrentScript(); + + /** @var \BeechIt\Bynder\Resource\BynderDriver $driver */ + $driver = $event->getDriver(); + + if ($resourceObject instanceof FileInterface && $resourceObject->getProperty('bynder') === true) { + if ($resourceObject->getProperty('bynder_url')) { + $publicUrl = $resourceObject->getProperty('bynder_url'); + } else { + $publicUrl = ConfigurationUtility::getUnavailableImage($relativeToCurrentScript); + } + } elseif ($driver instanceof BynderDriver) { + try { + $publicUrl = $driver->getPublicUrl($resourceObject->getIdentifier()); + } catch (FileDoesNotExistException $e) { + $publicUrl = ConfigurationUtility::getUnavailableImage($relativeToCurrentScript); + } + } else { + return; + } + + if ($publicUrl) { + $event->setPublicUrl($publicUrl); + } + } +} 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/Factory/BynderClientFactory.php b/Classes/Factory/BynderClientFactory.php new file mode 100644 index 0000000..7590793 --- /dev/null +++ b/Classes/Factory/BynderClientFactory.php @@ -0,0 +1,19 @@ +isProcessedFile($fileIdentifier)) { return true; @@ -297,22 +292,22 @@ public function isFolderEmpty($folderIdentifier) * @param string $newFileName * @param bool $removeOriginal * @return string|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045386); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045386); } /** * @param string $fileName * @param string $parentFolderIdentifier * @return string|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function createFile($fileName, $parentFolderIdentifier) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045387); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045387); } /** @@ -320,39 +315,39 @@ public function createFile($fileName, $parentFolderIdentifier) * @param string $targetFolderIdentifier * @param string $fileName * @return string|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045388); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045388); } /** * @param string $fileIdentifier * @param string $newName * @return string|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function renameFile($fileIdentifier, $newName) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045389); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045389); } /** * @param string $fileIdentifier * @param string $localFilePath * @return bool|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function replaceFile($fileIdentifier, $localFilePath) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045390); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045390); } /** * @param string $fileIdentifier * @return bool - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function deleteFile($fileIdentifier) { @@ -361,7 +356,7 @@ public function deleteFile($fileIdentifier) if ($this->isProcessedFile($fileIdentifier)) { return true; } - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045391); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045391); } /** @@ -395,11 +390,11 @@ public function hash($fileIdentifier, $hashAlgorithm) * @param string $targetFolderIdentifier * @param string $newFileName * @return string|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045392); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045392); } /** @@ -407,11 +402,11 @@ public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, * @param string $targetFolderIdentifier * @param string $newFolderName * @return array|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045393); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045393); } /** @@ -419,17 +414,17 @@ public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderId * @param string $targetFolderIdentifier * @param string $newFolderName * @return bool|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045394); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045394); } /** * @param string $fileIdentifier * @return string - * @throws Exception\FileDoesNotExistException + * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException */ public function getFileContents($fileIdentifier) { @@ -450,11 +445,11 @@ public function getFileContents($fileIdentifier) * @param string $fileIdentifier * @param string $contents * @return int|void - * @throws NotImplementedException + * @throws \BeechIt\Bynder\Exception\NotImplementedException */ public function setFileContents($fileIdentifier, $contents) { - throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', __CLASS__, __METHOD__), 1519045395); + throw new NotImplementedException(sprintf('Method %s::%s() is not implemented', self::class, __METHOD__), 1519045395); } /** @@ -506,13 +501,13 @@ public function getPermissions($identifier) * buffer before. Will be taken care of by the Storage. * * @param string $identifier - * @throws Exception\FileDoesNotExistException + * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException */ public function dumpFileContents($identifier) { try { $downloadLocation = $this->getAssetBankManager()->getMediaDownloadLocation($identifier)->wait(); - readfile($downloadLocation['s3_file'], 0); + readfile($downloadLocation['s3_file'], false); } catch (\Exception $exception) { throw new FileDoesNotExistException( sprintf('Requested file "%s" coudn\'t be found', $identifier), @@ -540,7 +535,7 @@ public function isWithin($folderIdentifier, $identifier) * @param string $fileIdentifier * @param array $propertiesToExtract * @return array - * @throws Exception\FileDoesNotExistException + * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException */ public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = []) { @@ -605,6 +600,18 @@ public function getFilesInFolder( $sort = '', $sortRev = false ) { + if ($folderIdentifier === $this->rootFolder) { + $message = GeneralUtility::makeInstance(FlashMessage::class, + 'As these files are handled by Bynder, you are not able to see the files.', + 'Bynder media not browsable', + FlashMessage::ERROR + ); + + $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); + $messageQueue = $flashMessageService->getMessageQueueByIdentifier(); + $messageQueue->addMessage($message); + } + return []; } @@ -672,14 +679,14 @@ protected function isProcessedFile(string $fileIdentifier): bool } /** - * @return AssetBankManager + * @return \Bynder\Api\Impl\AssetBankManager * @throws \InvalidArgumentException */ protected function getAssetBankManager(): AssetBankManager { if ($this->assetBankManager === null) { $this->assetBankManager = $this->getBynderService() - ->getBynderApi() + ->getBynderClient() ->getAssetBankManager(); } @@ -687,7 +694,7 @@ protected function getAssetBankManager(): AssetBankManager } /** - * @return BynderService + * @return \BeechIt\Bynder\Service\BynderService * @throws \InvalidArgumentException */ protected function getBynderService(): BynderService @@ -736,7 +743,6 @@ protected function extractFileInformation(array $mediaInfo, array $propertiesToE * * @param array $mediaInfo * @param string $property - * * @return bool|int|string * @throws \InvalidArgumentException */ @@ -791,7 +797,7 @@ protected function getSpecificFileInformation($mediaInfo, $property) * @param string $fileIdentifier * @return string The temporary path * @throws \RuntimeException - * @throws Exception\FileDoesNotExistException + * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException */ protected function saveFileToTemporaryPath($fileIdentifier): string { @@ -812,7 +818,7 @@ protected function saveFileToTemporaryPath($fileIdentifier): string * * @param string $fileIdentifier * @return string - * @throws Exception\FileDoesNotExistException + * @throws \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException */ protected function getTemporaryPathForFile($fileIdentifier): string { diff --git a/Classes/Service/BynderService.php b/Classes/Service/BynderService.php index a25137c..9f38cb4 100644 --- a/Classes/Service/BynderService.php +++ b/Classes/Service/BynderService.php @@ -8,144 +8,28 @@ * All code (c) Beech.it all rights reserved */ -use BeechIt\Bynder\Exception\InvalidExtensionConfigurationException; -use BeechIt\Bynder\Utility\ConfigurationUtility; use Bynder\Api\BynderApiFactory; +use Bynder\Api\BynderClient; use GuzzleHttp\Exception\RequestException; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -/** - * Class BynderService - */ class BynderService implements SingletonInterface { - /** - * @var string - */ + /** @var string */ 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 - */ - protected $bynderApi; + /** @var \Bynder\Api\BynderClient */ + protected $bynderClient; - /** - * @var FrontendInterface - */ + /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */ protected $cache; - public function __construct() - { - $extensionConfiguration = 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 + public function __construct(BynderClient $bynderClient) { - return $this->oAuthTokenSecret; + $this->bynderClient = $bynderClient; } /** @@ -157,7 +41,7 @@ public function getMediaInfo(string $uuid): array { // If $entry is null, it hasn't been cached. Calculate the value and store it in the cache: if (($fileInfo = $this->getCache()->get('mediainfo_' . $uuid)) === false) { - $fileInfo = $this->getBynderApi()->getAssetBankManager()->getMediaInfo($uuid)->wait(); + $fileInfo = $this->bynderClient->getAssetBankManager()->getMediaInfo($uuid)->wait(); $this->getCache()->set('mediainfo_' . $uuid, $fileInfo, [], 60); } @@ -174,11 +58,11 @@ public function getMediaInfo(string $uuid): array public function addAssetUsage( string $uuid, string $uri, - string $additionalInfo = null, - \DateTime $dateTime = null + ?string $additionalInfo = null, + ?\DateTime $dateTime = null ): bool { try { - $usage = $this->getBynderApi()->getAssetBankManager()->createUsage([ + $usage = $this->bynderClient->getAssetBankManager()->createUsage([ 'integration_id' => $this->bynderIntegrationId, 'asset_id' => $uuid, 'uri' => $uri, @@ -200,7 +84,7 @@ public function addAssetUsage( public function deleteAssetUsage(string $uuid, string $uri): bool { try { - $response = $this->getBynderApi()->getAssetBankManager()->deleteUSage([ + $response = $this->bynderClient->getAssetBankManager()->deleteUSage([ 'integration_id' => $this->bynderIntegrationId, 'asset_id' => $uuid, 'uri' => $uri, @@ -213,7 +97,7 @@ public function deleteAssetUsage(string $uuid, string $uri): bool } /** - * @return FrontendInterface + * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ protected function getCache(): FrontendInterface diff --git a/Classes/Slot/InstallSlot.php b/Classes/Slot/InstallSlot.php deleted file mode 100644 index a2b8c14..0000000 --- a/Classes/Slot/InstallSlot.php +++ /dev/null @@ -1,76 +0,0 @@ -findByStorageType(BynderDriver::KEY) !== []) { - return; - } - - // Create Bynder storage - $field_values = [ - 'pid' => 0, - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'crdate' => $GLOBALS['EXEC_TIME'], - 'name' => 'Bynder', - 'description' => 'Automatically created during the installation of EXT:bynder', - 'driver' => BynderDriver::KEY, - 'configuration' => '', - 'is_online' => 1, - 'is_browsable' => 1, - 'is_public' => 1, - 'is_writable' => 0, - 'is_default' => 0, - // We use the processed file folder of the default storage as fallback - 'processingfolder' => '1:/_processed_/', - ]; - - $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable('sys_file_storage'); - $dbConnection->insert('sys_file_storage', $field_values); - $storageUid = (int)$dbConnection->lastInsertId('sys_file_storage'); - - // Create file mount (for the editors) - $field_values = [ - 'pid' => 0, - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'title' => 'Bynder', - 'description' => 'Automatically created during the installation of EXT:bynder', - 'path' => '', - 'base' => $storageUid, - ]; - - $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable('sys_filemounts'); - $dbConnection->insert('sys_filemounts', $field_values); - } -} \ No newline at end of file diff --git a/Classes/Slot/PublicUrlSlot.php b/Classes/Slot/PublicUrlSlot.php deleted file mode 100644 index c4e9b7c..0000000 --- a/Classes/Slot/PublicUrlSlot.php +++ /dev/null @@ -1,78 +0,0 @@ -getProperty('bynder') === true) { - if ($resourceObject->getProperty('bynder_url')) { - $urlData['publicUrl'] = $resourceObject->getProperty('bynder_url'); - } else { - $urlData['publicUrl'] = $this->getUnavailableImage($relativeToCurrentScript); - } - } elseif ($driver instanceof BynderDriver) { - try { - $urlData['publicUrl'] = $driver->getPublicUrl($resourceObject->getIdentifier()); - } catch (FileDoesNotExistException $e) { - $urlData['publicUrl'] = $this->getUnavailableImage($relativeToCurrentScript); - } - } - } - - /** - * @param bool $relativeToCurrentScript - * @return string - */ - protected function getUnavailableImage($relativeToCurrentScript = false): string - { - $configuration = ConfigurationUtility::getExtensionConfiguration(); - $path = GeneralUtility::getFileAbsFileName( - $configuration['image_unavailable'] ?? - 'EXT:bynder/Resources/Public/Icons/ImageUnavailable.svg' - ); - - return ($relativeToCurrentScript) ? PathUtility::getAbsoluteWebPath($path) : str_replace(Environment::getPublicPath() . '/', '', $path); - } -} diff --git a/Classes/Utility/ConfigurationUtility.php b/Classes/Utility/ConfigurationUtility.php index 4d6a52f..76b2bf5 100644 --- a/Classes/Utility/ConfigurationUtility.php +++ b/Classes/Utility/ConfigurationUtility.php @@ -2,55 +2,50 @@ namespace BeechIt\Bynder\Utility; +use BeechIt\Bynder\Exception\InvalidExtensionConfigurationException; +use GuzzleHttp\HandlerStack; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Exception; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Object\ObjectManager; -use TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility as CoreConfigurationUtility; +use TYPO3\CMS\Core\Utility\PathUtility; /** * Utility: Configuration - * @package BeechIt\Bynder\Utility */ class ConfigurationUtility { const EXTENSION = 'bynder'; + /** @var array */ + private static $configuration; + /** * @return array */ - public static function getExtensionConfiguration(): array + protected static function getExtensionConfiguration(): 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']; + if (!self::$configuration) { + try { + $configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::EXTENSION); + } catch (Exception $e) { + $configuration = []; } - } else { - $extensionConfiguration = $objectManager->get(ExtensionConfiguration::class)->get('bynder'); - } - if (isset($extensionConfiguration['url'])) { - $extensionConfiguration['url'] = static::cleanUrl($extensionConfiguration['url']); - } - $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']); + self::$configuration = $configuration; } - return $extensionConfiguration; + + return self::$configuration; } /** - * Clean url - * - * When url given, make sure url is a valid url - * - * @param string $url * @return string + * @throws \BeechIt\Bynder\Exception\InvalidExtensionConfigurationException */ - public static function cleanUrl(string $url): string + public static function getDomain(): string { + $url = self::getExtensionConfiguration()['url'] ?? ''; + if ($url === '') { return $url; } @@ -67,6 +62,58 @@ public static function cleanUrl(string $url): string $url = rtrim($url, '/') . '/'; } - return $url; + $domain = parse_url($url, PHP_URL_HOST) ?: ''; + if (empty($domain)) { + throw new InvalidExtensionConfigurationException('Make sure Bynder domain is configured in extension manager', 1651241069); + } + + return $domain; + } + + /** + * @return string + * @throws \BeechIt\Bynder\Exception\InvalidExtensionConfigurationException + */ + public static function getPermanentToken(): string + { + $token = self::getExtensionConfiguration()['permanent_token'] ?? ''; + + if (empty($token)) { + throw new InvalidExtensionConfigurationException('Make sure Bynder permanent token is configured in extension manager', 1651241125); + } + + return $token; + } + + /** + * @return array + */ + public static function getHTTPRequestOptions(): array + { + $httpOptions = $GLOBALS['TYPO3_CONF_VARS']['HTTP'] ?? []; + $httpOptions['verify'] = filter_var($httpOptions['verify'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? $httpOptions['verify']; + + if (isset($httpOptions['handler']) && is_array($httpOptions['handler'])) { + $stack = HandlerStack::create(); + foreach ($httpOptions['handler'] ?? [] as $handler) { + $stack->push($handler); + } + $httpOptions['handler'] = $stack; + } + + return $httpOptions; + } + + /** + * @param bool $relativeToCurrentScript + * @return string + */ + public static function getUnavailableImage(bool $relativeToCurrentScript = false): string + { + $path = GeneralUtility::getFileAbsFileName( + self::getExtensionConfiguration()['image_unavailable'] ?: 'EXT:bynder/Resources/Public/Icons/ImageUnavailable.svg' + ); + + return ($relativeToCurrentScript) ? PathUtility::getAbsoluteWebPath($path) : str_replace(Environment::getPublicPath() . '/', '', $path); } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 1308e4e..6a435af 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -20,9 +20,35 @@ services: arguments: $storage: '@storage.bynder' + client.bynder: + class: Bynder\Api\BynderClient + factory: '@BeechIt\Bynder\Factory\BynderClientFactory' + + BeechIt\Bynder\Service\BynderService: + arguments: + $bynderClient: '@client.bynder' + BeechIt\Bynder\Controller\CompactViewController: public: true arguments: $bynderStorage: '@storage.bynder' $view: '@fluid_view.bynder' $indexer: '@indexer.bynder_storage' + + BeechIt\Bynder\EventListener\PersistBynderFileStorage: + tags: + - name: event.listener + identifier: 'bynder::persistStorage' + event: TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent + + BeechIt\Bynder\EventListener\ProcessBynderAsset: + tags: + - name: event.listener + identifier: 'bynder::processAsset' + event: TYPO3\CMS\Core\Resource\Event\BeforeFileProcessingEvent + + BeechIt\Bynder\EventListener\PublicBynderUrl: + tags: + - name: event.listener + identifier: 'bynder::publicUrl' + event: TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent diff --git a/README.md b/README.md index 2d3a013..03f80c4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The extension will allow the authorised users to: ### Requirements 1) You are a customer of Bynder https://www.bynder.com/trial/ -2) You have API access (tokens) https://domain.getbynder.com/pysettings#api +2) You have API access (permanent tokens) https://domain.getbynder.com/pysettings/permanent-tokens/ ### Installation @@ -27,16 +27,11 @@ Next got the the extension configuration of EXT:bynder and fill in the needed ur #### Available Configuration | Key | Description | Required | Default | -| ----------------: | ------------------------------------------------------------------------------------------------ | :------: | ------------------------------------------------------ | -| url | Bynder Url (example: domain.bynder.com) | Yes | | -| otf_base_url | OnTheFly derivative's Url (1) | *No* | | -| consumer_key | Bynder OAuth consumer key | Yes | | -| consumer_secret | Bynder OAuth consumer secret | Yes | | -| token_key | Bynder OAuth token key | Yes | | -| 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 +| ----------------: |--------------------------------------------------------------------------------------------------|:--------:| ------------------------------------------------------ | +| url | Bynder Url (example: domain.bynder.com) | Yes | | +| permanent_token | Bynder API: Permanent token | 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 | + ### How to contribute diff --git a/composer.json b/composer.json index d5b32ac..ef67ebd 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "license": ["GPL-2.0+"], "require": { "typo3/cms-core": "^10.4.13", - "bynder/bynder-php-sdk": "^1.0.5" + "bynder/bynder-php-sdk": "^2.1.5" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", diff --git a/ext_conf_template.txt b/ext_conf_template.txt index 577448f..523d497 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -1,20 +1,8 @@ # cat=basic/API; type=string; label=Bynder Url (domain.bynder.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) -otf_base_url = - -# cat=basic/API; type=string; label=Bynder OAuth consumer (key) -consumer_key = - -# cat=basic/API; type=string; label=Bynder OAuth consumer secret -consumer_secret = - -# cat=basic/API; type=string; label=Bynder OAuth token (key) -token_key = - -# cat=basic/API; type=string; label=Bynder OAuth token secret -token_secret = +# cat=basic/API; type=string; label=Bynder API: Permanent token +permanent_token = # 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 diff --git a/ext_emconf.php b/ext_emconf.php index 7b096e3..d5669b9 100755 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -4,11 +4,11 @@ # Extension Manager/Repository config file for ext "bynder". ######################################################################## -$EM_CONF[$_EXTKEY] = [ +$EM_CONF['bynder'] = [ 'title' => 'Bynder integration for TYPO3', 'description' => 'Integrate the Bynder DAM into TYPO3', 'category' => 'distribution', - 'version' => '0.0.3-dev', + 'version' => '1.0.0-dev', 'state' => 'beta', 'clearCacheOnLoad' => true, 'author' => 'Frans Saris - Beech.it', diff --git a/ext_localconf.php b/ext_localconf.php index 65fa4fc..b4db2cd 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -22,28 +22,6 @@ 'flexFormDS' => 'FILE:EXT:bynder/Configuration/FlexForms/BynderDriverFlexForm.xml', ]; -// 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', - \BeechIt\Bynder\Slot\InstallSlot::class, - 'createBynderFileStorage' -); -unset($signalSlotDispatcher); - // Register hooks to post/delete usage registration $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = \BeechIt\Bynder\Hook\DataHandlerHook::class;