[FEATURE] Improve handling of online media 00/41800/27
authorFrans Saris <franssaris@gmail.com>
Sat, 5 Sep 2015 20:12:50 +0000 (22:12 +0200)
committerWouter Wolters <typo3@wouterwolters.nl>
Tue, 15 Sep 2015 15:07:39 +0000 (17:07 +0200)
This patch introduces a new way of handling "online media", like
YouTube and Vimeo videos in TYPO3.
The media can be added by url and after that it can be used
like any other file.

To test with tt_content change
   $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'])
to
   $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'])
in typo3/sysext/frontend/Configuration/TCA/tt_content.php

To render a video in FE use <f:media file="{file}" />

File icon support based on mimetype will be added in a separate
patch #69764.

Releases: master
Resolves: #61799
Change-Id: I4b262e6fef80ba30fe627ef0ea3c55c2390c97ab
Reviewed-on: http://review.typo3.org/41800
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
28 files changed:
typo3/sysext/backend/Classes/Controller/File/CreateFolderController.php
typo3/sysext/backend/Classes/Controller/OnlineMediaController.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
typo3/sysext/backend/Classes/Form/FormResultCompiler.php
typo3/sysext/backend/Configuration/Backend/Routes.php
typo3/sysext/backend/Resources/Public/JavaScript/OnlineMedia.js [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconRegistry.php
typo3/sysext/core/Classes/Resource/Hook/FileInfoHook.php
typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/AbstractOEmbedHelper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/AbstractOnlineMediaHelper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/OnlineMediaHelperInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/OnlineMediaHelperRegistry.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/VimeoHelper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/YouTubeHelper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Metadata/Extractor.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/OnlineMedia/Processing/PreviewProcessing.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Rendering/VimeoRenderer.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Rendering/YouTubeRenderer.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Feature-61799-ImprovedHandlingOfOnlineMedia.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Resource/Rendering/VimeoRendererTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Resource/Rendering/YouTubeRendererTest.php [new file with mode: 0644]
typo3/sysext/core/ext_localconf.php
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/frontend/Configuration/TCA/tt_content.php
typo3/sysext/lang/locallang_core.xlf
typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php

index 7152778..57a9d2e 100644 (file)
@@ -14,18 +14,24 @@ namespace TYPO3\CMS\Backend\Controller\File;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Http\ControllerInterface;
+use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Core\Http\Response;
+use TYPO3\CMS\Core\Utility\MathUtility;
 
 /**
  * Script Class for the create-new script; Displays a form for creating up to 10 folders or one new text file
  */
-class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface {
+class CreateFolderController implements ControllerInterface {
 
        /**
         * @var int
@@ -35,7 +41,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
        /**
         * document template object
         *
-        * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
+        * @var DocumentTemplate
         */
        public $doc;
 
@@ -90,7 +96,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
        /**
         * Initialize
         *
-        * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
+        * @throws InsufficientFolderAccessPermissionsException
         * @return void
         */
        protected function init() {
@@ -100,7 +106,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
                // create the folder object
                if ($combinedIdentifier) {
-                       $this->folderObject = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($combinedIdentifier);
+                       $this->folderObject = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($combinedIdentifier);
                }
                // Cleaning and checking target directory
                if (!$this->folderObject) {
@@ -109,7 +115,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                        throw new \RuntimeException($title . ': ' . $message, 1294586845);
                }
                if ($this->folderObject->getStorage()->getUid() === 0) {
-                       throw new \TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException('You are not allowed to access folders outside your storages', 1375889838);
+                       throw new InsufficientFolderAccessPermissionsException('You are not allowed to access folders outside your storages', 1375889838);
                }
 
                // Setting the title and the icon
@@ -118,7 +124,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $icon = $iconFactory->getIcon('apps-filetree-root', Icon::SIZE_SMALL);
                $this->title = $icon . htmlspecialchars($this->folderObject->getStorage()->getName()) . ': ' . htmlspecialchars($this->folderObject->getIdentifier());
                // Setting template object
-               $this->doc = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
+               $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
                $this->doc->setModuleTemplate('EXT:backend/Resources/Private/Templates/file_newfolder.html');
                $this->doc->JScode = $this->doc->wrapScriptTags('
                        var path = "' . $this->target . '";
@@ -151,7 +157,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                if ($this->folderObject->checkActionPermission('add')) {
                        $code = '<form role="form" action="' . htmlspecialchars(BackendUtility::getModuleUrl('tce_file')) . '" method="post" name="editform">';
                        // Making the selector box for the number of concurrent folder-creations
-                       $this->number = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->number, 1, 10);
+                       $this->number = MathUtility::forceIntegerInRange($this->number, 1, 10);
                        $code .= '
                                <div class="form-group">
                                        <div class="form-section">
@@ -196,12 +202,52 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                }
 
                if ($this->folderObject->getStorage()->checkUserActionPermission('add', 'File')) {
-                       $pageContent .= '<form action="' . BackendUtility::getModuleUrl('tce_file') . '" method="post" name="editform2">';
+
+                       $pageContent .= '<form action="' . htmlspecialchars(BackendUtility::getModuleUrl('online_media')) . '" method="post" name="editform2">';
+                       // Create a list of allowed file extensions with the readable format "youtube, vimeo" etc.
+                       $fileExtList = array();
+                       $onlineMediaFileExt = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
+                       foreach ($onlineMediaFileExt as $fileExt) {
+                               if (GeneralUtility::verifyFilenameAgainstDenyPattern($fileExt)) {
+                                       $fileExtList[] = '<span class="label label-success">' . strtoupper(htmlspecialchars($fileExt)) . '</span>';
+                               }
+                       }
+                       // Add form fields for adding media files:
+                       $code = '
+                               <div class="form-group">
+                                       <div class="form-section">
+                                               <div class="form-group">
+                                                       <label for="newMedia">' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.label', TRUE) . ' ' . BackendUtility::cshItem('xMOD_csh_corebe', 'file_newMedia') . '</label>
+                                                       <div class="form-control-wrap">
+                                                               <input class="form-control" type="text" id="newMedia" name="file[newMedia][0][url]"
+                                                                       placeholder="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.placeholder', TRUE) . '" />
+                                                               <input type="hidden" name="file[newMedia][0][target]" value="' . htmlspecialchars($this->target) . '" />
+                                                       </div>
+                                                       <div class="help-block">
+                                                               ' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.allowedProviders', TRUE) . '<br>
+                                                               ' . implode(' ', $fileExtList) . '
+                                                       </div>
+                                               </div>
+                                       </div>
+                               </div>
+                               ';
+                       // Submit button for creation of a new media:
+                       $code .= '
+                               <div class="form-group">
+                                       <input class="btn btn-default" type="submit" value="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.submit', TRUE) . '" />
+                                       <input type="hidden" name="redirect" value="' . htmlspecialchars($this->returnUrl) . '" />
+                               </div>
+                               ';
+                       $pageContent .= $this->doc->section($lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media', TRUE), $code);
+                       $pageContent .= $this->doc->sectionEnd();
+                       $pageContent .= '</form>';
+
+                       $pageContent .= '<form action="' . BackendUtility::getModuleUrl('tce_file') . '" method="post" name="editform3">';
                        // Create a list of allowed file extensions with the nice format "*.jpg, *.gif" etc.
                        $fileExtList = array();
                        $textFileExt = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], TRUE);
                        foreach ($textFileExt as $fileExt) {
-                               if (!preg_match(('/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] . '/i'), ('.' . $fileExt))) {
+                               if (GeneralUtility::verifyFilenameAgainstDenyPattern($fileExt)) {
                                        $fileExtList[] = '<span class="label label-success">' . strtoupper(htmlspecialchars($fileExt)) . '</span>';
                                }
                        }
@@ -210,13 +256,13 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                                <div class="form-group">
                                        <div class="form-section">
                                                <div class="form-group">
-                                                       <label for="newfile">' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.label_newfile') . ' ' . BackendUtility::cshItem('xMOD_csh_corebe', 'file_newfile') . '</label>
+                                                       <label for="newfile">' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.label_newfile', TRUE) . ' ' . BackendUtility::cshItem('xMOD_csh_corebe', 'file_newfile') . '</label>
                                                        <div class="form-control-wrap">
                                                                <input class="form-control" type="text" id="newfile" name="file[newfile][0][data]" onchange="changed=true;" />
                                                                <input type="hidden" name="file[newfile][0][target]" value="' . htmlspecialchars($this->target) . '" />
                                                        </div>
                                                        <div class="help-block">
-                                                               ' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions') . '<br>
+                                                               ' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions', TRUE) . '<br>
                                                                ' . implode(' ', $fileExtList) . '
                                                        </div>
                                                </div>
@@ -230,7 +276,7 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                                        <input type="hidden" name="redirect" value="' . htmlspecialchars($this->returnUrl) . '" />
                                </div>
                                ';
-                       $pageContent .= $this->doc->section($lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.newfile'), $code);
+                       $pageContent .= $this->doc->section($lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.newfile', TRUE), $code);
                        $pageContent .= $this->doc->sectionEnd();
                        $pageContent .= '</form>';
                }
diff --git a/typo3/sysext/backend/Classes/Controller/OnlineMediaController.php b/typo3/sysext/backend/Classes/Controller/OnlineMediaController.php
new file mode 100644 (file)
index 0000000..a7ccade
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+namespace TYPO3\CMS\Backend\Controller;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Http\AjaxRequestHandler;
+use TYPO3\CMS\Core\Http\ControllerInterface;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Http\Response;
+
+/**
+ * Class OnlineMediaController handles uploading online media
+ */
+class OnlineMediaController implements ControllerInterface {
+
+       /**
+        * @param array $_
+        * @param AjaxRequestHandler $ajaxObj
+        * @return void
+        */
+       public function addAjaxAction($_, AjaxRequestHandler $ajaxObj = NULL) {
+               $ajaxObj->setContentFormat('json');
+
+               $url = GeneralUtility::_POST('url');
+               $targetFolderIdentifier = GeneralUtility::_POST('targetFolder');
+               $allowedExtensions = GeneralUtility::trimExplode(',', GeneralUtility::_POST('allowed') ?: '');
+
+               if (!empty($url)) {
+                       $file = $this->addMediaFromUrl($url, $targetFolderIdentifier, $allowedExtensions);
+                       if ($file !== NULL) {
+                               $ajaxObj->addContent('file', $file->getUid());
+                       } else {
+                               $ajaxObj->addContent('error', $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:online_media.error.invalid_url'));
+                       }
+               }
+       }
+
+       /**
+        * Process add media request
+        *
+        * @param ServerRequestInterface $request
+        * @return ResponseInterface $response
+        */
+       public function processRequest(ServerRequestInterface $request) {
+               $files = $request->getParsedBody()['file'];
+               $newMedia = [];
+               if (isset($files['newMedia'])) {
+                       $newMedia = (array)$files['newMedia'];
+               }
+
+               foreach ($newMedia as $media) {
+                       if (!empty($media['url']) && !empty($media['target'])) {
+                               $allowed = !empty($media['allowed']) ? GeneralUtility::trimExplode(',', $media['allowed']) : [];
+                               $file = $this->addMediaFromUrl($media['url'], $media['target'], $allowed);
+                               if ($file !== NULL) {
+                                       $flashMessage = GeneralUtility::makeInstance(
+                                               FlashMessage::class,
+                                               $file->getName(),
+                                               $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.added'),
+                                               FlashMessage::OK,
+                                               TRUE
+                                       );
+                               } else {
+                                       $flashMessage = GeneralUtility::makeInstance(
+                                               FlashMessage::class,
+                                               $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:online_media.error.invalid_url'),
+                                               $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:online_media.error.new_media.failed'),
+                                               FlashMessage::ERROR,
+                                               TRUE
+                                       );
+                               }
+                               $this->addFlashMessage($flashMessage);
+                       }
+               }
+
+               /** @var Response $response */
+               $response = GeneralUtility::makeInstance(Response::class);
+               $redirect = isset($request->getParsedBody()['redirect']) ? $request->getParsedBody()['redirect'] : $request->getQueryParams()['redirect'];
+               if ($redirect) {
+                       $response = $response
+                               ->withHeader('Location', GeneralUtility::locationHeaderUrl($redirect))
+                               ->withStatus(303);
+               }
+
+               return $response;
+       }
+
+       /**
+        * @param string $url
+        * @param string $targetFolderIdentifier
+        * @param string[] $allowedExtensions
+        * @return File|NULL
+        */
+       protected function addMediaFromUrl($url, $targetFolderIdentifier, array $allowedExtensions = []) {
+               $targetFolder = NULL;
+               if ($targetFolderIdentifier) {
+                       try {
+                               $targetFolder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($targetFolderIdentifier);
+                       } catch (\Exception $e) {
+                               $targetFolder = NULL;
+                       }
+               }
+               if ($targetFolder === NULL) {
+                       $targetFolder = $this->getBackendUser()->getDefaultUploadFolder();
+               }
+               return OnlineMediaHelperRegistry::getInstance()->transformUrlToFile($url, $targetFolder, $allowedExtensions);
+       }
+
+       /**
+        * Add flash message to message queue
+        *
+        * @param FlashMessage $flashMessage
+        * @return void
+        */
+       protected function addFlashMessage(FlashMessage $flashMessage) {
+               /** @var $flashMessageService FlashMessageService */
+               $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+
+               /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
+               $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+               $defaultFlashMessageQueue->enqueue($flashMessage);
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+}
index 4d9ca36..ac3e402 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -28,7 +29,6 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
-use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
@@ -172,6 +172,9 @@ class InlineControlContainer extends AbstractContainer {
                $this->inlineData['nested'][$nameObject] = $this->data['tabAndInlineStack'];
 
                // If relations are required to be unique, get the uids that have already been used on the foreign side of the relation
+               $uniqueMax = 0;
+               $possibleRecords = [];
+               $uniqueIds = [];
                if ($config['foreign_unique']) {
                        // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one:
                        $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_unique']);
@@ -497,6 +500,7 @@ class InlineControlContainer extends AbstractContainer {
         */
        protected function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA) {
                $backendUser = $this->getBackendUserAuthentication();
+               $languageService = $this->getLanguageService();
 
                $config = $PA['fieldConf']['config'];
                ArrayUtility::mergeRecursiveWithOverrule($config, $conf);
@@ -507,9 +511,9 @@ class InlineControlContainer extends AbstractContainer {
                $mode = 'db';
                $showUpload = FALSE;
                if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
-                       $createNewRelationText = $this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle'], TRUE);
+                       $createNewRelationText = $languageService->sL($config['appearance']['createNewRelationLinkTitle'], TRUE);
                } else {
-                       $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', TRUE);
+                       $createNewRelationText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', TRUE);
                }
                if (is_array($config['appearance'])) {
                        if (isset($config['appearance']['elementBrowserType'])) {
@@ -539,8 +543,12 @@ class InlineControlContainer extends AbstractContainer {
                                ' . $this->iconFactory->getIcon('actions-insert-record', Icon::SIZE_SMALL) . '
                                ' . $createNewRelationText . '
                        </a>';
-
                $isDirectFileUploadEnabled = (bool)$this->getBackendUserAuthentication()->uc['edit_docModuleUpload'];
+               $allowedArray = GeneralUtility::trimExplode(',', $allowed, TRUE);
+               $onlineMediaAllowed = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
+               if (!empty($allowedArray)) {
+                       $onlineMediaAllowed = array_intersect($allowedArray, $onlineMediaAllowed);
+               }
                if ($showUpload && $isDirectFileUploadEnabled) {
                        $folder = $backendUser->getDefaultUploadFolder();
                        if (
@@ -557,15 +565,31 @@ class InlineControlContainer extends AbstractContainer {
                                        data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
                                        data-max-file-size="' . htmlspecialchars($maxFileSize) . '"
                                        ><span class="t3-icon t3-icon-actions t3-icon-actions-edit t3-icon-edit-upload">&nbsp;</span>';
-                               $item .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:file_upload.select-and-submit', TRUE);
+                               $item .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:file_upload.select-and-submit', TRUE);
                                $item .= '</a>';
+
+                               if (!empty($onlineMediaAllowed)) {
+                                       $buttonText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.button', TRUE);
+                                       $placeholder = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.placeholder', TRUE);
+                                       $buttonSubmit = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.submit', TRUE);
+                                       $item .= '
+                                               <span class="btn btn-default t3js-online-media-add-btn"
+                                                       data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
+                                                       data-online-media-allowed="' . htmlspecialchars(implode(',', $onlineMediaAllowed)) . '"
+                                                       data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
+                                                       title="' . $buttonText . '"
+                                                       data-btn-submit="' . $buttonSubmit . '"
+                                                       data-placeholder="' . $placeholder . '"
+                                                       >
+                                                       '. $this->iconFactory->getIcon('actions-online-media-add', Icon::SIZE_SMALL) . '
+                                                       ' . $buttonText . '</span>';
+                               }
                        }
                }
 
                $item = '<div class="form-control-wrap">' . $item . '</div>';
                $allowedList = '';
-               $allowedArray = GeneralUtility::trimExplode(',', $allowed, TRUE);
-               $allowedLabel = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions', TRUE);
+               $allowedLabel = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions', TRUE);
                foreach ($allowedArray as $allowedItem) {
                        $allowedList .= '<span class="label label-success">' . strtoupper($allowedItem) . '</span> ';
                }
@@ -627,7 +651,7 @@ class InlineControlContainer extends AbstractContainer {
                                }
                                $item .= '
                                <span class="input-group-btn">
-                                       <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText .'">
+                                       <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText .'">
                                                ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL) . $createNewRelationText . '
                                        </a>
                                </span>';
index 472c9c9..774b7bc 100644 (file)
@@ -246,6 +246,7 @@ class FormResultCompiler {
 
                $pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileListLocalisation');
                $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DragUploader');
+               $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/OnlineMedia');
 
                $pageRenderer->addInlineLanguagelabelFile(
                        \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('lang') . 'locallang_core.xlf',
index 3ddc4b1..0b287a0 100644 (file)
@@ -121,6 +121,12 @@ return [
                'controller' => Controller\File\FileUploadController::class
        ],
 
+       // Add new online media
+       'online_media' => [
+               'path' => '/online-media',
+               'controller' => Controller\OnlineMediaController::class
+       ],
+
        /** DB Records-related routes */
        // Register record history module
        'record_history' => [
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/OnlineMedia.js b/typo3/sysext/backend/Resources/Public/JavaScript/OnlineMedia.js
new file mode 100644 (file)
index 0000000..f48f8ec
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Javascript for show the online media dialog
+ */
+define('TYPO3/CMS/Backend/OnlineMedia', ['jquery', 'nprogress', 'TYPO3/CMS/Lang/Lang', 'TYPO3/CMS/Backend/Modal'], function($, NProgress) {
+       "use strict";
+
+       var OnlineMediaPlugin = function(element) {
+               var me = this;
+               me.$btn = $(element);
+               me.target = me.$btn.data('target-folder');
+               me.irreObjectUid = me.$btn.data('file-irre-object');
+               me.allowed = me.$btn.data('online-media-allowed');
+               me.btnSubmit = me.$btn.data('data-btn-submit') || 'Add';
+               me.placeholder = me.$btn.data('placeholder') || 'Paste media url here...';
+
+               // No IRRE element found then hide input+button
+               if (!me.irreObjectUid) {
+                       me.$btn.hide();
+                       return;
+               }
+
+               me.addOnlineMedia = function(url) {
+                       NProgress.start();
+                       $.post(TYPO3.settings.ajaxUrls['OnlineMedia::add'],
+                               {
+                                       url: url,
+                                       targetFolder: me.target,
+                                       allowed: me.allowed
+                               },
+                               function(data) {
+                                       if (data.file) {
+                                               inline.delayedImportElement(
+                                                       me.irreObjectUid,
+                                                       'sys_file',
+                                                       data.file,
+                                                       'file'
+                                               );
+                                       } else {
+                                               var $confirm = top.TYPO3.Modal.confirm(
+                                                       'ERROR',
+                                                       data.error,
+                                                       top.TYPO3.Severity.error,
+                                                       [{
+                                                               text: TYPO3.lang['button.ok'] || 'OK',
+                                                               btnClass: 'btn-' + top.TYPO3.Modal.getSeverityClass(top.TYPO3.Severity.error),
+                                                               name: 'ok'
+                                                       }]
+                                               ).on('confirm.button.ok', function() {
+                                                       $confirm.trigger('modal-dismiss');
+                                               });
+                                       }
+                                       NProgress.done();
+                               }
+                       );
+               };
+
+               // Bind key press enter event
+               me.$btn.on('click', function(evt) {
+                       evt.preventDefault();
+                       var $modal = top.TYPO3.Modal.show(
+                               me.$btn.attr('title'),
+                               '<div class="form-control-wrap">' +
+                               '<input type="text" class="form-control online-media-url" placeholder="' + me.placeholder + '" />' +
+                               '</div>',
+                               top.TYPO3.Severity.notice,
+                               [{
+                                       text: me.btnSubmit,
+                                       btnClass: 'btn',
+                                       name: 'ok',
+                                       trigger: function() {
+                                               var url = $modal.find('input.online-media-url').val();
+                                               if (url) {
+                                                       $modal.trigger('modal-dismiss');
+                                                       me.addOnlineMedia(url);
+                                               }
+                                       }
+                               }]
+                       );
+
+               });
+       };
+
+       // register the jQuery plugin "OnlineMediaPlugin"
+       $.fn.onlineMedia = function(option) {
+               return this.each(function() {
+                       var $this = $(this),
+                               data = $this.data('OnlineMediaPlugin');
+                       if (!data) {
+                               $this.data('OnlineMediaPlugin', (data = new OnlineMediaPlugin(this)));
+                       }
+                       if (typeof option === 'string') {
+                               data[option]();
+                       }
+               });
+       };
+
+       $(function() {
+               $('.t3js-online-media-add-btn').onlineMedia();
+       });
+});
index fa6e1a3..6e6ae71 100644 (file)
@@ -715,6 +715,12 @@ class IconRegistry implements \TYPO3\CMS\Core\SingletonInterface {
                                'source' => 'EXT:t3skin/images/icons/actions/template-new.png',
                        )
                ),
+               'actions-online-media-add' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'cloud',
+                       )
+               ),
 
                // Extensions
                'extensions-extensionmanager-update-script' => array(
index 3915b72..d75ea3c 100644 (file)
@@ -77,7 +77,7 @@ class FileInfoHook {
                                                        'alt="" class="t3-tceforms-sysfile-imagepreview" />';
                        }
                        $content .= '<strong>' . htmlspecialchars($file->getName()) . '</strong>';
-                       $content .= '(' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($file->getSize())) . 'bytes)<br />';
+                       $content .= ' (' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($file->getSize())) . 'bytes)<br />';
                        $content .= BackendUtility::getProcessedValue('sys_file', 'type', $file->getType()) . ' (' . $file->getMimeType() . ')<br />';
                        $content .= $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xlf:fileMetaDataLocation', TRUE) . ': ';
                        $content .= htmlspecialchars($file->getStorage()->getName()) . ' - ' . htmlspecialchars($file->getIdentifier()) . '<br />';
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/AbstractOEmbedHelper.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/AbstractOEmbedHelper.php
new file mode 100644 (file)
index 0000000..8824398
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class AbstractOEmbedHelper
+ * See http://oembed.com/ for more on OEmbed specification
+ */
+abstract class AbstractOEmbedHelper extends AbstractOnlineMediaHelper {
+
+       /**
+        * @param string $mediaId
+        * @param string $format
+        * @return string
+        */
+       abstract protected function getOEmbedUrl($mediaId, $format = 'json');
+
+       /**
+        * Transform mediaId to File
+        *
+        * @param string $mediaId
+        * @param Folder $targetFolder
+        * @param string $fileExtension
+        * @return File
+        */
+       protected function transformMediaIdToFile($mediaId, Folder $targetFolder, $fileExtension) {
+               $file = $this->findExistingFileByOnlineMediaId($mediaId, $targetFolder, $fileExtension);
+
+               // no existing file create new
+               if ($file === NULL) {
+                       $oEmbed = $this->getOEmbedData($mediaId);
+                       if (!empty($oEmbed)) {
+                               $fileName = $oEmbed['title'] . '.' . $fileExtension;
+                       } else {
+                               $fileName = $mediaId . '.' . $fileExtension;
+                       }
+                       $file = $this->createNewFile($targetFolder, $fileName, $mediaId);
+               }
+               return $file;
+       }
+
+       /**
+        * Get OEmbed data
+        *
+        * @param string $mediaId
+        * @return array|NULL
+        */
+       protected function getOEmbedData($mediaId) {
+               $oEmbed = GeneralUtility::getUrl(
+                       $this->getOEmbedUrl($mediaId)
+               );
+               if ($oEmbed) {
+                       $oEmbed = json_decode($oEmbed, TRUE);
+               }
+               return $oEmbed;
+       }
+
+       /**
+        * Get meta data for OnlineMedia item
+        * Using the meta data from oEmbed
+        *
+        * @param File $file
+        * @return array with metadata
+        */
+       public function getMetaData(File $file) {
+               $metadata = array();
+
+               $oEmbed = $this->getOEmbedData($this->getOnlineMediaId($file));
+
+               if ($oEmbed) {
+                       $metadata['width'] = (int)$oEmbed['width'];
+                       $metadata['height'] = (int)$oEmbed['height'];
+                       if (empty($file->getProperty('title'))) {
+                               $metadata['title'] = strip_tags($oEmbed['title']);
+                       }
+                       $metadata['author'] = $oEmbed['author_name'];
+               }
+
+               return $metadata;
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/AbstractOnlineMediaHelper.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/AbstractOnlineMediaHelper.php
new file mode 100644 (file)
index 0000000..e3a196a
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class AbstractOnlineMediaHelper
+ */
+abstract class AbstractOnlineMediaHelper implements OnlineMediaHelperInterface {
+
+       /**
+        * Cached OnlineMediaIds [fileUid => id]
+        *
+        * @var array
+        */
+       protected $onlineMediaIdCache = array();
+
+       /**
+        * File extension bind to the OnlineMedia helper
+        *
+        * @var string
+        */
+       protected $extension = '';
+
+       /**
+        * Constructor
+        *
+        * @param string $extension file extension bind to the OnlineMedia helper
+        */
+       public function __construct($extension) {
+               $this->extension = $extension;
+       }
+
+       /**
+        * Get Online Media item id
+        *
+        * @param File $file
+        * @return string
+        */
+       public function getOnlineMediaId(File $file) {
+               if (!isset($this->onlineMediaIdCache[$file->getUid()])) {
+                       // By definition these files only contain the ID of the remote media source
+                       $this->onlineMediaIdCache[$file->getUid()] = trim($file->getContents());
+               }
+               return $this->onlineMediaIdCache[$file->getUid()];
+       }
+
+       /**
+        * Search for files with same onlineMediaId by content hash in indexed storage
+        *
+        * @param string $onlineMediaId
+        * @param Folder $targetFolder
+        * @param string $fileExtension
+        * @return File|NULL
+        */
+       protected function findExistingFileByOnlineMediaId($onlineMediaId, Folder $targetFolder, $fileExtension) {
+               $file = NULL;
+               $fileHash = sha1($onlineMediaId);
+               $files = $this->getFileIndexRepository()->findByContentHash($fileHash);
+               if (!empty($files)) {
+                       foreach ($files as $fileIndexEntry) {
+                               if (
+                                       $fileIndexEntry['folder_hash'] === $targetFolder->getHashedIdentifier()
+                                       && (int)$fileIndexEntry['storage'] === $targetFolder->getStorage()->getUid()
+                                       && $fileIndexEntry['extension'] === $fileExtension
+                               ) {
+                                       $file = $this->getResourceFactory()->getFileObject($fileIndexEntry['uid'], $fileIndexEntry);
+                                       break;
+                               }
+                       }
+               }
+               return $file;
+       }
+
+       /**
+        * Create new OnlineMedia item container file
+        *
+        * @param Folder $targetFolder
+        * @param string $fileName
+        * @param string $onlineMediaId
+        * @return File
+        */
+       protected function createNewFile(Folder $targetFolder, $fileName, $onlineMediaId) {
+               $tempFilePath = GeneralUtility::tempnam('online_media');
+               file_put_contents($tempFilePath, $onlineMediaId);
+               return $targetFolder->addFile($tempFilePath, $fileName, 'changeName');
+       }
+
+       /**
+        * Get temporary folder path to save preview images
+        *
+        * @return string
+        */
+       protected function getTempFolderPath() {
+               $path = PATH_site . 'typo3temp/online_media/';
+               if (!is_dir($path)) {
+                       GeneralUtility::mkdir($path);
+               }
+               return $path;
+       }
+
+       /**
+        * Returns an instance of the FileIndexRepository
+        *
+        * @return FileIndexRepository
+        */
+       protected function getFileIndexRepository() {
+               return FileIndexRepository::getInstance();
+       }
+
+       /**
+        * Returns the ResourceFactory
+        *
+        * @return ResourceFactory
+        */
+       protected function getResourceFactory() {
+               return ResourceFactory::getInstance();
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/OnlineMediaHelperInterface.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/OnlineMediaHelperInterface.php
new file mode 100644 (file)
index 0000000..846e7df
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+
+/**
+ * Interface OnlineMediaInterface
+ */
+interface OnlineMediaHelperInterface {
+
+       /**
+        * Constructor
+        *
+        * @param string $extension file extension bind to the OnlineMedia helper
+        */
+       public function __construct($extension);
+
+       /**
+        * Try to transform given URL to a File
+        *
+        * @param string $url
+        * @param Folder $targetFolder
+        * @return File|NULL
+        */
+       public function transformUrlToFile($url, Folder $targetFolder);
+
+       /**
+        * Get Online Media item id
+        *
+        * @param File $file
+        * @return string
+        */
+       public function getOnlineMediaId(File $file);
+
+       /**
+        * Get public url
+        *
+        * Return NULL if you want to use core default behaviour
+        *
+        * @param File $file
+        * @param bool $relativeToCurrentScript
+        * @return string|NULL
+        */
+       public function getPublicUrl(File $file, $relativeToCurrentScript = FALSE);
+
+       /**
+        * Get local absolute file path to preview image
+        *
+        * Return an empty string when no preview image is available
+        *
+        * @param File $file
+        * @return string
+        */
+       public function getPreviewImage(File $file);
+
+       /**
+        * Get meta data for OnlineMedia item
+        *
+        * See $GLOBALS[TCA][sys_file_metadata][columns] for possible fields to fill/use
+        *
+        * @param File $file
+        * @return array with metadata
+        */
+       public function getMetaData(File $file);
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/OnlineMediaHelperRegistry.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/OnlineMediaHelperRegistry.php
new file mode 100644 (file)
index 0000000..5ea4ffc
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Resource\File;
+
+/**
+ * Online Media Source Registry
+ */
+class OnlineMediaHelperRegistry implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * Returns an instance of this class
+        *
+        * @return OnlineMediaHelperRegistry
+        */
+       public static function getInstance() {
+               return GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class);
+       }
+
+       /**
+        * Get helper class for given File
+        *
+        * @param File $file
+        * @return bool|OnlineMediaHelperInterface
+        */
+       public function getOnlineMediaHelper(File $file) {
+               $registeredHelpers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers'];
+               if (isset($registeredHelpers[$file->getExtension()])) {
+                       return GeneralUtility::makeInstance($registeredHelpers[$file->getExtension()], $file->getExtension());
+               }
+               return FALSE;
+       }
+
+       /**
+        * Try to transform given URL to a File
+        *
+        * @param string $url
+        * @param Folder $targetFolder
+        * @param string[] $allowedExtensions
+        * @return File|NULL
+        */
+       public function transformUrlToFile($url, Folder $targetFolder, $allowedExtensions = []) {
+               $registeredHelpers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers'];
+               foreach ($registeredHelpers as $extension => $className) {
+                       if (!empty($allowedExtensions) && !in_array($extension, $allowedExtensions, TRUE)) {
+                               continue;
+                       }
+                       /** @var OnlineMediaHelperInterface $helper */
+                       $helper = GeneralUtility::makeInstance($className, $extension);
+                       $file = $helper->transformUrlToFile($url, $targetFolder);
+                       if ($file !== NULL) {
+                               return $file;
+                       }
+               }
+               return NULL;
+       }
+
+       /**
+        * Get all file extensions that have a OnlineMediaHelper
+        *
+        * @return string[]
+        */
+       public function getSupportedFileExtensions() {
+               return array_keys($GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers']);
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/VimeoHelper.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/VimeoHelper.php
new file mode 100644 (file)
index 0000000..23b1814
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Vimeo helper class
+ */
+class VimeoHelper extends AbstractOEmbedHelper {
+
+       /**
+        * Get public url
+        * Return NULL if you want to use core default behaviour
+        *
+        * @param File $file
+        * @param bool $relativeToCurrentScript
+        * @return string|NULL
+        */
+       public function getPublicUrl(File $file, $relativeToCurrentScript = FALSE) {
+               $videoId = $this->getOnlineMediaId($file);
+               return sprintf('https://vimeo.com/%s', $videoId);
+       }
+
+       /**
+        * Get local absolute file path to preview image
+        *
+        * @param File $file
+        * @return string
+        */
+       public function getPreviewImage(File $file) {
+               $videoId = $this->getOnlineMediaId($file);
+               $temporaryFileName = $this->getTempFolderPath() . 'vimeo_' . md5($videoId) . '.jpg';
+               if (!file_exists($temporaryFileName)) {
+                       $oEmbedData = $this->getOEmbedData($videoId);
+                       $previewImage = GeneralUtility::getUrl($oEmbedData['thumbnail_url']);
+                       if ($previewImage !== FALSE) {
+                               file_put_contents($temporaryFileName, $previewImage);
+                               GeneralUtility::fixPermissions($temporaryFileName);
+                       }
+               }
+               return $temporaryFileName;
+       }
+
+       /**
+        * Try to transform given URL to a File
+        *
+        * @param string $url
+        * @param Folder $targetFolder
+        * @return File|NULL
+        */
+       public function transformUrlToFile($url, Folder $targetFolder) {
+               $videoId = NULL;
+               // Try to get the Vimeo code from given url.
+               // Next formats are supported with and without http(s)://
+               // - vimeo.com/<code> # Share URL
+               // - player.vimeo.com/video/<code> # URL form iframe embed code, can also get code from full iframe snippet
+               if (preg_match('/vimeo\.com\/(video\/)*([0-9]+)/i', $url, $matches)) {
+                       $videoId = $matches[2];
+               }
+               if (empty($videoId)) {
+                       return NULL;
+               }
+               return $this->transformMediaIdToFile($videoId, $targetFolder, $this->extension);
+       }
+
+       /**
+        * Get oEmbed data url
+        *
+        * @param string $mediaId
+        * @param string $format
+        * @return string
+        */
+       protected function getOEmbedUrl($mediaId, $format = 'json') {
+               return sprintf(
+                       'https://vimeo.com/api/oembed.%s?url=%s',
+                       urlencode($format),
+                       urlencode(sprintf('https://vimeo.com/%s', $mediaId))
+               );
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/YouTubeHelper.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Helpers/YouTubeHelper.php
new file mode 100644 (file)
index 0000000..45db4a6
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * SlideShare helper class
+ */
+class YouTubeHelper extends AbstractOEmbedHelper {
+
+       /**
+        * Get public url
+        *
+        * @param File $file
+        * @param bool $relativeToCurrentScript
+        * @return string|NULL
+        */
+       public function getPublicUrl(File $file, $relativeToCurrentScript = FALSE) {
+               $videoId = $this->getOnlineMediaId($file);
+               return sprintf('https://www.youtube.com/watch?v=%s', $videoId);
+       }
+
+       /**
+        * Get local absolute file path to preview image
+        *
+        * @param File $file
+        * @return string
+        */
+       public function getPreviewImage(File $file) {
+               $videoId = $this->getOnlineMediaId($file);
+               $temporaryFileName = $this->getTempFolderPath() . 'youtube_' . md5($videoId) . '.jpg';
+
+               if (!file_exists($temporaryFileName)) {
+                       $previewImage = GeneralUtility::getUrl(
+                               sprintf('https://img.youtube.com/vi/%s/0.jpg', $videoId)
+                       );
+                       if ($previewImage !== FALSE) {
+                               file_put_contents($temporaryFileName, $previewImage);
+                               GeneralUtility::fixPermissions($temporaryFileName);
+                       }
+               }
+
+               return $temporaryFileName;
+       }
+
+       /**
+        * Try to transform given URL to a File
+        *
+        * @param string $url
+        * @param Folder $targetFolder
+        * @return File|NULL
+        */
+       public function transformUrlToFile($url, Folder $targetFolder) {
+               $videoId = NULL;
+               // Try to get the YouTube code from given url.
+               // These formats are supported with and without http(s)://
+               // - youtu.be/<code> # Share URL
+               // - www.youtube.com/watch?v=<code> # Normal web link
+               // - www.youtube.com/v/<code>
+               // - www.youtube-nocookie.com/v/<code> # youtube-nocookie.com web link
+               // - www.youtube.com/embed/<code> # URL form iframe embed code, can also get code from full iframe snippet
+               if (preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)) {
+                       $videoId = $match[1];
+               }
+               if (empty($videoId)) {
+                       return NULL;
+               }
+               return $this->transformMediaIdToFile($videoId, $targetFolder, $this->extension);
+       }
+
+       /**
+        * Get oEmbed url to retrieve oEmbed data
+        *
+        * @param string $mediaId
+        * @param string $format
+        * @return string
+        */
+       protected function getOEmbedUrl($mediaId, $format = 'json') {
+               return sprintf('https://www.youtube.com/oembed?url=%s&format=%s',
+                       urlencode(sprintf('https://www.youtube.com/watch?v=%s', $mediaId)),
+                       rawurlencode($format)
+               );
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Metadata/Extractor.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Metadata/Extractor.php
new file mode 100644 (file)
index 0000000..6c2e526
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Metadata;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\Index\ExtractorInterface;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Core\Resource\File;
+
+/**
+ * Class Extractor
+ */
+class Extractor implements ExtractorInterface {
+
+       /**
+        * Returns an array of supported file types
+        *
+        * @return array
+        */
+       public function getFileTypeRestrictions() {
+               return array();
+       }
+
+       /**
+        * Get all supported DriverClasses
+        * empty array indicates no restrictions
+        *
+        * @return array
+        */
+       public function getDriverRestrictions() {
+               return array();
+       }
+
+       /**
+        * Returns the data priority of the extraction Service
+        *
+        * @return int
+        */
+       public function getPriority() {
+               return 10;
+       }
+
+       /**
+        * Returns the execution priority of the extraction Service
+        *
+        * @return int
+        */
+       public function getExecutionPriority() {
+               return 10;
+       }
+
+       /**
+        * Checks if the given file can be processed by this Extractor
+        *
+        * @param File $file
+        * @return bool
+        */
+       public function canProcess(File $file) {
+               return OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($file) !== FALSE;
+       }
+
+       /**
+        * The actual processing TASK
+        * Should return an array with database properties for sys_file_metadata to write
+        *
+        * @param File $file
+        * @param array $previousExtractedData optional, contains the array of already extracted data
+        * @return array
+        */
+       public function extractMetaData(File $file, array $previousExtractedData = array()) {
+               /** @var OnlineMediaHelperInterface $helper */
+               $helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($file);
+               return $helper !== FALSE ? $helper->getMetaData($file) : [];
+       }
+}
diff --git a/typo3/sysext/core/Classes/Resource/OnlineMedia/Processing/PreviewProcessing.php b/typo3/sysext/core/Classes/Resource/OnlineMedia/Processing/PreviewProcessing.php
new file mode 100644 (file)
index 0000000..369283c
--- /dev/null
@@ -0,0 +1,248 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\OnlineMedia\Processing;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
+use TYPO3\CMS\Core\Resource\Driver\AbstractDriver;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Resource\ProcessedFileRepository;
+use TYPO3\CMS\Core\Resource\Processing\LocalImageProcessor;
+use TYPO3\CMS\Core\Resource\Service\FileProcessingService;
+use TYPO3\CMS\Core\Utility\CommandUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Frontend\Imaging\GifBuilder;
+
+/**
+ * Preview of Online Media item Processing
+ */
+class PreviewProcessing {
+
+       /**
+        * @var LocalImageProcessor
+        */
+       protected $processor;
+
+       /**
+        * @param ProcessedFile $processedFile
+        * @return bool
+        */
+       protected function needsReprocessing($processedFile) {
+               return $processedFile->isNew()
+                       || (!$processedFile->usesOriginalFile() && !$processedFile->exists())
+                       || $processedFile->isOutdated();
+       }
+
+       /**
+        * Process file
+        * Create static image preview for Online Media item when possible
+        *
+        * @param FileProcessingService $fileProcessingService
+        * @param AbstractDriver $driver
+        * @param ProcessedFile $processedFile
+        * @param File $file
+        * @param string $taskType
+        * @param array $configuration
+        */
+       public function processFile(FileProcessingService $fileProcessingService, AbstractDriver $driver, ProcessedFile $processedFile, File $file, $taskType, array $configuration) {
+               if ($taskType !== 'Image.Preview' && $taskType !== 'Image.CropScaleMask') {
+                       return;
+               }
+               // Check if processing is needed
+               if (!$this->needsReprocessing($processedFile)) {
+                       return;
+               }
+               // Check if there is a OnlineMediaHelper registered for this file type
+               $helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($file);
+               if ($helper === FALSE) {
+                       return;
+               }
+               // Check if helper provides a preview image
+               $temporaryFileName = $helper->getPreviewImage($file);
+               if (empty($temporaryFileName) || !file_exists($temporaryFileName)) {
+                       return;
+               }
+               $temporaryFileNameForResizedThumb = uniqid(PATH_site . 'typo3temp/online_media_' . $file->getHashedIdentifier()) . '.jpg';
+               switch ($taskType) {
+                       case 'Image.Preview':
+                               // Merge custom configuration with default configuration
+                               $configuration = array_merge(array('width' => 64, 'height' => 64), $configuration);
+                               $configuration['width'] = MathUtility::forceIntegerInRange($configuration['width'], 1, 1000);
+                               $configuration['height'] = MathUtility::forceIntegerInRange($configuration['height'], 1, 1000);
+                               $this->resizeImage($temporaryFileName, $temporaryFileNameForResizedThumb, $configuration);
+                               break;
+
+                       case 'Image.CropScaleMask':
+                               $this->cropScaleImage($temporaryFileName, $temporaryFileNameForResizedThumb, $configuration);
+                               break;
+               }
+               if (is_file($temporaryFileNameForResizedThumb)) {
+                       $processedFile->setName($this->getTargetFileName($processedFile));
+                       list($width, $height) = getimagesize($temporaryFileNameForResizedThumb);
+                       $processedFile->updateProperties(
+                               array(
+                                       'width' => $width,
+                                       'height' => $height,
+                                       'size' => filesize($temporaryFileNameForResizedThumb),
+                                       'checksum' => $processedFile->getTask()->getConfigurationChecksum()
+                               )
+                       );
+                       $processedFile->updateWithLocalFile($temporaryFileNameForResizedThumb);
+
+                       /** @var ProcessedFileRepository $processedFileRepository */
+                       $processedFileRepository = GeneralUtility::makeInstance(ProcessedFileRepository::class);
+                       $processedFileRepository->add($processedFile);
+               }
+       }
+
+       /**
+        * @param ProcessedFile $processedFile
+        * @param string $prefix
+        * @return string
+        */
+       protected function getTargetFileName(ProcessedFile $processedFile, $prefix = 'preview_') {
+               return $prefix . $processedFile->getTask()->getConfigurationChecksum() . '_' . $processedFile->getOriginalFile()->getNameWithoutExtension() . '.jpg';
+       }
+
+       /**
+        * @param string $originalFileName
+        * @param string $temporaryFileName
+        * @param array $configuration
+        */
+       protected function resizeImage($originalFileName, $temporaryFileName, $configuration) {
+               // Create the temporary file
+               if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['im'])) {
+                       return;
+               }
+
+               if (file_exists($originalFileName)) {
+                       $arguments = CommandUtility::escapeShellArguments([
+                               'width' => $configuration['width'],
+                               'height' => $configuration['height'],
+                               'originalFileName' => $originalFileName,
+                               'temporaryFileName' => $temporaryFileName,
+                       ]);
+                       $parameters = '-sample ' . $arguments['width'] . 'x' . $arguments['height'] . ' '
+                               . $arguments['originalFileName'] . '[0] ' . $arguments['temporaryFileName'];
+
+                       $cmd = GeneralUtility::imageMagickCommand('convert', $parameters) . ' 2>&1';
+                       CommandUtility::exec($cmd);
+               }
+
+               if (!file_exists($temporaryFileName)) {
+                       // Create a error image
+                       $graphicalFunctions = $this->getGraphicalFunctionsObject();
+                       $graphicalFunctions->getTemporaryImageWithText($temporaryFileName, 'No thumb', 'generated!', basename($originalFileName));
+               }
+       }
+
+       /**
+        * cropScaleImage
+        *
+        * @param string $originalFileName
+        * @param string $temporaryFileName
+        * @param array $configuration
+        */
+       protected function cropScaleImage($originalFileName, $temporaryFileName, $configuration) {
+               if (file_exists($originalFileName)) {
+                       /** @var $gifBuilder GifBuilder */
+                       $gifBuilder = GeneralUtility::makeInstance(GifBuilder::class);
+                       $gifBuilder->init();
+
+                       $options = $this->getConfigurationForImageCropScaleMask($configuration, $gifBuilder);
+                       $info = $gifBuilder->getImageDimensions($originalFileName);
+                       $data = $gifBuilder->getImageScale($info, $configuration['width'], $configuration['height'], $options);
+
+                       $info[0] = $data[0];
+                       $info[1] = $data[1];
+                       $frame = '';
+                       $params = $gifBuilder->cmds['jpg'];
+
+                       // Cropscaling:
+                       if ($data['crs']) {
+                               if (!$data['origW']) {
+                                       $data['origW'] = $data[0];
+                               }
+                               if (!$data['origH']) {
+                                       $data['origH'] = $data[1];
+                               }
+                               $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
+                               $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
+                               $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! ';
+                       }
+                       $command = $gifBuilder->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
+                       $gifBuilder->imageMagickExec($originalFileName, $temporaryFileName, $command, $frame);
+               }
+               if (!file_exists($temporaryFileName)) {
+                       // Create a error image
+                       $graphicalFunctions = $this->getGraphicalFunctionsObject();
+                       $graphicalFunctions->getTemporaryImageWithText($temporaryFileName, 'No thumb', 'generated!', basename($originalFileName));
+               }
+       }
+
+       /**
+        * Get configuration for ImageCropScaleMask processing
+        *
+        * @param array $configuration
+        * @param GifBuilder $gifBuilder
+        * @return array
+        */
+       protected function getConfigurationForImageCropScaleMask(array $configuration, GifBuilder $gifBuilder) {
+               if (!empty($configuration['useSample'])) {
+                       $gifBuilder->scalecmd = '-sample';
+               }
+               $options = array();
+               if (!empty($configuration['maxWidth'])) {
+                       $options['maxW'] = $configuration['maxWidth'];
+               }
+               if (!empty($configuration['maxHeight'])) {
+                       $options['maxH'] = $configuration['maxHeight'];
+               }
+               if (!empty($configuration['minWidth'])) {
+                       $options['minW'] = $configuration['minWidth'];
+               }
+               if (!empty($configuration['minHeight'])) {
+                       $options['minH'] = $configuration['minHeight'];
+               }
+
+               $options['noScale'] = $configuration['noScale'];
+
+               return $options;
+       }
+
+       /**
+        * @return LocalImageProcessor
+        */
+       protected function getProcessor() {
+               if (!$this->processor) {
+                       $this->processor = GeneralUtility::makeInstance(LocalImageProcessor::class);
+               }
+               return $this->processor;
+       }
+
+       /**
+        * @return GraphicalFunctions
+        */
+       protected function getGraphicalFunctionsObject() {
+               static $graphicalFunctionsObject = NULL;
+               if ($graphicalFunctionsObject === NULL) {
+                       $graphicalFunctionsObject = GeneralUtility::makeInstance(GraphicalFunctions::class);
+               }
+               return $graphicalFunctionsObject;
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/Rendering/VimeoRenderer.php b/typo3/sysext/core/Classes/Resource/Rendering/VimeoRenderer.php
new file mode 100644 (file)
index 0000000..3b41b95
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Rendering;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\FileReference;
+
+/**
+ * Vimeo renderer class
+ */
+class VimeoRenderer implements FileRendererInterface {
+
+       /**
+        * @var OnlineMediaHelperInterface
+        */
+       protected $onlineMediaHelper;
+
+       /**
+        * Returns the priority of the renderer
+        * This way it is possible to define/overrule a renderer
+        * for a specific file type/context.
+        * For example create a video renderer for a certain storage/driver type.
+        * Should be between 1 and 100, 100 is more important than 1
+        *
+        * @return int
+        */
+       public function getPriority() {
+               return 1;
+       }
+
+       /**
+        * Check if given File(Reference) can be rendered
+        *
+        * @param FileInterface $file File of FileReference to render
+        * @return bool
+        */
+       public function canRender(FileInterface $file) {
+               return ($file->getMimeType() === 'video/vimeo' || $file->getExtension() === 'vimeo') && $this->getOnlineMediaHelper($file) !== FALSE;
+       }
+
+       /**
+        * Get online media helper
+        *
+        * @param FileInterface $file
+        * @return bool|OnlineMediaHelperInterface
+        */
+       protected function getOnlineMediaHelper(FileInterface $file) {
+               if ($this->onlineMediaHelper === NULL) {
+                       $orgFile = $file;
+                       if ($orgFile instanceof FileReference) {
+                               $orgFile = $orgFile->getOriginalFile();
+                       }
+                       if ($orgFile instanceof File) {
+                               $this->onlineMediaHelper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($orgFile);
+                       } else {
+                               $this->onlineMediaHelper = FALSE;
+                       }
+               }
+               return $this->onlineMediaHelper;
+       }
+
+       /**
+        * Render for given File(Reference) html output
+        *
+        * @param FileInterface $file
+        * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
+        * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
+        * @param array $options
+        * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl()
+        * @return string
+        */
+       public function render(FileInterface $file, $width, $height, array $options = NULL, $usedPathsRelativeToCurrentScript = FALSE) {
+
+               if ($file instanceof FileReference) {
+                       $autoplay = $file->getProperty('autoplay');
+                       if ($autoplay !== NULL) {
+                               $options['autoplay'] = $autoplay;
+                       }
+               }
+
+               $urlParams = array();
+               if (!empty($options['autoplay'])) {
+                       $urlParams[] = 'autoplay=1';
+               }
+               if (!empty($options['loop'])) {
+                       $urlParams[] = 'loop=1';
+               }
+               $urlParams[] = 'title=' . (int)!empty($options['showinfo']);
+               $urlParams[] = 'byline=' . (int)!empty($options['showinfo']);
+               $urlParams[] = 'portrait=0';
+
+               if ($file instanceof FileReference) {
+                       $orgFile = $file->getOriginalFile();
+               } else {
+                       $orgFile = $file;
+               }
+
+               $videoId = $this->getOnlineMediaHelper($file)->getOnlineMediaId($orgFile);
+               $attributes = array(
+                       'src' => sprintf('//player.vimeo.com/video/%s?%s', $videoId, implode('&amp;', $urlParams)),
+               );
+               $width = (int)$width;
+               if (!empty($width)) {
+                       $attributes['width'] = $width;
+               }
+               $height = (int)$height;
+               if (!empty($height)) {
+                       $attributes['height'] = $height;
+               }
+               if (is_object($GLOBALS['TSFE']) && $GLOBALS['TSFE']->config['config']['doctype'] !== 'html5') {
+                       $attributes['frameborder'] = '0';
+               }
+               $output = '';
+               foreach ($attributes as $key => $value) {
+                       $output .= $key . '="' . $value . '" ';
+               }
+
+               // wrap in div so you can make it responsive
+               return '<div class="video-container"><iframe ' . $output . 'allowfullscreen></iframe></div>';
+       }
+
+}
diff --git a/typo3/sysext/core/Classes/Resource/Rendering/YouTubeRenderer.php b/typo3/sysext/core/Classes/Resource/Rendering/YouTubeRenderer.php
new file mode 100644 (file)
index 0000000..a7c6b01
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Rendering;
+
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\FileReference;
+
+/**
+ * YouTube renderer class
+ */
+class YouTubeRenderer implements FileRendererInterface {
+
+       /**
+        * @var OnlineMediaHelperInterface
+        */
+       protected $onlineMediaHelper;
+
+       /**
+        * Returns the priority of the renderer
+        * This way it is possible to define/overrule a renderer
+        * for a specific file type/context.
+        * For example create a video renderer for a certain storage/driver type.
+        * Should be between 1 and 100, 100 is more important than 1
+        *
+        * @return int
+        */
+       public function getPriority() {
+               return 1;
+       }
+
+       /**
+        * Check if given File(Reference) can be rendered
+        *
+        * @param FileInterface $file File of FileReference to render
+        * @return bool
+        */
+       public function canRender(FileInterface $file) {
+               return ($file->getMimeType() === 'video/youtube' || $file->getExtension() === 'youtube') && $this->getOnlineMediaHelper($file) !== FALSE;
+       }
+
+       /**
+        * Get online media helper
+        *
+        * @param FileInterface $file
+        * @return bool|OnlineMediaHelperInterface
+        */
+       protected function getOnlineMediaHelper(FileInterface $file) {
+               if ($this->onlineMediaHelper === NULL) {
+                       $orgFile = $file;
+                       if ($orgFile instanceof FileReference) {
+                               $orgFile = $orgFile->getOriginalFile();
+                       }
+                       if ($orgFile instanceof File) {
+                               $this->onlineMediaHelper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($orgFile);
+                       } else {
+                               $this->onlineMediaHelper = FALSE;
+                       }
+               }
+               return $this->onlineMediaHelper;
+       }
+
+       /**
+        * Render for given File(Reference) html output
+        *
+        * @param FileInterface $file
+        * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
+        * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
+        * @param array $options
+        * @param bool $usedPathsRelativeToCurrentScript See $file->getPublicUrl()
+        * @return string
+        */
+       public function render(FileInterface $file, $width, $height, array $options = NULL, $usedPathsRelativeToCurrentScript = FALSE) {
+
+               if ($file instanceof FileReference) {
+                       $autoplay = $file->getProperty('autoplay');
+                       if ($autoplay !== NULL) {
+                               $options['autoplay'] = $autoplay;
+                       }
+               }
+
+               $urlParams = array('autohide=1');
+               if (!isset($options['controls']) || !empty($options['controls'])) {
+                       $urlParams[] = 'controls=2';
+               }
+               if (!empty($options['autoplay'])) {
+                       $urlParams[] = 'autoplay=1';
+               }
+               if (!empty($options['loop'])) {
+                       $urlParams[] = 'loop=1';
+               }
+               if (!isset($options['enablejsapi']) || !empty($options['enablejsapi'])) {
+                       $urlParams[] = 'enablejsapi=1&amp;origin=' . GeneralUtility::getIndpEnv('HTTP_HOST');
+               }
+               $urlParams[] = 'showinfo=' . (int)!empty($options['showinfo']);
+
+               if ($file instanceof FileReference) {
+                       $orgFile = $file->getOriginalFile();
+               } else {
+                       $orgFile = $file;
+               }
+
+               $videoId = $this->getOnlineMediaHelper($file)->getOnlineMediaId($orgFile);
+               $attributes = array(
+                       'src' => sprintf(
+                               '//www.youtube%s.com/embed/%s?%s',
+                               !empty($options['no-cookie']) ? '-nocookie' : '',
+                               $videoId,
+                               implode('&amp;', $urlParams)
+                       ),
+               );
+
+               $width = (int)$width;
+               if (!empty($width)) {
+                       $attributes['width'] = $width;
+               }
+               $height = (int)$height;
+               if (!empty($height)) {
+                       $attributes['height'] = $height;
+               }
+               if (is_object($GLOBALS['TSFE']) && $GLOBALS['TSFE']->config['config']['doctype'] !== 'html5') {
+                       $attributes['frameborder'] = '0';
+               }
+               $output = '';
+               foreach ($attributes as $key => $value) {
+                       $output .= $key . '="' . $value . '" ';
+               }
+
+               // wrap in div so you can make it responsive
+               return '<div class="video-container"><iframe ' . $output . 'allowfullscreen></iframe></div>';
+       }
+
+}
index c9db178..37b5500 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Core\Resource;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
 use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
 use TYPO3\CMS\Core\Resource\Index\Indexer;
@@ -1215,6 +1216,14 @@ class ResourceStorage implements ResourceStorageInterface {
                        // Pre-process the public URL by an accordant slot
                        $this->emitPreGeneratePublicUrlSignal($resourceObject, $relativeToCurrentScript, array('publicUrl' => &$publicUrl));
 
+                       if (
+                               $publicUrl === NULL
+                               && $resourceObject instanceof File
+                               && ($helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($resourceObject)) !== FALSE
+                       ) {
+                               $publicUrl = $helper->getPublicUrl($resourceObject, $relativeToCurrentScript);
+                       }
+
                        // If slot did not handle the signal, use the default way to determine public URL
                        if ($publicUrl === NULL) {
 
index de64bd7..0f1615d 100644 (file)
@@ -85,8 +85,8 @@ return array(
                 * @deprecated since 4.6 - will be removed in 6.2.
                 */
                'curlTimeout' => 0,                                             // Integer: Timeout value for cURL requests in seconds. 0 means to wait indefinitely. Deprecated since 4.6 - will be removed in 6.2. See below for http options.
-               'textfile_ext' => 'txt,ts,typoscript,html,htm,css,tmpl,js,sql,xml,csv,xlf',             // Text file extensions. Those that can be edited. Executable PHP files may not be editable in webspace if disallowed!
-               'mediafile_ext' => 'gif,jpg,jpeg,bmp,png,pdf,svg,ai,mov,avi',// Commalist of file extensions perceived as media files by TYPO3. Lowercase and no spaces between!
+               'textfile_ext' => 'txt,ts,typoscript,html,htm,css,tmpl,js,sql,xml,csv,xlf',     // Text file extensions. Those that can be edited. Executable PHP files may not be editable in webspace if disallowed!
+               'mediafile_ext' => 'gif,jpg,jpeg,bmp,png,pdf,svg,ai,mov,avi,youtube,vimeo',     // Commalist of file extensions perceived as media files by TYPO3. Lowercase and no spaces between!
                'binPath' => '',                                                // String: List of absolute paths where external programs should be searched for. Eg. <code>/usr/local/webbin/,/home/xyz/bin/</code>. (ImageMagick path have to be configured separately)
                'binSetup' => '',                                               // String (textarea): List of programs (separated by newline or comma). By default programs will be searched in default paths and the special paths defined by 'binPath'. When PHP has openbasedir enabled the programs can not be found and have to be configured here. Example: <code>perl=/usr/bin/perl,unzip=/usr/local/bin/unzip</code>
                't3lib_cs_convMethod' => '',                    // String (values: "iconv", "recode", "mbstring", default is homemade PHP-code). Defines which of these PHP-features to use for various charset conversion functions in \TYPO3\CMS\Core\Charset\CharsetConverter. Will speed up charset conversion radically.
@@ -257,14 +257,20 @@ return array(
                                'static' => \TYPO3\CMS\Core\Resource\Collection\StaticFileCollection::class,
                                'folder' => \TYPO3\CMS\Core\Resource\Collection\FolderBasedFileCollection::class,
                                'category' => \TYPO3\CMS\Core\Resource\Collection\CategoryBasedFileCollection::class,
-                       )
+                       ),
+                       'onlineMediaHelpers' => array(
+                               'youtube' => \TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\YouTubeHelper::class,
+                               'vimeo' => \TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\VimeoHelper::class,
+                       ),
                ),
                'FileInfo' => array(
                        // Static mapping for file extensions to mime types.
                        // In special cases the mime type is not detected correctly.
                        // Use this array only if the automatic detection does not work correct!
                        'fileExtensionToMimeType' => array(
-                               'svg' => 'image/svg+xml'
+                               'svg' => 'image/svg+xml',
+                               'youtube' => 'video/youtube',
+                               'vimeo' => 'video/vimeo',
                        )
                ),
                'livesearch' => array(),        // Array: keywords used for commands to search for specific tables
@@ -954,6 +960,10 @@ return array(
                        'LiveSearch' => array(
                                'callbackMethod' => \TYPO3\CMS\Backend\Controller\LiveSearchController::class . '->liveSearchAction',
                                'csrfTokenCheck' => TRUE
+                       ),
+                       'OnlineMedia::add' => array(
+                               'callbackMethod' => \TYPO3\CMS\Backend\Controller\OnlineMediaController::class . '->addAjaxAction',
+                               'csrfTokenCheck' => TRUE
                        )
                ),
                'toolbarItems' => array(), // Array: Registered toolbar items classes
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-61799-ImprovedHandlingOfOnlineMedia.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-61799-ImprovedHandlingOfOnlineMedia.rst
new file mode 100644 (file)
index 0000000..f091dc1
--- /dev/null
@@ -0,0 +1,111 @@
+===================================================
+Feature: #61799 - Improved handling of online media
+===================================================
+
+Description
+===========
+
+Editors can now use YouTube and Vimeo videos (online media) just like a any other file. Organising them just like any
+other file in the file list and selecting them in element browser to use in a CE or any other record.
+Adding new online media files is done providing the URL to online media. The matching helper class will fetch the
+needed metadata and when available supply an image that will be used as preview.
+
+YouTube and Vimeo support
+-------------------------
+
+To core provides an ``OnlineMediaHelper`` and a ``FileRenderer`` class for YouTube and Vimeo.
+
+Adding YouTube videos can be done by providing a URL in one of the following formats (with and without http(s)://):
+
+- youtu.be/<code> # Share URL
+- www.youtube.com/watch?v=<code> # Normal web link
+- www.youtube.com/v/<code>
+- www.youtube-nocookie.com/v/<code> # youtube-nocookie.com web link
+- www.youtube.com/embed/<code> # URL form iframe embed code, can also get code from full iframe snippet
+
+Adding Vimeo videos can be done by providing a URL in one of the following formats (with and without http(s)://):
+
+- vimeo.com/<code> # Share URL
+- player.vimeo.com/video/<code> # URL form iframe embed code, can also get code from full iframe snippet
+
+
+Each renderer has some custom configuration options:
+
+**YouTubeRenderer:**
+
+* ``bool autoplay`` default = FALSE; when set video starts immediately after loading of the page
+* ``int controls`` default = 2; see `https://developers.google.com/youtube/player_parameters#controls`_
+* ``bool loop`` default = FALSE; if set video starts over again from te beginning when finished
+* ``bool enablejsapi`` default = TRUE; see `https://developers.google.com/youtube/player_parameters#enablejsapi`_
+* ``bool showinfo`` default = FALSE; show video title and uploader before video starts playing
+* ``bool no-cookie`` default = FALSE; use domain youtube-nocookie.com instead of youtube.com when embedding a video
+
+Example of setting the YouTubeRenderer options with the MediaViewHelper:
+
+.. code-block:: html
+
+    <!-- enable js api and set no-cookie support for YouTube videos -->
+    <f:media file="{file}" additionalConfig="{enablejsapi:1, 'no-cookie': true}" />
+
+
+**VimeoRenderer:**
+
+* ``bool autoplay`` default = FALSE; when set video starts immediately after loading of the page
+* ``bool loop`` default = FALSE; if set video starts over again from te beginning when finished
+* ``bool showinfo`` default = FALSE; show video title and uploader before video starts playing
+
+Example of setting the YouTubeRenderer options with the MediaViewHelper:
+
+.. code-block:: html
+
+    <!-- show title and uploader for YouTube and Vimeo before video starts playing -->
+    <f:media file="{file}" additionalConfig="{showinfo:1}" />
+
+
+Register your own online media service
+--------------------------------------
+
+For every service you need a ``OnlineMediaHelper`` class that implements ``OnlineMediaHelperInterface`` and a
+``FileRenderer`` class (see #61800) that implements ``FileRendererInterface``. The online media helper is responsible
+for translating the input given by the editor to a ``onlineMediaId`` that is known to the service. And the renderer is
+responsible for turning the ``onlineMediaId`` to the correct HTML output to show the media item.
+
+The ``onlineMediaId`` is stored in a plain text file that only holds this ID. By giving this file a custom file extension
+TYPO3 knows which ``OnlineMediaHelper`` and ``FileRenderer`` belong to it. To further tell TYPO3 what kind of
+"file" (text, image, audio, video, application, other) this online media we also need to bind a custom mime-type to
+this file extension.
+
+With adding this custom file extension to ``$GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']`` (see `#69543 <Feature-69543-IntroducedGLOBALSTYPO3_CONF_VARSSYSmediafile_ext.rst>`_) your custom
+online media file can be used throughout the backend every where all media files are allowed.
+
+*Example of registering your own online media file/service:*
+
+.. code-block:: php
+
+    // Register your own online video service (the used key is also the bind file extension name)
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['OnlineMediaHelpers']['myvideo'] = \MyCompany\Myextension\Helpers\MyVideoHelper::class;
+
+    $rendererRegistry = \TYPO3\CMS\Core\Resource\Rendering\RendererRegistry::getInstance();
+    $rendererRegistry->registerRendererClass(
+        'MyCompany\\Myextension\\Rendering\\MyVideoRenderer'
+    );
+
+    // Register an custom mime-type for your videos
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['FileInfo']['fileExtensionToMimeType']['myvideo'] = 'video/myvideo';
+
+    // Register your custom file extension as allowed media file
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] .= ',myvideo';
+
+
+Override by core provided Helper class with your own helper class
+-----------------------------------------------------------------
+
+The helper classed provided by core use the ``oEmbed`` web service provided by YouTube and Vimeo to gather some basic
+metadata for the provided video urls. The upside is that you do not need an API user/key to use their webservice as these
+services are publicly available. But the downside is that the gathered info is kind of spare. So if you have a API user/key
+for there services, you could create an own helper class which provides more meta data.
+
+.. code-block:: php
+
+    // Register your own online custom youtube helper class
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['OnlineMediaHelpers']['youtube'] = \MyCompany\Myextension\Helpers\YouTubeHelper::class;
diff --git a/typo3/sysext/core/Tests/Unit/Resource/Rendering/VimeoRendererTest.php b/typo3/sysext/core/Tests/Unit/Resource/Rendering/VimeoRendererTest.php
new file mode 100644 (file)
index 0000000..cdca19c
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Resource\Rendering;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\VimeoHelper;
+use TYPO3\CMS\Core\Resource\Rendering\VimeoRenderer;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+
+/**
+ * Class VimeoRendererTest
+ */
+class VimeoRendererTest extends UnitTestCase {
+
+       /**
+        * @var VimeoRenderer|\PHPUnit_Framework_MockObject_MockObject
+        */
+       protected $subject;
+
+       /**
+        * Set up the test
+        */
+       protected function setUp() {
+               parent::setUp();
+
+               /** @var VimeoHelper|\PHPUnit_Framework_MockObject_MockObject $vimeoHelper */
+               $vimeoHelper = $this->getAccessibleMock(VimeoHelper::class, array('getOnlineMediaId'), array('vimeo'));
+               $vimeoHelper->expects($this->any())->method('getOnlineMediaId')->will($this->returnValue('7331'));
+
+               $this->subject = $this->getAccessibleMock(VimeoRenderer::class, array('getOnlineMediaHelper'), array());
+               $this->subject->expects($this->any())->method('getOnlineMediaHelper')->will($this->returnValue($vimeoHelper));
+       }
+
+       /**
+        * @test
+        */
+       public function getPriorityReturnsCorrectValue() {
+               $this->assertSame(1, $this->subject->getPriority());
+       }
+
+       /**
+        * @test
+        */
+       public function canRenderReturnsTrueOnCorrectFile() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock1 */
+               $fileResourceMock1 = $this->getMock(File::class, array(), array(), '', FALSE);
+               $fileResourceMock1->expects($this->any())->method('getMimeType')->will($this->returnValue('video/vimeo'));
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock2 */
+               $fileResourceMock2 = $this->getMock(File::class, array(), array(), '', FALSE);
+               $fileResourceMock2->expects($this->any())->method('getMimeType')->will($this->returnValue('video/unknown'));
+               $fileResourceMock2->expects($this->any())->method('getExtension')->will($this->returnValue('vimeo'));
+
+               $this->assertTrue($this->subject->canRender($fileResourceMock1));
+               $this->assertTrue($this->subject->canRender($fileResourceMock2));
+       }
+
+       /**
+        * @test
+        */
+       public function canRenderReturnsFalseOnCorrectFile() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+               $fileResourceMock->expects($this->any())->method('getMimeType')->will($this->returnValue('video/youtube'));
+
+               $this->assertFalse($this->subject->canRender($fileResourceMock));
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//player.vimeo.com/video/7331?title=0&amp;byline=0&amp;portrait=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200')
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputWithLoopIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//player.vimeo.com/video/7331?loop=1&amp;title=0&amp;byline=0&amp;portrait=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200', array('loop' => 1))
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputWithAutoplayIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//player.vimeo.com/video/7331?autoplay=1&amp;title=0&amp;byline=0&amp;portrait=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200', array('autoplay' => 1))
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputWithAutoplayAndWithoutControllsIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//player.vimeo.com/video/7331?autoplay=1&amp;title=0&amp;byline=0&amp;portrait=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200', array('autoplay' => 1))
+               );
+       }
+
+}
diff --git a/typo3/sysext/core/Tests/Unit/Resource/Rendering/YouTubeRendererTest.php b/typo3/sysext/core/Tests/Unit/Resource/Rendering/YouTubeRendererTest.php
new file mode 100644 (file)
index 0000000..e4cda14
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Resource\Rendering;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\YouTubeHelper;
+use TYPO3\CMS\Core\Resource\Rendering\YouTubeRenderer;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class YouTubeRendererTest
+ */
+class YouTubeRendererTest extends UnitTestCase {
+
+       /**
+        * @var YouTubeRenderer|\PHPUnit_Framework_MockObject_MockObject
+        */
+       protected $subject;
+
+       /**
+        * Set up the test
+        */
+       protected function setUp() {
+               parent::setUp();
+               GeneralUtility::flushInternalRuntimeCaches();
+               $_SERVER['HTTP_HOST'] = 'test.server.org';
+
+               /** @var YouTubeHelper|\PHPUnit_Framework_MockObject_MockObject $youTubeHelper */
+               $youTubeHelper = $this->getAccessibleMock(YouTubeHelper::class, array('getOnlineMediaId'), array('youtube'));
+               $youTubeHelper->expects($this->any())->method('getOnlineMediaId')->will($this->returnValue('7331'));
+
+               $this->subject = $this->getAccessibleMock(YouTubeRenderer::class, array('getOnlineMediaHelper'), array());
+               $this->subject ->expects($this->any())->method('getOnlineMediaHelper')->will($this->returnValue($youTubeHelper));
+       }
+
+       /**
+        * @test
+        */
+       public function getPriorityReturnsCorrectValue() {
+               $this->assertSame(1, $this->subject->getPriority());
+       }
+
+       /**
+        * @test
+        */
+       public function canRenderReturnsTrueOnCorrectFile() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock1 */
+               $fileResourceMock1 = $this->getMock(File::class, array(), array(), '', FALSE);
+               $fileResourceMock1->expects($this->any())->method('getMimeType')->will($this->returnValue('video/youtube'));
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock2 */
+               $fileResourceMock2 = $this->getMock(File::class, array(), array(), '', FALSE);
+               $fileResourceMock2->expects($this->any())->method('getMimeType')->will($this->returnValue('video/unknown'));
+               $fileResourceMock2->expects($this->any())->method('getExtension')->will($this->returnValue('youtube'));
+
+               $this->assertTrue($this->subject->canRender($fileResourceMock1));
+               $this->assertTrue($this->subject->canRender($fileResourceMock2));
+       }
+
+       /**
+        * @test
+        */
+       public function canRenderReturnsFalseOnCorrectFile() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+               $fileResourceMock->expects($this->any())->method('getMimeType')->will($this->returnValue('video/vimeo'));
+
+               $this->assertFalse($this->subject->canRender($fileResourceMock));
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//www.youtube.com/embed/7331?autohide=1&amp;controls=2&amp;enablejsapi=1&amp;origin=test.server.org&amp;showinfo=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200')
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputWithLoopIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//www.youtube.com/embed/7331?autohide=1&amp;controls=2&amp;loop=1&amp;enablejsapi=1&amp;origin=test.server.org&amp;showinfo=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200', array('loop' => 1))
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputWithAutoplayIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//www.youtube.com/embed/7331?autohide=1&amp;controls=2&amp;autoplay=1&amp;enablejsapi=1&amp;origin=test.server.org&amp;showinfo=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200', array('autoplay' => 1))
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function renderOutputWithAutoplayAndWithoutControllsIsCorrect() {
+               /** @var File|\PHPUnit_Framework_MockObject_MockObject $fileResourceMock */
+               $fileResourceMock = $this->getMock(File::class, array(), array(), '', FALSE);
+
+               $this->assertSame(
+                       '<div class="video-container"><iframe src="//www.youtube.com/embed/7331?autohide=1&amp;autoplay=1&amp;enablejsapi=1&amp;origin=test.server.org&amp;showinfo=0" width="300" height="200" allowfullscreen></iframe></div>',
+                       $this->subject->render($fileResourceMock, '300m', '200', array('controls' => 0, 'autoplay' => 1))
+               );
+       }
+
+}
index 82a1ce9..b128fa0 100644 (file)
@@ -62,6 +62,12 @@ if (!\TYPO3\CMS\Core\Core\Bootstrap::usesComposerClassLoading()) {
                'dumpClassLoadingInformation'
        );
 }
+$signalSlotDispatcher->connect(
+       TYPO3\CMS\Core\Resource\ResourceStorage::class,
+       \TYPO3\CMS\Core\Resource\Service\FileProcessingService::SIGNAL_PreFileProcess,
+       \TYPO3\CMS\Core\Resource\OnlineMedia\Processing\PreviewProcessing::class,
+       'processFile'
+);
 
 unset($signalSlotDispatcher);
 
@@ -71,6 +77,14 @@ $GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['dumpFile'] = \TYPO3\CMS\Core\C
 $rendererRegistry = \TYPO3\CMS\Core\Resource\Rendering\RendererRegistry::getInstance();
 $rendererRegistry->registerRendererClass(\TYPO3\CMS\Core\Resource\Rendering\AudioTagRenderer::class);
 $rendererRegistry->registerRendererClass(\TYPO3\CMS\Core\Resource\Rendering\VideoTagRenderer::class);
+$rendererRegistry->registerRendererClass(\TYPO3\CMS\Core\Resource\Rendering\YouTubeRenderer::class);
+$rendererRegistry->registerRendererClass(\TYPO3\CMS\Core\Resource\Rendering\VimeoRenderer::class);
+unset($rendererRegistry);
 
 $textExtractorRegistry = \TYPO3\CMS\Core\Resource\TextExtraction\TextExtractorRegistry::getInstance();
 $textExtractorRegistry->registerTextExtractor(\TYPO3\CMS\Core\Resource\TextExtraction\PlainTextExtractor::class);
+unset($textExtractorRegistry);
+
+$extractorRegistry = \TYPO3\CMS\Core\Resource\Index\ExtractorRegistry::getInstance();
+$extractorRegistry->registerExtractionService(\TYPO3\CMS\Core\Resource\OnlineMedia\Metadata\Extractor::class);
+unset($extractorRegistry);
index ecb618a..a8c2d1a 100644 (file)
@@ -742,7 +742,7 @@ class FileList extends AbstractRecordList {
                                                        $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject);
                                                        $theData[$field] .= $flashMessage->render();
                                                        // Thumbnails?
-                                               } elseif ($this->thumbs && $this->isImage($ext)) {
+                                               } elseif ($this->thumbs && ($this->isImage($ext) || $this->isMediaFile($ext))) {
                                                        $processedFile = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, array());
                                                        if ($processedFile) {
                                                                $thumbUrl = $processedFile->getPublicUrl(TRUE);
@@ -793,6 +793,16 @@ class FileList extends AbstractRecordList {
        }
 
        /**
+        * Returns TRUE if $ext is an media-extension according to $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']
+        *
+        * @param string $ext File extension
+        * @return bool
+        */
+       public function isMediaFile($ext) {
+               return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'], strtolower($ext));
+       }
+
+       /**
         * Wraps the directory-titles ($code) in a link to filelist/Modules/Filelist/index.php (id=$path) and sorting commands...
         *
         * @param string $code String to be wrapped
index 81a342e..63892f7 100644 (file)
@@ -471,7 +471,7 @@ return array(
                                                        --palette--;;filePalette'
                                        )
                                )
-                       ), $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'])
+                       ), $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'])
                ),
                'imagewidth' => array(
                        'exclude' => 1,
index cb6efd7..26733a7 100644 (file)
@@ -619,6 +619,33 @@ Do you want to continue WITHOUT saving?</source>
                        <trans-unit id="file_newfolder.php.number_of_folders">
                                <source>Number of folders:</source>
                        </trans-unit>
+                       <trans-unit id="online_media.new_media">
+                               <source>Add new media asset</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.new_media.label">
+                               <source>Url of remote source:</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.new_media.allowedProviders">
+                               <source>Allowed media providers:</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.new_media.button">
+                               <source>Add media by URL</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.new_media.submit">
+                               <source>Add media</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.new_media.added">
+                               <source>Added media</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.new_media.placeholder">
+                               <source>Paste media url here...</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.error.new_media.failed">
+                               <source>Adding media failed</source>
+                       </trans-unit>
+                       <trans-unit id="online_media.error.invalid_url">
+                               <source>Unknown/not allowed URL</source>
+                       </trans-unit>
                        <trans-unit id="file_clipupload.php.warning_head">
                                <source>Upload path error</source>
                        </trans-unit>
index 437b276..d14b031 100755 (executable)
@@ -29,6 +29,7 @@ use TYPO3\CMS\Core\ElementBrowser\ElementBrowserHookInterface;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Resource\Exception;
 use TYPO3\CMS\Core\Resource\File;
@@ -1920,7 +1921,7 @@ class ElementBrowser {
                        $fileExtension = $fileObject->getExtension();
                        // Thumbnail/size generation:
                        $imgInfo = array();
-                       if (GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), strtolower($fileExtension)) && !$noThumbs) {
+                       if (GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] . ',' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']), strtolower($fileExtension)) && !$noThumbs) {
                                $processedFile = $fileObject->process(
                                        ProcessedFile::CONTEXT_IMAGEPREVIEW,
                                        array('width' => 64, 'height' => 64)
@@ -2390,10 +2391,20 @@ class ElementBrowser {
                if ($count === '0') {
                        return '';
                }
+               $pArr = explode('|', $this->bparams);
+               $allowedExtensions = isset($pArr[3]) ? GeneralUtility::trimExplode(',', $pArr[3], TRUE) : [];
+
                $count = (int)$count === 0 ? 1 : (int)$count;
                // Create header, showing upload path:
                $header = $folderObject->getIdentifier();
                $lang = $this->getLanguageService();
+               // Create a list of allowed file extensions with the readable format "youtube, vimeo" etc.
+               $fileExtList = array();
+               foreach ($allowedExtensions as $fileExt) {
+                       if (GeneralUtility::verifyFilenameAgainstDenyPattern($fileExt)) {
+                               $fileExtList[] = '<span class="label label-success">' . strtoupper(htmlspecialchars($fileExt)) . '</span>';
+                       }
+               }
                $code = '
                        <br />
                        <!--
@@ -2426,6 +2437,16 @@ class ElementBrowser {
                        . '&bparams=' . rawurlencode($this->bparams)
                        . (is_array($this->P) ? GeneralUtility::implodeArrayForUrl('P', $this->P) : '');
                $code .= '<input type="hidden" name="redirect" value="' . htmlspecialchars($redirectValue) . '" />';
+
+               if (!empty($fileExtList)) {
+                       $code .= '
+                               <div class="help-block">
+                                       ' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions', TRUE) . '<br>
+                                       ' . implode(' ', $fileExtList) . '
+                               </div>
+                       ';
+               }
+
                $code .= '
                        <div id="c-override">
                                <label>
@@ -2439,7 +2460,66 @@ class ElementBrowser {
                $code .= '</td>
                                        </tr>
                                </table>
+                       </form>';
+
+               // Add online media
+               // Create a list of allowed file extensions in a readable format "youtube, vimeo" etc.
+               $fileExtList = array();
+               $onlineMediaFileExt = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
+               foreach ($onlineMediaFileExt as $fileExt) {
+                       if (
+                               GeneralUtility::verifyFilenameAgainstDenyPattern($fileExt)
+                               && (empty($allowedExtensions) || in_array($fileExt, $allowedExtensions, TRUE))
+                       ) {
+                               $fileExtList[] = '<span class="label label-success">' . strtoupper(htmlspecialchars($fileExt)) . '</span>';
+                       }
+               }
+               if (!empty($fileExtList)) {
+                       $code .= '
+                               <!--
+                       Form, adding online media urls:
+                               -->
+                               <form action="' . htmlspecialchars(BackendUtility::getModuleUrl('online_media')) . '" method="post" name="editform1"'
+                               . ' id="typo3-addMediaForm">
+                                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-uplFiles">
+                                               <tr>
+                                                       <td>' . $this->barheader($lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media', TRUE) . ':') . '</td>
+                                               </tr>
+                                               <tr>
+                                                       <td class="c-wCell c-hCell"><strong>' . $lang->getLL('path', TRUE) . ':</strong> '
+                               . htmlspecialchars($header) . '</td>
+                                               </tr>
+                                               <tr>
+                                                       <td class="c-wCell c-hCell">
+                                                               <input type="text" name="file[newMedia][0][url]"' . $this->doc->formWidth(35)
+                               . ' size="50" placeholder="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.placeholder', TRUE) . '" />
+                                       <input type="hidden" name="file[newMedia][0][target]" value="'
+                               . htmlspecialchars($folderObject->getCombinedIdentifier()) . '" />
+                                       <input type="hidden" name="file[newMedia][0][allowed]" value="'
+                               . htmlspecialchars(implode(',', $allowedExtensions)) . '" />
+                                       <button>' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.submit', TRUE) . '</button>
+                                       <div class="help-block">
+                                               ' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.allowedProviders') . '<br>
+                                               ' . implode(' ', $fileExtList) . '
+                                       </div>
+                                               ';
+               }
+
+               // Make footer of upload form, including the submit button:
+               $redirectValue = $this->getThisScript()
+                       . 'act=' . $this->act
+                       . '&mode=' . $this->mode
+                       . '&expandFolder=' . rawurlencode($folderObject->getCombinedIdentifier())
+                       . '&bparams=' . rawurlencode($this->bparams)
+                       . (is_array($this->P) ? GeneralUtility::implodeArrayForUrl('P', $this->P) : '');
+               $code .= '<input type="hidden" name="redirect" value="' . htmlspecialchars($redirectValue) . '" />';
+
+               $code .= '</td>
+                                       </tr>
+                               </table>
                        </form><br />';
+
+
                return $code;
        }