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 @@
+