TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -0,0 +1,101 @@
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
use TYPO3\CMS\Core\Resource\Exception\OnlineMediaAlreadyExistsException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* 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 string $fileExtension
* @return File
*/
protected function transformMediaIdToFile($mediaId, Folder $targetFolder, $fileExtension)
{
$file = $this->findExistingFileByOnlineMediaId($mediaId, $targetFolder, $fileExtension);
if ($file !== null) {
throw new OnlineMediaAlreadyExistsException($file, 1695236851);
}
// no existing file create new
$oEmbed = $this->getOEmbedData($mediaId);
if (!empty($oEmbed['title'])) {
$fileName = $oEmbed['title'] . '.' . $fileExtension;
} else {
$fileName = $mediaId . '.' . $fileExtension;
}
return $this->createNewFile($targetFolder, $fileName, $mediaId);
}
/**
* Get OEmbed data
*
* @param string $mediaId
* @return array|null
*/
protected function getOEmbedData($mediaId)
{
$oEmbed = (string)GeneralUtility::getUrl(
$this->getOEmbedUrl($mediaId)
);
if ($oEmbed !== '') {
$oEmbed = json_decode($oEmbed, true);
if (is_array($oEmbed)) {
return $oEmbed;
}
}
return null;
}
/**
* Get meta data for OnlineMedia item
* Using the meta data from oEmbed
*
* @return array with metadata
*/
public function getMetaData(File $file)
{
$metadata = [];
$oEmbed = $this->getOEmbedData($this->getOnlineMediaId($file));
if (is_array($oEmbed) && $oEmbed !== []) {
$metadata['width'] = (int)($oEmbed['width'] ?? 0);
$metadata['height'] = (int)($oEmbed['height'] ?? 0);
if (empty($file->getProperty('title'))) {
$metadata['title'] = strip_tags($oEmbed['title'] ?? '');
}
$metadata['author'] = $oEmbed['author_name'] ?? '';
}
return $metadata;
}
}
@@ -0,0 +1,149 @@
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
use TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
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\Resource\ResourceInstructionTrait;
use TYPO3\CMS\Core\Utility\GeneralUtility;
abstract class AbstractOnlineMediaHelper implements OnlineMediaHelperInterface
{
use ResourceInstructionTrait;
/**
* Cached OnlineMediaIds [fileUid => id]
*
* @var array
*/
protected $onlineMediaIdCache = [];
/**
* 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
*
* @return string
*/
public function getOnlineMediaId(File $file)
{
if (!isset($this->onlineMediaIdCache[$file->getUid()])) {
// Limiting media identifier to 2048 bytes
if ($file->getSize() > 2048) {
return '';
}
try {
// By definition these files only contain the ID of the remote media source
$this->onlineMediaIdCache[$file->getUid()] = trim($file->getContents());
} catch (InsufficientFileAccessPermissionsException|IllegalFileExtensionException $e) {
// User has no access to the file - online media id can not be fetched
return '';
}
}
return $this->onlineMediaIdCache[$file->getUid()];
}
/**
* Search for files with same onlineMediaId by content hash in indexed storage
*
* @param string $onlineMediaId
* @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.
* This is created inside typo3temp/ and then moved from FAL to the proper storage.
*
* @param string $fileName
* @param string $onlineMediaId
* @return File
*/
protected function createNewFile(Folder $targetFolder, $fileName, $onlineMediaId)
{
$temporaryFile = GeneralUtility::tempnam('online_media');
GeneralUtility::writeFileToTypo3tempDir($temporaryFile, $onlineMediaId);
$this->skipResourceConsistencyCheckForCommands($targetFolder->getStorage(), $temporaryFile, $fileName);
$file = $targetFolder->addFile($temporaryFile, $fileName, DuplicationBehavior::RENAME);
return $file;
}
/**
* Get temporary folder path to save preview images.
* In composer-mode with TYPO3 installations, this needs to be put under public/
* In the future this should be handled via processed file objects.
*
* @return string
*/
protected function getTempFolderPath()
{
$path = Environment::getPublicPath() . '/typo3temp/assets/online_media/';
if (!is_dir($path)) {
GeneralUtility::mkdir_deep($path);
}
return $path;
}
protected function getFileIndexRepository(): FileIndexRepository
{
return GeneralUtility::makeInstance(FileIndexRepository::class);
}
protected function getResourceFactory(): ResourceFactory
{
return GeneralUtility::makeInstance(ResourceFactory::class);
}
}
@@ -0,0 +1,77 @@
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
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
* @return File|null
*/
public function transformUrlToFile($url, Folder $targetFolder);
/**
* Get Online Media item id
*
* @return string
*/
public function getOnlineMediaId(File $file);
/**
* Get public url
*
* Return NULL if you want to use core default behaviour
*
* @param File $file
* @return string|null
*/
public function getPublicUrl(File $file);
/**
* 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);
}
@@ -0,0 +1,83 @@
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Online Media Source Registry
*/
class OnlineMediaHelperRegistry implements SingletonInterface
{
/**
* Checks if there is a helper for this file extension
*/
public function hasOnlineMediaHelper(string $fileExtension): bool
{
return isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers'][$fileExtension]);
}
/**
* Get helper class for given File
*
* @return false|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 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 an OnlineMediaHelper
*
* @return string[]
*/
public function getSupportedFileExtensions()
{
return array_keys($GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['onlineMediaHelpers']);
}
}
@@ -0,0 +1,98 @@
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
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
*
* @return string|null
*/
public function getPublicUrl(File $file)
{
$videoId = $this->getOnlineMediaId($file);
return sprintf('https://vimeo.com/%s', rawurlencode($videoId));
}
/**
* Get local absolute file path to preview image
*
* @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);
if (!empty($oEmbedData['thumbnail_url'])) {
$previewImage = GeneralUtility::getUrl($oEmbedData['thumbnail_url']);
if ($previewImage !== false) {
GeneralUtility::writeFile($temporaryFileName, $previewImage, true);
}
}
}
return $temporaryFileName;
}
/**
* Try to transform given URL to a File
*
* @param string $url
* @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>/<optionalPrivateCode> # Share URL
// - vimeo.com/event/<code>
// - player.vimeo.com/video/<code>/<optionalPrivateCode> # URL form iframe embed code, can also get code from full iframe snippet
if (preg_match('/vimeo\.com\/(?:video\/|event\/)?([0-9a-z\/]+)/i', $url, $matches)) {
$videoId = $matches[1];
}
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?width=2048&url=%s',
rawurlencode($format),
rawurlencode(sprintf('https://vimeo.com/%s', rawurlencode($mediaId)))
);
}
}
@@ -0,0 +1,106 @@
<?php
/*
* 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!
*/
namespace TYPO3\CMS\Core\Resource\OnlineMedia\Helpers;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Youtube helper class
*/
class YouTubeHelper extends AbstractOEmbedHelper
{
/**
* Get public url
*
* @return string|null
*/
public function getPublicUrl(File $file)
{
$videoId = $this->getOnlineMediaId($file);
return sprintf('https://www.youtube.com/watch?v=%s', rawurlencode($videoId));
}
/**
* Get local absolute file path to preview image
*
* @return string
*/
public function getPreviewImage(File $file)
{
$videoId = $this->getOnlineMediaId($file);
$temporaryFileName = $this->getTempFolderPath() . 'youtube_' . md5($videoId) . '.jpg';
if (!file_exists($temporaryFileName)) {
$tryNames = ['maxresdefault.jpg', 'sddefault.jpg', 'hqdefault.jpg', 'mqdefault.jpg', '0.jpg'];
foreach ($tryNames as $tryName) {
$previewImage = GeneralUtility::getUrl(
sprintf('https://img.youtube.com/vi/%s/%s', $videoId, $tryName)
);
if ($previewImage !== false) {
GeneralUtility::writeFile($temporaryFileName, $previewImage, true);
break;
}
}
}
return $temporaryFileName;
}
/**
* Try to transform given URL to a File
*
* @param string $url
* @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
// - www.youtube.com/shorts/<code>
// - www.youtube.com/live/<code>
if (preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?|shorts|live)/|.*[?&]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&maxwidth=2048&maxheight=2048',
rawurlencode(sprintf('https://www.youtube.com/watch?v=%s', rawurlencode($mediaId))),
rawurlencode($format)
);
}
}