first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,523 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Exception\Archive\ExtractException;
use TYPO3\CMS\Core\Service\Archive\ZipService;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
/*
* 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!
*/
/**
* Utility for dealing with files and folders
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class FileHandlingUtility implements SingletonInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility
*/
protected $emConfUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
*/
protected $installUtility;
/**
* @var \TYPO3\CMS\Core\Localization\LanguageService
*/
protected $languageService;
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility
*/
public function injectEmConfUtility(\TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility)
{
$this->emConfUtility = $emConfUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
*/
public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility)
{
$this->installUtility = $installUtility;
}
/**
* @param \TYPO3\CMS\Core\Localization\LanguageService $languageService
*/
public function injectLanguageService(\TYPO3\CMS\Core\Localization\LanguageService $languageService)
{
$this->languageService = $languageService;
}
/**
* Initialize method - loads language file
*/
public function initializeObject()
{
$this->languageService->includeLLFile('EXT:extensionmanager/Resources/Private/Language/locallang.xlf');
}
/**
* Unpack an extension in t3x data format and write files
*
* @param array $extensionData
* @param Extension $extension
* @param string $pathType
*/
public function unpackExtensionFromExtensionDataArray(array $extensionData, Extension $extension = null, $pathType = 'Local')
{
$extensionDir = $this->makeAndClearExtensionDir($extensionData['extKey'], $pathType);
$files = $this->extractFilesArrayFromExtensionData($extensionData);
$directories = $this->extractDirectoriesFromExtensionData($files);
$files = array_diff_key($files, array_flip($directories));
$this->createDirectoriesForExtensionFiles($directories, $extensionDir);
$this->writeExtensionFiles($files, $extensionDir);
$this->writeEmConfToFile($extensionData, $extensionDir, $extension);
$this->reloadPackageInformation($extensionData['extKey']);
}
/**
* Extract needed directories from given extensionDataFilesArray
*
* @param array $files
* @return array
*/
protected function extractDirectoriesFromExtensionData(array $files)
{
$directories = [];
foreach ($files as $filePath => $file) {
preg_match('/(.*)\\//', $filePath, $matches);
if (!empty($matches[0])) {
$directories[] = $matches[0];
}
}
return array_unique($directories);
}
/**
* Returns the "FILES" part from the data array
*
* @param array $extensionData
* @return mixed
*/
protected function extractFilesArrayFromExtensionData(array $extensionData)
{
return $extensionData['FILES'];
}
/**
* Loops over an array of directories and creates them in the given root path
* It also creates nested directory structures
*
* @param array $directories
* @param string $rootPath
*/
protected function createDirectoriesForExtensionFiles(array $directories, $rootPath)
{
foreach ($directories as $directory) {
$this->createNestedDirectory($rootPath . $directory);
}
}
/**
* Wrapper for utility method to create directory recusively
*
* @param string $directory Absolute path
* @throws ExtensionManagerException
*/
protected function createNestedDirectory($directory)
{
try {
GeneralUtility::mkdir_deep($directory);
} catch (\RuntimeException $exception) {
throw new ExtensionManagerException(
sprintf($this->languageService->getLL('fileHandling.couldNotCreateDirectory'), $this->getRelativePath($directory)),
1337280416
);
}
}
/**
* Loops over an array of files and writes them to the given rootPath
*
* @param array $files
* @param string $rootPath
*/
protected function writeExtensionFiles(array $files, $rootPath)
{
foreach ($files as $file) {
GeneralUtility::writeFile($rootPath . $file['name'], $file['content']);
}
}
/**
* Removes the current extension of $type and creates the base folder for
* the new one (which is going to be imported)
*
* @param string $extensionKey
* @param string $pathType Extension installation scope (Local,Global,System)
* @throws ExtensionManagerException
* @return string
*/
protected function makeAndClearExtensionDir($extensionKey, $pathType = 'Local')
{
$extDirPath = $this->getExtensionDir($extensionKey, $pathType);
if (is_dir($extDirPath)) {
$this->removeDirectory($extDirPath);
}
$this->addDirectory($extDirPath);
return $extDirPath;
}
/**
* Returns the installation directory for an extension depending on the installation scope
*
* @param string $extensionKey
* @param string $pathType Extension installation scope (Local,Global,System)
* @return string
* @throws ExtensionManagerException
*/
public function getExtensionDir($extensionKey, $pathType = 'Local')
{
$paths = Extension::returnInstallPaths();
$path = $paths[$pathType] ?? '';
if (!$path || !is_dir($path) || !$extensionKey) {
throw new ExtensionManagerException(
sprintf($this->languageService->getLL('fileHandling.installPathWasNoDirectory'), $this->getRelativePath($path)),
1337280417
);
}
return $path . $extensionKey . '/';
}
/**
* Add specified directory
*
* @param string $extDirPath
* @throws ExtensionManagerException
*/
protected function addDirectory($extDirPath)
{
GeneralUtility::mkdir($extDirPath);
if (!is_dir($extDirPath)) {
throw new ExtensionManagerException(
sprintf($this->languageService->getLL('fileHandling.couldNotCreateDirectory'), $this->getRelativePath($extDirPath)),
1337280418
);
}
}
/**
* Creates directories configured in ext_emconf.php if not already present
*
* @param array $extension
*/
public function ensureConfiguredDirectoriesExist(array $extension)
{
foreach ($this->getAbsolutePathsToConfiguredDirectories($extension) as $directory) {
if (!$this->directoryExists($directory)) {
$this->createNestedDirectory($directory);
}
}
}
/**
* Wrapper method for directory existence check
*
* @param string $directory
* @return bool
*/
protected function directoryExists($directory)
{
return is_dir($directory);
}
/**
* Checks configuration and returns an array of absolute paths that should be created
*
* @param array $extension
* @return array
*/
protected function getAbsolutePathsToConfiguredDirectories(array $extension)
{
$requestedDirectories = [];
$requestUploadFolder = isset($extension['uploadfolder']) ? (bool)$extension['uploadfolder'] : false;
if ($requestUploadFolder) {
$requestedDirectories[] = $this->getAbsolutePath($this->getPathToUploadFolder($extension));
}
$requestCreateDirectories = empty($extension['createDirs']) ? false : (string)$extension['createDirs'];
if ($requestCreateDirectories) {
foreach (GeneralUtility::trimExplode(',', $extension['createDirs']) as $directoryToCreate) {
$requestedDirectories[] = $this->getAbsolutePath($directoryToCreate);
}
}
return $requestedDirectories;
}
/**
* Upload folders always reside in “uploads/tx_[extKey-with-no-underscore]”
*
* @param array $extension
* @return string
*/
protected function getPathToUploadFolder($extension)
{
return 'uploads/tx_' . str_replace('_', '', $extension['key']) . '/';
}
/**
* Remove specified directory
*
* @param string $extDirPath
* @throws ExtensionManagerException
*/
public function removeDirectory($extDirPath)
{
$extDirPath = GeneralUtility::fixWindowsFilePath($extDirPath);
$extensionPathWithoutTrailingSlash = rtrim($extDirPath, '/');
if (is_link($extensionPathWithoutTrailingSlash) && !Environment::isWindows()) {
$result = unlink($extensionPathWithoutTrailingSlash);
} else {
$result = GeneralUtility::rmdir($extDirPath, true);
}
if ($result === false) {
throw new ExtensionManagerException(
sprintf($this->languageService->getLL('fileHandling.couldNotRemoveDirectory'), $this->getRelativePath($extDirPath)),
1337280415
);
}
}
/**
* Constructs emConf and writes it to corresponding file
* In case the file has been extracted already, the properties of the meta data take precedence but are merged with the present ext_emconf.php
*
* @param array $extensionData
* @param string $rootPath
* @param Extension $extension
*/
protected function writeEmConfToFile(array $extensionData, $rootPath, Extension $extension = null)
{
$emConfFileData = [];
if (file_exists($rootPath . 'ext_emconf.php')) {
$emConfFileData = $this->emConfUtility->includeEmConf(
[
'key' => $extensionData['extKey'],
'siteRelPath' => PathUtility::stripPathSitePrefix($rootPath)
]
);
}
$extensionData['EM_CONF'] = array_replace_recursive($emConfFileData, $extensionData['EM_CONF']);
$emConfContent = $this->emConfUtility->constructEmConf($extensionData, $extension);
GeneralUtility::writeFile($rootPath . 'ext_emconf.php', $emConfContent);
}
/**
* Is the given path a valid path for extension installation
*
* @param string $path the absolute (!) path in question
* @return bool
*/
public function isValidExtensionPath($path)
{
$allowedPaths = Extension::returnAllowedInstallPaths();
foreach ($allowedPaths as $allowedPath) {
if (GeneralUtility::isFirstPartOfStr($path, $allowedPath)) {
return true;
}
}
return false;
}
/**
* Returns absolute path
*
* @param string $relativePath
* @throws ExtensionManagerException
* @return string
*/
protected function getAbsolutePath($relativePath)
{
$absolutePath = GeneralUtility::getFileAbsFileName(GeneralUtility::resolveBackPath(Environment::getPublicPath() . '/' . $relativePath));
if (empty($absolutePath)) {
throw new ExtensionManagerException('Illegal relative path given', 1350742864);
}
return $absolutePath;
}
/**
* Returns relative path
*
* @param string $absolutePath
* @return string
*/
protected function getRelativePath($absolutePath)
{
return PathUtility::stripPathSitePrefix($absolutePath);
}
/**
* Get extension path for an available or installed extension
*
* @param string $extension
* @return string
*/
public function getAbsoluteExtensionPath($extension)
{
$extension = $this->installUtility->enrichExtensionWithDetails($extension);
$absolutePath = $this->getAbsolutePath($extension['siteRelPath']);
return $absolutePath;
}
/**
* Get version of an available or installed extension
*
* @param string $extension
* @return string
*/
public function getExtensionVersion($extension)
{
$extensionData = $this->installUtility->enrichExtensionWithDetails($extension);
$version = $extensionData['version'];
return $version;
}
/**
* Create a zip file from an extension
*
* @param array $extension
* @return string Name and path of create zip file
*/
public function createZipFileFromExtension($extension)
{
$extensionPath = $this->getAbsoluteExtensionPath($extension);
// Add trailing slash to the extension path, getAllFilesAndFoldersInPath explicitly requires that.
$extensionPath = PathUtility::sanitizeTrailingSeparator($extensionPath);
$version = $this->getExtensionVersion($extension);
if (empty($version)) {
$version = '0.0.0';
}
$temporaryPath = Environment::getVarPath() . '/transient/';
if (!@is_dir($temporaryPath)) {
GeneralUtility::mkdir($temporaryPath);
}
$fileName = $temporaryPath . $extension . '_' . $version . '_' . date('YmdHi', $GLOBALS['EXEC_TIME']) . '.zip';
$zip = new \ZipArchive();
$zip->open($fileName, \ZipArchive::CREATE);
$excludePattern = $GLOBALS['TYPO3_CONF_VARS']['EXT']['excludeForPackaging'];
// Get all the files of the extension, but exclude the ones specified in the excludePattern
$files = GeneralUtility::getAllFilesAndFoldersInPath(
[], // No files pre-added
$extensionPath, // Start from here
'', // Do not filter files by extension
true, // Include subdirectories
PHP_INT_MAX, // Recursion level
$excludePattern // Files and directories to exclude.
);
// Make paths relative to extension root directory.
$files = GeneralUtility::removePrefixPathFromList($files, $extensionPath);
// Remove the one empty path that is the extension dir itself.
$files = array_filter($files);
foreach ($files as $file) {
$fullPath = $extensionPath . $file;
// Distinguish between files and directories, as creation of the archive
// fails on Windows when trying to add a directory with "addFile".
if (is_dir($fullPath)) {
$zip->addEmptyDir($file);
} else {
$zip->addFile($fullPath, $file);
}
}
$zip->close();
return $fileName;
}
/**
* Unzip an extension.zip.
*
* @param string $file path to zip file
* @param string $fileName file name
* @param string $pathType path type (Local, Global, System)
* @throws ExtensionManagerException
*/
public function unzipExtensionFromFile($file, $fileName, $pathType = 'Local')
{
$extensionDir = $this->makeAndClearExtensionDir($fileName, $pathType);
try {
$zipService = GeneralUtility::makeInstance(ZipService::class);
if ($zipService->verify($file)) {
$zipService->extract($file, $extensionDir);
}
} catch (ExtractException $e) {
$this->logger->error('Extracting the extension archive failed', ['exception' => $e]);
throw new ExtensionManagerException('Extracting the extension archive failed: ' . $e->getMessage(), 1565777179, $e);
}
GeneralUtility::fixPermissions($extensionDir, true);
}
/**
* Sends a zip file to the browser and deletes it afterwards
*
* @param string $fileName
* @param string $downloadName
*/
public function sendZipFileToBrowserAndDelete($fileName, $downloadName = '')
{
if ($downloadName === '') {
$downloadName = PathUtility::basename($fileName);
}
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($fileName));
header('Content-Disposition: attachment; filename="' . $downloadName . '"');
readfile($fileName);
unlink($fileName);
die;
}
/**
* @param string $extensionKey
*/
protected function reloadPackageInformation($extensionKey)
{
$this->installUtility->reloadPackageInformation($extensionKey);
}
}