diff --git a/Classes/AbstractOnlineMediaHelper.php b/Classes/AbstractOnlineMediaHelper.php new file mode 100644 index 0000000..e1a1e0a --- /dev/null +++ b/Classes/AbstractOnlineMediaHelper.php @@ -0,0 +1,34 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Http\RequestFactory; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\AbstractOnlineMediaHelper as Typo3AbstractOnlineMediaHelper; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +abstract class AbstractOnlineMediaHelper extends Typo3AbstractOnlineMediaHelper +{ + protected function getRequestFactory(): RequestFactory + { + return GeneralUtility::makeInstance(RequestFactory::class); + } +} diff --git a/Classes/Anchor/OnlineMediaHelper.php b/Classes/Anchor/OnlineMediaHelper.php new file mode 100644 index 0000000..c2bad97 --- /dev/null +++ b/Classes/Anchor/OnlineMediaHelper.php @@ -0,0 +1,137 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\AbstractOnlineMediaHelper; +use Symfony\Component\DomCrawler\Crawler; +use TYPO3\CMS\Core\Http\Uri; +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class OnlineMediaHelper extends AbstractOnlineMediaHelper +{ + public function transformUrlToFile($url, Folder $targetFolder) + { + $mediaId = null; + $uri = new Uri($url); + if ($uri->getHost() === Registration::DOMAIN) { + $mediaId = trim($uri->getPath(), '/'); + } + + if ($mediaId === null) { + return null; + } + + $file = $this->findExistingFileByOnlineMediaId($mediaId, $targetFolder, $this->extension); + if ($file !== null) { + return $file; + } + + $fileName = $mediaId . '.' . $this->extension; + $remoteData = $this->getRemoteData($mediaId); + if (!empty($remoteData['title'])) { + $fileName = $remoteData['title'] . '.' . $this->extension; + } + + return $this->createNewFile($targetFolder, $fileName, $mediaId); + } + + public function getPublicUrl(File $file, $relativeToCurrentScript = false) + { + return sprintf( + 'https://' . Registration::DOMAIN . '/%s', + $this->getOnlineMediaId($file) + ); + } + + public function getPreviewImage(File $file) + { + $data = $this->getRemoteData($this->getOnlineMediaId($file)); + if (isset($data['thumbnail_url']) === false) { + return ''; + } + + $imageContent = GeneralUtility::getUrl($data['thumbnail_url']); + if ($imageContent === false) { + return ''; + } + + $tempName = GeneralUtility::tempnam($file->getNameWithoutExtension(), $file->getExtension()); + GeneralUtility::writeFile($tempName, $imageContent); + + return $tempName; + } + + public function getMetaData(File $file) + { + $remoteData = $this->getRemoteData($this->getOnlineMediaId($file)); + if ($remoteData === []) { + return []; + } + + $metadata = [ + 'width' => (int)$remoteData['width'], + 'height' => (int)$remoteData['height'], + ]; + + if (empty($file->getProperty('title'))) { + $metadata['title'] = strip_tags($remoteData['title']); + } + + if (empty($file->getProperty('description'))) { + $metadata['description'] = strip_tags($remoteData['description']); + } + + return $metadata; + } + + private function getRemoteData(string $mediaId): array + { + static $data = []; + + if (isset($data[$mediaId]) !== false) { + return $data[$mediaId]; + } + + $videoWebsite = $this->getRequestFactory()->request( + 'https://' . Registration::DOMAIN . '/' . $mediaId + )->getBody()->getContents(); + if ($videoWebsite === '') { + return []; + } + + $domCrawler = new Crawler($videoWebsite); + $remoteData = [ + 'title' => $domCrawler->filter('meta[property="og:title"]')->first()->attr('content'), + 'description' => $domCrawler->filter('meta[name="description"]')->first()->attr('content'), + 'width' => (int) $domCrawler->filter('meta[property="og:image:width"]')->first()->attr('content'), + 'height' => (int) $domCrawler->filter('meta[property="og:image:height"]')->first()->attr('content'), + 'thumbnail_url' => $domCrawler->filter('meta[property="og:image"]')->first()->attr('content'), + ]; + + $remoteData['title'] = str_replace(' • A podcast on Anchor', '', $remoteData['title']); + + $data[$mediaId] = $remoteData; + return $remoteData; + } +} diff --git a/Classes/Anchor/Registration.php b/Classes/Anchor/Registration.php new file mode 100644 index 0000000..36418a0 --- /dev/null +++ b/Classes/Anchor/Registration.php @@ -0,0 +1,55 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry; +use TYPO3\CMS\Core\Utility\ArrayUtility; + +class Registration +{ + public const FILE_EXTENSION = 'anchor'; + + public const FILE_MIME_TYPE = 'audio/anchor'; + + public const DOMAIN = 'anchor.fm'; + + public static function everything() + { + RendererRegistry::getInstance()->registerRendererClass(Renderer::class); + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] .= ',' . self::FILE_EXTENSION; + ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TYPO3_CONF_VARS'], [ + 'SYS' => [ + 'fal' => [ + 'onlineMediaHelpers' => [ + self::FILE_EXTENSION => OnlineMediaHelper::class, + ], + ], + 'FileInfo' => [ + 'fileExtensionToMimeType' => [ + self::FILE_EXTENSION => self::FILE_MIME_TYPE, + ], + ], + ], + ]); + } +} diff --git a/Classes/Anchor/Renderer.php b/Classes/Anchor/Renderer.php new file mode 100644 index 0000000..f091c61 --- /dev/null +++ b/Classes/Anchor/Renderer.php @@ -0,0 +1,102 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\IFrameRenderer; +use TYPO3\CMS\Core\Http\Uri; +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; + +class Renderer implements FileRendererInterface +{ + /** + * @var IFrameRenderer + */ + private $iframeRenderer; + + public function __construct( + IFrameRenderer $iframeRenderer + ) { + $this->iframeRenderer = $iframeRenderer; + } + + public function getPriority() + { + return 1; + } + + public function canRender(FileInterface $file) + { + return ( + $file->getMimeType() === Registration::FILE_MIME_TYPE + || $file->getExtension() === Registration::FILE_EXTENSION + ) + && $this->iframeRenderer->getOnlineMediaHelper($file) !== false + ; + } + + public function render( + FileInterface $file, + $width, + $height, + array $options = [], + $usedPathsRelativeToCurrentScript = false + ) { + $options = $this->iframeRenderer->collectOptions($options, $file); + $attributes = $this->iframeRenderer->collectAttributes('400px', '102px', $options); + $attributes['frameborder'] = 0; + $attributes['scrolling'] = 'no'; + + return sprintf( + '', + htmlspecialchars($this->createUrl($options, $file), ENT_QUOTES | ENT_HTML5), + empty($attributes) ? '' : ' ' . GeneralUtility::implodeAttributes($attributes) + ); + } + + private function createUrl(array $options, FileInterface $file) + { + if ($file instanceof FileReference) { + $file = $file->getOriginalFile(); + } + + $url = $this->iframeRenderer->getOnlineMediaHelper($file)->getPublicUrl($file); + $url = $this->addEmbedPathSegment($url); + + return $url; + } + + private function addEmbedPathSegment(string $url): string + { + $url = new Uri($url); + + $pathSegments = GeneralUtility::trimExplode('/', $url->getPath(), true); + $url = $url->withPath(implode('/', array_merge([ + $pathSegments[0], + 'embed', + ], array_slice($pathSegments, 1)))); + + return (string) $url; + } +} diff --git a/Classes/Facebook/Video/OnlineMediaHelper.php b/Classes/Facebook/Video/OnlineMediaHelper.php new file mode 100644 index 0000000..59f6fcf --- /dev/null +++ b/Classes/Facebook/Video/OnlineMediaHelper.php @@ -0,0 +1,139 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\AbstractOnlineMediaHelper; +use Symfony\Component\DomCrawler\Crawler; +use TYPO3\CMS\Core\Http\RequestFactory; +use TYPO3\CMS\Core\Http\Uri; +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class OnlineMediaHelper extends AbstractOnlineMediaHelper +{ + public function transformUrlToFile($url, Folder $targetFolder) + { + $mediaId = null; + $uri = new Uri($url); + if ( + $uri->getHost() === Registration::DOMAIN + || $uri->getHost() === 'www.facebook.com' + ) { + $mediaId = trim($uri->getPath(), '/'); + } + + if ($mediaId === null) { + return null; + } + + $file = $this->findExistingFileByOnlineMediaId($mediaId, $targetFolder, $this->extension); + if ($file !== null) { + return $file; + } + + $fileName = $mediaId . '.' . $this->extension; + $remoteData = $this->getRemoteData($mediaId); + if (!empty($remoteData['title'])) { + $fileName = $remoteData['title'] . '.' . $this->extension; + } + + return $this->createNewFile($targetFolder, $fileName, $mediaId); + } + + public function getPublicUrl(File $file, $relativeToCurrentScript = false) + { + return sprintf( + 'https://' . Registration::DOMAIN . '/%s', + $this->getOnlineMediaId($file) + ); + } + + public function getPreviewImage(File $file) + { + $data = $this->getRemoteData($this->getOnlineMediaId($file)); + if (isset($data['thumbnail_url']) === false) { + return ''; + } + + $imageContent = GeneralUtility::getUrl($data['thumbnail_url']); + if ($imageContent === false) { + return ''; + } + + $tempName = GeneralUtility::tempnam($file->getNameWithoutExtension(), $file->getExtension()); + GeneralUtility::writeFile($tempName, $imageContent); + + return $tempName; + } + + public function getMetaData(File $file) + { + $remoteData = $this->getRemoteData($this->getOnlineMediaId($file)); + if ($remoteData === []) { + return []; + } + + $metadata = [ + 'width' => (int)$remoteData['width'], + 'height' => (int)$remoteData['height'], + ]; + + if (empty($file->getProperty('title'))) { + $metadata['title'] = strip_tags($remoteData['title']); + } + + if (empty($file->getProperty('description'))) { + $metadata['description'] = strip_tags($remoteData['description']); + } + + return $metadata; + } + + private function getRemoteData(string $mediaId): array + { + static $data = []; + + if (isset($data[$mediaId]) !== false) { + return $data[$mediaId]; + } + + $videoWebsite = $this->getRequestFactory()->request( + 'https://www.facebook.com/' . $mediaId + )->getBody()->getContents(); + if ($videoWebsite === '') { + return []; + } + + $domCrawler = new Crawler($videoWebsite); + $remoteData = [ + 'title' => $domCrawler->filter('meta[property="og:title"]')->first()->attr('content'), + 'description' => $domCrawler->filter('meta[name="description"]')->first()->attr('content'), + 'width' => (int) $domCrawler->filter('meta[property="og:video:width"]')->first()->attr('content'), + 'height' => (int) $domCrawler->filter('meta[property="og:video:height"]')->first()->attr('content'), + 'thumbnail_url' => $domCrawler->filter('meta[property="og:image"]')->first()->attr('content'), + ]; + + $data[$mediaId] = $remoteData; + return $remoteData; + } +} diff --git a/Classes/Facebook/Video/Registration.php b/Classes/Facebook/Video/Registration.php new file mode 100644 index 0000000..ddcbd18 --- /dev/null +++ b/Classes/Facebook/Video/Registration.php @@ -0,0 +1,55 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry; +use TYPO3\CMS\Core\Utility\ArrayUtility; + +class Registration +{ + public const FILE_EXTENSION = 'facebookvideo'; + + public const FILE_MIME_TYPE = 'video/facebook'; + + public const DOMAIN = 'facebook.com'; + + public static function everything() + { + RendererRegistry::getInstance()->registerRendererClass(Renderer::class); + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] .= ',' . self::FILE_EXTENSION; + ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TYPO3_CONF_VARS'], [ + 'SYS' => [ + 'fal' => [ + 'onlineMediaHelpers' => [ + self::FILE_EXTENSION => OnlineMediaHelper::class, + ], + ], + 'FileInfo' => [ + 'fileExtensionToMimeType' => [ + self::FILE_EXTENSION => self::FILE_MIME_TYPE, + ], + ], + ], + ]); + } +} diff --git a/Classes/Facebook/Video/Renderer.php b/Classes/Facebook/Video/Renderer.php new file mode 100644 index 0000000..b1d5d2f --- /dev/null +++ b/Classes/Facebook/Video/Renderer.php @@ -0,0 +1,83 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\IFrameRenderer; +use TYPO3\CMS\Core\Resource\FileInterface; +use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class Renderer implements FileRendererInterface +{ + /** + * @var IFrameRenderer + */ + private $iframeRenderer; + + public function __construct( + IFrameRenderer $iframeRenderer + ) { + $this->iframeRenderer = $iframeRenderer; + } + + public function getPriority() + { + return 1; + } + + public function canRender(FileInterface $file) + { + return ( + $file->getMimeType() === Registration::FILE_MIME_TYPE + || $file->getExtension() === Registration::FILE_EXTENSION + ) + && $this->iframeRenderer->getOnlineMediaHelper($file) !== false + ; + } + + public function render( + FileInterface $file, + $width, + $height, + array $options = [], + $usedPathsRelativeToCurrentScript = false + ) { + $options = $this->iframeRenderer->collectOptions($options, $file); + $attributes = $this->iframeRenderer->collectAttributes((int) $width, (int) $height, $options); + + return sprintf( + '', + htmlspecialchars($this->createUrl($options, $file), ENT_QUOTES | ENT_HTML5), + empty($attributes) ? '' : ' ' . GeneralUtility::implodeAttributes($attributes) + ); + } + + private function createUrl(array $options, FileInterface $file) + { + $file = $this->iframeRenderer->getOriginalFile($file); + + return sprintf( + 'https://' . Registration::DOMAIN . '/plugins/video.php?href=%s', + rawurlencode($this->iframeRenderer->getOnlineMediaHelper($file)->getPublicUrl($file)) + ); + } +} diff --git a/Classes/IFrameRenderer.php b/Classes/IFrameRenderer.php new file mode 100644 index 0000000..ea738ae --- /dev/null +++ b/Classes/IFrameRenderer.php @@ -0,0 +1,134 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\FileInterface; +use TYPO3\CMS\Core\Resource\FileReference; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; + +/** + * Provides common API to generate iFrames for external multimedia. + */ +class IFrameRenderer +{ + /** + * Generates pairs of key/value; not yet html-escaped + */ + public function collectAttributes( + string $width, + string $height, + array $options + ): array { + $attributes = []; + $attributes['allowfullscreen'] = true; + + if (isset($options['additionalAttributes']) && is_array($options['additionalAttributes'])) { + $attributes = array_merge($attributes, $options['additionalAttributes']); + } + if (isset($options['data']) && is_array($options['data'])) { + array_walk( + $options['data'], + function (&$value, $key) use (&$attributes) { + $attributes['data-' . $key] = $value; + } + ); + } + if ($width !== '' && $width !== 0) { + $attributes['width'] = htmlspecialchars($width); + } + if ($height !== '' && $height !== 0) { + $attributes['height'] = htmlspecialchars($height); + } + if ( + isset($GLOBALS['TSFE']) + && is_object($GLOBALS['TSFE']) + && ( + isset($GLOBALS['TSFE']->config['config']['doctype']) + && $GLOBALS['TSFE']->config['config']['doctype'] !== 'html5' + ) + ) { + $attributes['frameborder'] = 0; + } + $allowedAttributes = [ + 'class', + 'dir', + 'id', + 'lang', + 'style', + 'title', + 'accesskey', + 'tabindex', + 'onclick', + 'allow', + ]; + foreach ($allowedAttributes as $key) { + if (!empty($options[$key])) { + $attributes[$key] = $options[$key]; + } + } + + $attributes = array_map('htmlspecialchars', $attributes); + + return $attributes; + } + + public function collectOptions( + array $options, + FileInterface $file + ): array { + // Check for an autoplay option at the file reference itself, if not overridden yet. + if (!isset($options['autoplay']) && $file instanceof FileReference) { + $autoplay = $file->getProperty('autoplay'); + if ($autoplay !== null) { + $options['autoplay'] = $autoplay; + } + } + + if (!isset($options['allow'])) { + $options['allow'] = 'fullscreen'; + if (!empty($options['autoplay'])) { + $options['allow'] = 'autoplay; fullscreen'; + } + } + + return $options; + } + + public function getOriginalFile(FileInterface $file): File + { + $orgFile = $file; + + if ($orgFile instanceof FileReference) { + $orgFile = $orgFile->getOriginalFile(); + } + + return $orgFile; + } + + public function getOnlineMediaHelper(FileInterface $file): OnlineMediaHelperInterface + { + return OnlineMediaHelperRegistry::getInstance() + ->getOnlineMediaHelper($this->getOriginalFile($file)); + } +} diff --git a/Classes/Soundcloud/OnlineMediaHelper.php b/Classes/Soundcloud/OnlineMediaHelper.php new file mode 100644 index 0000000..256fe02 --- /dev/null +++ b/Classes/Soundcloud/OnlineMediaHelper.php @@ -0,0 +1,107 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Http\Uri; +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\AbstractOEmbedHelper; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class OnlineMediaHelper extends AbstractOEmbedHelper +{ + public function transformUrlToFile($url, Folder $targetFolder) + { + $id = null; + $uri = new Uri($url); + if ($uri->getHost() === Registration::DOMAIN) { + $id = trim($uri->getPath(), '/'); + } + + if ($id === null) { + return null; + } + + return $this->transformMediaIdToFile($id, $targetFolder, $this->extension); + } + + public function getPublicUrl(File $file, $relativeToCurrentScript = false) + { + return sprintf( + 'https://' . Registration::DOMAIN . '/%s', + $this->getOnlineMediaId($file) + ); + } + + public function getPreviewImage(File $file) + { + $data = $this->getOEmbedData($this->getOnlineMediaId($file)); + + if ($data === false || isset($data['thumbnail_url']) === false) { + return ''; + } + + $imageContent = GeneralUtility::getUrl($data['thumbnail_url']); + if ($imageContent === false) { + return ''; + } + + $tempName = GeneralUtility::tempnam($file->getNameWithoutExtension(), $file->getExtension()); + GeneralUtility::writeFile($tempName, $imageContent); + + return $tempName; + } + + // Wrap original to add caching. + // Otherwise foreign API might be called multiple times. + protected function getOEmbedData($mediaId) + { + static $data = []; + + if (isset($data[$mediaId]) === false) { + $data[$mediaId] = parent::getOEmbedData($mediaId); + } + + return $data[$mediaId]; + } + + public function getMetaData(File $file) + { + $oEmbed = $this->getOEmbedData($this->getOnlineMediaId($file)); + $metadata = parent::getMetaData($file); + + if (empty($file->getProperty('description'))) { + $metadata['description'] = strip_tags($oEmbed['description']); + } + + return $metadata; + } + + protected function getOEmbedUrl($mediaId, $format = 'json') + { + return sprintf( + 'https://' . Registration::DOMAIN . '/oembed?format=%s&url=%s', + rawurlencode($format), + sprintf('https://' . Registration::DOMAIN . '/%s', $mediaId) + ); + } +} diff --git a/Classes/Soundcloud/Registration.php b/Classes/Soundcloud/Registration.php new file mode 100644 index 0000000..4493368 --- /dev/null +++ b/Classes/Soundcloud/Registration.php @@ -0,0 +1,57 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry; +use TYPO3\CMS\Core\Utility\ArrayUtility; + +class Registration +{ + public const FILE_EXTENSION = 'soundcloud'; + + public const FILE_MIME_TYPE = 'audio/soundcloud'; + + public const DOMAIN = 'soundcloud.com'; + + public const EMBED_DOMAIN = 'w.soundcloud.com'; + + public static function everything() + { + RendererRegistry::getInstance()->registerRendererClass(Renderer::class); + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] .= ',' . self::FILE_EXTENSION; + ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TYPO3_CONF_VARS'], [ + 'SYS' => [ + 'fal' => [ + 'onlineMediaHelpers' => [ + self::FILE_EXTENSION => OnlineMediaHelper::class, + ], + ], + 'FileInfo' => [ + 'fileExtensionToMimeType' => [ + self::FILE_EXTENSION => self::FILE_MIME_TYPE, + ], + ], + ], + ]); + } +} diff --git a/Classes/Soundcloud/Renderer.php b/Classes/Soundcloud/Renderer.php new file mode 100644 index 0000000..d05fd95 --- /dev/null +++ b/Classes/Soundcloud/Renderer.php @@ -0,0 +1,89 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\IFrameRenderer; +use TYPO3\CMS\Core\Resource\FileInterface; +use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class Renderer implements FileRendererInterface +{ + /** + * @var IFrameRenderer + */ + private $iframeRenderer; + + public function __construct( + IFrameRenderer $iframeRenderer + ) { + $this->iframeRenderer = $iframeRenderer; + } + + public function getPriority() + { + return 1; + } + + public function canRender(FileInterface $file) + { + return ( + $file->getMimeType() === Registration::FILE_MIME_TYPE + || $file->getExtension() === Registration::FILE_EXTENSION + ) + && $this->iframeRenderer->getOnlineMediaHelper($file) !== false + ; + } + + public function render( + FileInterface $file, + $width, + $height, + array $options = [], + $usedPathsRelativeToCurrentScript = false + ) { + $options = $this->iframeRenderer->collectOptions($options, $file); + $attributes = $this->iframeRenderer->collectAttributes($width . '%', $height, $options); + + return sprintf( + '', + htmlspecialchars($this->createUrl($options, $file), ENT_QUOTES | ENT_HTML5), + empty($attributes) ? '' : ' ' . GeneralUtility::implodeAttributes($attributes) + ); + } + + private function createUrl(array $options, FileInterface $file) + { + $urlParams = []; + if (!empty($options['autoplay'])) { + $urlParams[] = 'auto_play=1'; + } + + $file = $this->iframeRenderer->getOriginalFile($file); + + return sprintf( + 'https://' . Registration::EMBED_DOMAIN . '/player/?url=%s?%s', + rawurlencode($this->iframeRenderer->getOnlineMediaHelper($file)->getPublicUrl($file)), + implode('&', $urlParams) + ); + } +} diff --git a/Classes/Spotify/OnlineMediaHelper.php b/Classes/Spotify/OnlineMediaHelper.php new file mode 100644 index 0000000..378d122 --- /dev/null +++ b/Classes/Spotify/OnlineMediaHelper.php @@ -0,0 +1,133 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\AbstractOnlineMediaHelper; +use Symfony\Component\DomCrawler\Crawler; +use TYPO3\CMS\Core\Http\Uri; +use TYPO3\CMS\Core\Resource\File; +use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class OnlineMediaHelper extends AbstractOnlineMediaHelper +{ + public function transformUrlToFile($url, Folder $targetFolder) + { + $mediaId = null; + $uri = new Uri($url); + if ($uri->getHost() === Registration::DOMAIN) { + $mediaId = trim($uri->getPath(), '/'); + } + + if ($mediaId === null) { + return null; + } + + $file = $this->findExistingFileByOnlineMediaId($mediaId, $targetFolder, $this->extension); + if ($file !== null) { + return $file; + } + + $fileName = $mediaId . '.' . $this->extension; + $remoteData = $this->getRemoteData($mediaId); + if (!empty($remoteData['title'])) { + $fileName = $remoteData['title'] . '.' . $this->extension; + } + + return $this->createNewFile($targetFolder, $fileName, $mediaId); + } + + public function getPublicUrl(File $file, $relativeToCurrentScript = false) + { + return sprintf( + 'https://' . Registration::DOMAIN . '/%s', + $this->getOnlineMediaId($file) + ); + } + + public function getPreviewImage(File $file) + { + $data = $this->getRemoteData($this->getOnlineMediaId($file)); + if (isset($data['thumbnail_url']) === false) { + return ''; + } + + $imageContent = GeneralUtility::getUrl($data['thumbnail_url']); + if ($imageContent === false) { + return ''; + } + + $tempName = GeneralUtility::tempnam($file->getNameWithoutExtension(), $file->getExtension()); + GeneralUtility::writeFile($tempName, $imageContent); + + return $tempName; + } + + public function getMetaData(File $file) + { + $remoteData = $this->getRemoteData($this->getOnlineMediaId($file)); + if ($remoteData === []) { + return []; + } + + $metadata = [ + 'width' => (int)$remoteData['width'], + 'height' => (int)$remoteData['height'], + ]; + + if (empty($file->getProperty('title'))) { + $metadata['title'] = strip_tags($remoteData['title']); + } + + if (empty($file->getProperty('description'))) { + $metadata['description'] = strip_tags($remoteData['description']); + } + + return $metadata; + } + + private function getRemoteData(string $mediaId): array + { + static $data = []; + + if (isset($data[$mediaId]) !== false) { + return $data[$mediaId]; + } + + $videoWebsite = $this->getRequestFactory()->request( + 'https://' . Registration::DOMAIN . '/' . $mediaId + )->getBody()->getContents(); + if ($videoWebsite === '') { + return []; + } + + $domCrawler = new Crawler($videoWebsite); + $remoteData = [ + 'title' => $domCrawler->filter('meta[property="og:title"]')->first()->attr('content'), + 'description' => $domCrawler->filter('meta[name="description"]')->first()->attr('content'), + 'thumbnail_url' => $domCrawler->filter('meta[property="og:image"]')->first()->attr('content'), + ]; + + $data[$mediaId] = $remoteData; + return $remoteData; + } +} diff --git a/Classes/Spotify/Registration.php b/Classes/Spotify/Registration.php new file mode 100644 index 0000000..9d7ef1f --- /dev/null +++ b/Classes/Spotify/Registration.php @@ -0,0 +1,55 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry; +use TYPO3\CMS\Core\Utility\ArrayUtility; + +class Registration +{ + public const FILE_EXTENSION = 'spotify'; + + public const FILE_MIME_TYPE = 'audio/spotify'; + + public const DOMAIN = 'open.spotify.com'; + + public static function everything() + { + RendererRegistry::getInstance()->registerRendererClass(Renderer::class); + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] .= ',' . self::FILE_EXTENSION; + ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TYPO3_CONF_VARS'], [ + 'SYS' => [ + 'fal' => [ + 'onlineMediaHelpers' => [ + self::FILE_EXTENSION => OnlineMediaHelper::class, + ], + ], + 'FileInfo' => [ + 'fileExtensionToMimeType' => [ + self::FILE_EXTENSION => self::FILE_MIME_TYPE, + ], + ], + ], + ]); + } +} diff --git a/Classes/Spotify/Renderer.php b/Classes/Spotify/Renderer.php new file mode 100644 index 0000000..43fb2bd --- /dev/null +++ b/Classes/Spotify/Renderer.php @@ -0,0 +1,96 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\ExternalTypo3Multimedia\IFrameRenderer; +use TYPO3\CMS\Core\Http\Uri; +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; + +class Renderer implements FileRendererInterface +{ + /** + * @var IFrameRenderer + */ + private $iframeRenderer; + + public function __construct( + IFrameRenderer $iframeRenderer + ) { + $this->iframeRenderer = $iframeRenderer; + } + + public function getPriority() + { + return 1; + } + + public function canRender(FileInterface $file) + { + return ( + $file->getMimeType() === Registration::FILE_MIME_TYPE + || $file->getExtension() === Registration::FILE_EXTENSION + ) + && $this->iframeRenderer->getOnlineMediaHelper($file) !== false + ; + } + + public function render( + FileInterface $file, + $width, + $height, + array $options = [], + $usedPathsRelativeToCurrentScript = false + ) { + $options = $this->iframeRenderer->collectOptions($options, $file); + $attributes = $this->iframeRenderer->collectAttributes('100%', '235px', $options); + $attributes['allowtransparency'] = 'true'; + $attributes['allow'] = 'encrypted-media'; + + return sprintf( + '', + htmlspecialchars($this->createUrl($options, $file), ENT_QUOTES | ENT_HTML5), + empty($attributes) ? '' : ' ' . GeneralUtility::implodeAttributes($attributes) + ); + } + + private function createUrl(array $options, FileInterface $file) + { + if ($file instanceof FileReference) { + $file = $file->getOriginalFile(); + } + + $url = $this->iframeRenderer->getOnlineMediaHelper($file)->getPublicUrl($file); + $url = $this->addEmbedPathSegment($url); + + return $url; + } + + private function addEmbedPathSegment(string $url): string + { + $url = new Uri($url); + $url = $url->withPath('embed-podcast/' . ltrim($url->getPath(), '/')); + return (string) $url; + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..3628bb3 --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Codappix\ExternalTypo3Multimedia\: + resource: '../Classes/*' diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3336d9a --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "codappix/external-typo3-multimedia", + "type": "typo3-cms-extension", + "description": "Provide integration of external multimedia like Soundcloud and Facebook videos", + "license": "GPL-2.0-or-later", + "version": "1.0.0", + "authors": [ + { + "name": "Daniel Siepmann", + "email": "coding@daniel-siepmann.de", + "role": "Developer" + } + ], + "require": { + "typo3/cms-core": "^10.4", + "symfony/dom-crawler": "^5.0" + }, + "autoload": { + "psr-4": { + "Codappix\\ExternalTypo3Multimedia\\": "Classes/" + } + }, + "extra": { + "typo3/cms": { + "extension-key": "external_multimedia" + } + } +} diff --git a/ext_emconf.php b/ext_emconf.php new file mode 100644 index 0000000..51681a4 --- /dev/null +++ b/ext_emconf.php @@ -0,0 +1,21 @@ + 'Support External Multimedia', + 'description' => 'Add support for media like Soundcloud or Facebook videos.', + 'category' => 'plugin', + 'author' => 'Daniel Siepmann', + 'author_email' => 'coding@daniel-siepmann.de', + 'author_company' => 'Codappix GmbH', + 'state' => 'beta', + 'version' => '1.0.0', + 'constraints' => [ + 'depends' => [ + 'typo3' => '*', + ], + 'conflicts' => [ + ], + 'suggests' => [ + ], + ], +]; diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..eda3b80 --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,8 @@ +