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,133 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Connection;
/*
* 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\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
/**
* TER2 connection handling class for the TYPO3 Extension Manager.
*
* It contains methods for downloading and uploading extensions and related code
* @internal This class is a specific TER implementation and is not part of the Public TYPO3 API.
*/
class TerUtility
{
/**
* @var string
*/
public $wsdlUrl;
/**
* Fetches an extension from the given mirror
*
* @param string $extensionKey Extension Key
* @param string $version Version to install
* @param string $expectedMd5 Expected MD5 hash of extension file
* @param string $mirrorUrl URL of mirror to use
* @throws ExtensionManagerException
* @return array T3X data
*/
public function fetchExtension($extensionKey, $version, $expectedMd5, $mirrorUrl)
{
if (
(bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode')
|| Environment::isComposerMode()
) {
throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078620);
}
$extensionPath = strtolower($extensionKey);
$mirrorUrl .= $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath . '_' . $version . '.t3x';
$t3x = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($mirrorUrl);
$md5 = md5($t3x);
if ($t3x === false) {
throw new ExtensionManagerException(sprintf('The T3X file "%s" could not be fetched. Possible reasons: network problems, allow_url_fopen is off, cURL is not enabled in Install Tool.', $mirrorUrl), 1334426097);
}
if ($md5 === $expectedMd5) {
// Fetch and return:
$extensionData = $this->decodeExchangeData($t3x);
} else {
throw new ExtensionManagerException('Error: MD5 hash of downloaded file not as expected: ' . $md5 . ' != ' . $expectedMd5, 1334426098);
}
return $extensionData;
}
/**
* Decode server data
* This is information like the extension list, extension
* information etc., return data after uploads (new em_conf)
* On success, returns an array with data array and stats
* array as key 0 and 1.
*
* @param string $externalData Data stream from remove server
* @throws ExtensionManagerException
* @return array $externalData
* @see fetchServerData(), processRepositoryReturnData()
*/
public function decodeServerData($externalData)
{
$parts = explode(':', $externalData, 4);
$dat = base64_decode($parts[2]);
gzuncompress($dat);
// compare hashes ignoring any leading whitespace. See bug #0000365.
if (ltrim($parts[0]) == md5($dat)) {
if ($parts[1] === 'gzcompress') {
if (function_exists('gzuncompress')) {
$dat = gzuncompress($dat);
} else {
throw new ExtensionManagerException('Decoding Error: No decompressor available for compressed content. gzuncompress() function is not available!', 1342859463);
}
}
$listArr = unserialize($dat, ['allowed_classes' => false]);
if (!is_array($listArr)) {
throw new ExtensionManagerException('Error: Unserialized information was not an array - strange!', 1342859489);
}
} else {
throw new ExtensionManagerException('Error: MD5 hashes in T3X data did not match!', 1342859505);
}
return $listArr;
}
/**
* Decodes extension upload array.
* This kind of data is when an extension is uploaded to TER
*
* @param string $stream Data stream
* @throws ExtensionManagerException
* @return array Array with result on success, otherwise an error string.
*/
public function decodeExchangeData($stream)
{
$parts = explode(':', $stream, 3);
if ($parts[1] === 'gzcompress') {
if (function_exists('gzuncompress')) {
$parts[2] = gzuncompress($parts[2]);
} else {
throw new ExtensionManagerException('Decoding Error: No decompressor available for compressed content. gzcompress()/gzuncompress() ' . 'functions are not available!', 1344761814);
}
}
if (hash_equals($parts[0], md5($parts[2]))) {
$output = unserialize($parts[2], ['allowed_classes' => false]);
if (!is_array($output)) {
throw new ExtensionManagerException('Error: Content could not be unserialized to an array. Strange (since MD5 hashes match!)', 1344761938);
}
} else {
throw new ExtensionManagerException('Error: MD5 mismatch. Maybe the extension file was downloaded and saved as a text file by the ' . 'browser and thereby corrupted!? (Always select "All" filetype when saving extensions)', 1344761991);
}
return $output;
}
}
@@ -0,0 +1,664 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3\CMS\Extensionmanager\Domain\Model\Dependency;
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
use TYPO3\CMS\Extensionmanager\Exception;
/**
* Utility for dealing with dependencies
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
*/
protected $extensionRepository;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
*/
protected $listUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility
*/
protected $emConfUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
*/
protected $managementService;
/**
* @var array
*/
protected $availableExtensions = [];
/**
* @var string
*/
protected $localExtensionStorage = '';
/**
* @var array
*/
protected $dependencyErrors = [];
/**
* @var bool
*/
protected $skipDependencyCheck = false;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
*/
public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
{
$this->extensionRepository = $extensionRepository;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
*/
public function injectListUtility(\TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility)
{
$this->listUtility = $listUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility
*/
public function injectEmConfUtility(\TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility)
{
$this->emConfUtility = $emConfUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService
*/
public function injectManagementService(\TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService)
{
$this->managementService = $managementService;
}
/**
* @param string $localExtensionStorage
*/
public function setLocalExtensionStorage($localExtensionStorage)
{
$this->localExtensionStorage = $localExtensionStorage;
}
/**
* Setter for available extensions
* gets available extensions from list utility if not already done
*/
protected function setAvailableExtensions()
{
$this->availableExtensions = $this->listUtility->getAvailableExtensions();
}
/**
* @param bool $skipDependencyCheck
*/
public function setSkipDependencyCheck($skipDependencyCheck)
{
$this->skipDependencyCheck = $skipDependencyCheck;
}
/**
* Checks dependencies for special cases (currently typo3 and php)
*
* @param Extension $extension
*/
public function checkDependencies(Extension $extension)
{
$this->dependencyErrors = [];
$dependencies = $extension->getDependencies();
foreach ($dependencies as $dependency) {
/** @var Dependency $dependency */
$identifier = strtolower($dependency->getIdentifier());
try {
if (in_array($identifier, Dependency::$specialDependencies)) {
if (!$this->skipDependencyCheck) {
$methodName = 'check' . ucfirst($identifier) . 'Dependency';
$this->{$methodName}($dependency);
}
} else {
if ($dependency->getType() === 'depends') {
$this->checkExtensionDependency($dependency);
}
}
} catch (Exception\UnresolvedDependencyException $e) {
if (in_array($identifier, Dependency::$specialDependencies)) {
$extensionKey = $extension->getExtensionKey();
} else {
$extensionKey = $identifier;
}
if (!isset($this->dependencyErrors[$extensionKey])) {
$this->dependencyErrors[$extensionKey] = [];
}
$this->dependencyErrors[$extensionKey][] = [
'code' => $e->getCode(),
'message' => $e->getMessage()
];
}
}
}
/**
* Returns TRUE if a dependency error was found
*
* @return bool
*/
public function hasDependencyErrors()
{
return !empty($this->dependencyErrors);
}
/**
* Return the dependency errors
*
* @return array
*/
public function getDependencyErrors()
{
return $this->dependencyErrors;
}
/**
* Returns true if current TYPO3 version fulfills extension requirements
*
* @param Dependency $dependency
* @throws Exception\UnresolvedTypo3DependencyException
* @return bool
*/
protected function checkTypo3Dependency(Dependency $dependency)
{
$lowerCaseIdentifier = strtolower($dependency->getIdentifier());
if ($lowerCaseIdentifier === 'typo3') {
if (!($dependency->getLowestVersion() === '') && version_compare(VersionNumberUtility::getNumericTypo3Version(), $dependency->getLowestVersion()) === -1) {
throw new Exception\UnresolvedTypo3DependencyException(
'Your TYPO3 version is lower than this extension requires. It requires TYPO3 versions ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
1399144499
);
}
if (!($dependency->getHighestVersion() === '') && version_compare($dependency->getHighestVersion(), VersionNumberUtility::getNumericTypo3Version()) === -1) {
throw new Exception\UnresolvedTypo3DependencyException(
'Your TYPO3 version is higher than this extension requires. It requires TYPO3 versions ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
1399144521
);
}
} else {
throw new Exception\UnresolvedTypo3DependencyException(
'checkTypo3Dependency can only check TYPO3 dependencies. Found dependency with identifier "' . $dependency->getIdentifier() . '"',
1399144551
);
}
return true;
}
/**
* Returns true if current php version fulfills extension requirements
*
* @param Dependency $dependency
* @throws Exception\UnresolvedPhpDependencyException
* @return bool
*/
protected function checkPhpDependency(Dependency $dependency)
{
$lowerCaseIdentifier = strtolower($dependency->getIdentifier());
if ($lowerCaseIdentifier === 'php') {
if (!($dependency->getLowestVersion() === '') && version_compare(PHP_VERSION, $dependency->getLowestVersion()) === -1) {
throw new Exception\UnresolvedPhpDependencyException(
'Your PHP version is lower than necessary. You need at least PHP version ' . $dependency->getLowestVersion(),
1377977857
);
}
if (!($dependency->getHighestVersion() === '') && version_compare($dependency->getHighestVersion(), PHP_VERSION) === -1) {
throw new Exception\UnresolvedPhpDependencyException(
'Your PHP version is higher than allowed. You can use PHP versions ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
1377977856
);
}
} else {
throw new Exception\UnresolvedPhpDependencyException(
'checkPhpDependency can only check PHP dependencies. Found dependency with identifier "' . $dependency->getIdentifier() . '"',
1377977858
);
}
return true;
}
/**
* Main controlling function for checking dependencies
* Dependency check is done in the following way:
* - installed extension in matching version ? - return true
* - available extension in matching version ? - mark for installation
* - remote (TER) extension in matching version? - mark for download
*
* @todo handle exceptions / markForUpload
* @param Dependency $dependency
* @throws Exception\MissingVersionDependencyException
* @return bool
*/
protected function checkExtensionDependency(Dependency $dependency)
{
$extensionKey = $dependency->getIdentifier();
$extensionIsLoaded = $this->isDependentExtensionLoaded($extensionKey);
if ($extensionIsLoaded === true) {
$isLoadedVersionCompatible = $this->isLoadedVersionCompatible($dependency);
if ($isLoadedVersionCompatible === true || $this->skipDependencyCheck) {
return true;
}
$extension = $this->listUtility->getExtension($extensionKey);
$loadedVersion = $extension->getPackageMetaData()->getVersion();
if (version_compare($loadedVersion, $dependency->getHighestVersion()) === -1) {
try {
$this->getExtensionFromRepository($extensionKey, $dependency);
} catch (Exception\UnresolvedDependencyException $e) {
throw new Exception\MissingVersionDependencyException(
'The extension ' . $extensionKey . ' is installed in version ' . $loadedVersion
. ' but needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion() . ' and could not be fetched from TER',
1396302624
);
}
} else {
throw new Exception\MissingVersionDependencyException(
'The extension ' . $extensionKey . ' is installed in version ' . $loadedVersion .
' but needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
1430561927
);
}
} else {
$extensionIsAvailable = $this->isDependentExtensionAvailable($extensionKey);
if ($extensionIsAvailable === true) {
$isAvailableVersionCompatible = $this->isAvailableVersionCompatible($dependency);
if ($isAvailableVersionCompatible) {
$unresolvedDependencyErrors = $this->dependencyErrors;
$this->managementService->markExtensionForInstallation($extensionKey);
$this->dependencyErrors = array_merge($unresolvedDependencyErrors, $this->dependencyErrors);
} else {
$extension = $this->listUtility->getExtension($extensionKey);
$availableVersion = $extension->getPackageMetaData()->getVersion();
if (version_compare($availableVersion, $dependency->getHighestVersion()) === -1) {
try {
$this->getExtensionFromRepository($extensionKey, $dependency);
} catch (Exception\MissingExtensionDependencyException $e) {
if (!$this->skipDependencyCheck) {
throw new Exception\MissingVersionDependencyException(
'The extension ' . $extensionKey . ' is available in version ' . $availableVersion
. ' but is needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion() . ' and could not be fetched from TER',
1430560390
);
}
}
} else {
if (!$this->skipDependencyCheck) {
throw new Exception\MissingVersionDependencyException(
'The extension ' . $extensionKey . ' is available in version ' . $availableVersion
. ' but is needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
1430562374
);
}
// Dependency check is skipped and the local version has to be installed
$this->managementService->markExtensionForInstallation($extensionKey);
}
}
} else {
$unresolvedDependencyErrors = $this->dependencyErrors;
$this->getExtensionFromRepository($extensionKey, $dependency);
$this->dependencyErrors = array_merge($unresolvedDependencyErrors, $this->dependencyErrors);
}
}
return false;
}
/**
* Get an extension from a repository
* (might be in the extension itself or the TER)
*
* @param string $extensionKey
* @param Dependency $dependency
* @throws Exception\UnresolvedDependencyException
*/
protected function getExtensionFromRepository($extensionKey, Dependency $dependency)
{
if (!$this->getExtensionFromInExtensionRepository($extensionKey)) {
$this->getExtensionFromTer($extensionKey, $dependency);
}
}
/**
* Gets an extension from the in extension repository
* (the local extension storage)
*
* @param string $extensionKey
* @return bool
*/
protected function getExtensionFromInExtensionRepository($extensionKey)
{
if ($this->localExtensionStorage !== '' && is_dir($this->localExtensionStorage)) {
$extList = \TYPO3\CMS\Core\Utility\GeneralUtility::get_dirs($this->localExtensionStorage);
if (in_array($extensionKey, $extList)) {
$this->managementService->markExtensionForCopy($extensionKey, $this->localExtensionStorage);
return true;
}
}
return false;
}
/**
* Handles checks to find a compatible extension version from TER to fulfill given dependency
*
* @todo unit tests
* @param string $extensionKey
* @param Dependency $dependency
* @throws Exception\UnresolvedDependencyException
*/
protected function getExtensionFromTer($extensionKey, Dependency $dependency)
{
$isExtensionDownloadableFromTer = $this->isExtensionDownloadableFromTer($extensionKey);
if (!$isExtensionDownloadableFromTer) {
if (!$this->skipDependencyCheck) {
if ($this->extensionRepository->countAll() > 0) {
throw new Exception\MissingExtensionDependencyException(
'The extension ' . $extensionKey . ' is not available from TER.',
1399161266
);
}
throw new Exception\MissingExtensionDependencyException(
'The extension ' . $extensionKey . ' could not be checked. Please update your Extension-List from TYPO3 Extension Repository (TER).',
1430580308
);
}
return;
}
$isDownloadableVersionCompatible = $this->isDownloadableVersionCompatible($dependency);
if (!$isDownloadableVersionCompatible) {
if (!$this->skipDependencyCheck) {
throw new Exception\MissingVersionDependencyException(
'No compatible version found for extension ' . $extensionKey,
1399161284
);
}
return;
}
$latestCompatibleExtensionByIntegerVersionDependency = $this->getLatestCompatibleExtensionByIntegerVersionDependency($dependency);
if (!$latestCompatibleExtensionByIntegerVersionDependency instanceof Extension) {
if (!$this->skipDependencyCheck) {
throw new Exception\MissingExtensionDependencyException(
'Could not resolve dependency for "' . $dependency->getIdentifier() . '"',
1399161302
);
}
return;
}
if ($this->isDependentExtensionLoaded($extensionKey)) {
$this->managementService->markExtensionForUpdate($latestCompatibleExtensionByIntegerVersionDependency);
} else {
$this->managementService->markExtensionForDownload($latestCompatibleExtensionByIntegerVersionDependency);
}
}
/**
* @param string $extensionKey
* @return bool
*/
protected function isDependentExtensionLoaded($extensionKey)
{
return ExtensionManagementUtility::isLoaded($extensionKey);
}
/**
* @param Dependency $dependency
* @return bool
*/
protected function isLoadedVersionCompatible(Dependency $dependency)
{
$extensionVersion = ExtensionManagementUtility::getExtensionVersion($dependency->getIdentifier());
return $this->isVersionCompatible($extensionVersion, $dependency);
}
/**
* @param string $version
* @param Dependency $dependency
* @return bool
*/
protected function isVersionCompatible($version, Dependency $dependency)
{
if (!($dependency->getLowestVersion() === '') && version_compare($version, $dependency->getLowestVersion()) === -1) {
return false;
}
if (!($dependency->getHighestVersion() === '') && version_compare($dependency->getHighestVersion(), $version) === -1) {
return false;
}
return true;
}
/**
* Checks whether the needed extension is available
* (not necessarily installed, but present in system)
*
* @param string $extensionKey
* @return bool
*/
protected function isDependentExtensionAvailable($extensionKey)
{
$this->setAvailableExtensions();
return array_key_exists($extensionKey, $this->availableExtensions);
}
/**
* Checks whether the available version is compatible
*
* @param Dependency $dependency
* @return bool
*/
protected function isAvailableVersionCompatible(Dependency $dependency)
{
$this->setAvailableExtensions();
$extensionData = $this->emConfUtility->includeEmConf($this->availableExtensions[$dependency->getIdentifier()]);
return $this->isVersionCompatible($extensionData['version'], $dependency);
}
/**
* Checks whether a ter extension with $extensionKey exists
*
* @param string $extensionKey
* @return bool
*/
protected function isExtensionDownloadableFromTer($extensionKey)
{
return $this->extensionRepository->countByExtensionKey($extensionKey) > 0;
}
/**
* Checks whether a compatible version of the extension exists in TER
*
* @param Dependency $dependency
* @return bool
*/
protected function isDownloadableVersionCompatible(Dependency $dependency)
{
$versions = $this->getLowestAndHighestIntegerVersions($dependency);
$count = $this->extensionRepository->countByVersionRangeAndExtensionKey(
$dependency->getIdentifier(),
$versions['lowestIntegerVersion'],
$versions['highestIntegerVersion']
);
return !empty($count);
}
/**
* Get the latest compatible version of an extension that's
* compatible with the current core and PHP version.
*
* @param iterable $extensions
* @return Extension|null
*/
protected function getCompatibleExtension(iterable $extensions): ?Extension
{
foreach ($extensions as $extension) {
/** @var Extension $extension */
$this->checkDependencies($extension);
$extensionKey = $extension->getExtensionKey();
if (isset($this->dependencyErrors[$extensionKey])) {
// reset dependencyErrors and continue with next version
unset($this->dependencyErrors[$extensionKey]);
continue;
}
return $extension;
}
return null;
}
/**
* Get the latest compatible version of an extension that
* fulfills the given dependency from TER
*
* @param Dependency $dependency
* @return Extension
*/
protected function getLatestCompatibleExtensionByIntegerVersionDependency(Dependency $dependency)
{
$versions = $this->getLowestAndHighestIntegerVersions($dependency);
$compatibleDataSets = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion(
$dependency->getIdentifier(),
$versions['lowestIntegerVersion'],
$versions['highestIntegerVersion']
);
return $this->getCompatibleExtension($compatibleDataSets);
}
/**
* Return array of lowest and highest version of dependency as integer
*
* @param Dependency $dependency
* @return array
*/
protected function getLowestAndHighestIntegerVersions(Dependency $dependency)
{
$lowestVersion = $dependency->getLowestVersion();
$lowestVersionInteger = $lowestVersion ? VersionNumberUtility::convertVersionNumberToInteger($lowestVersion) : 0;
$highestVersion = $dependency->getHighestVersion();
$highestVersionInteger = $highestVersion ? VersionNumberUtility::convertVersionNumberToInteger($highestVersion) : 0;
return [
'lowestIntegerVersion' => $lowestVersionInteger,
'highestIntegerVersion' => $highestVersionInteger
];
}
/**
* @param string $extensionKey
* @return array
*/
public function findInstalledExtensionsThatDependOnMe($extensionKey)
{
$availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation();
$dependentExtensions = [];
foreach ($availableAndInstalledExtensions as $availableAndInstalledExtensionKey => $availableAndInstalledExtension) {
if (isset($availableAndInstalledExtension['installed']) && $availableAndInstalledExtension['installed'] === true) {
if (is_array($availableAndInstalledExtension['constraints']) && is_array($availableAndInstalledExtension['constraints']['depends']) && array_key_exists($extensionKey, $availableAndInstalledExtension['constraints']['depends'])) {
$dependentExtensions[] = $availableAndInstalledExtensionKey;
}
}
}
return $dependentExtensions;
}
/**
* Get extensions (out of a given list) that are suitable for the current TYPO3 version
*
* @param \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array $extensions List of extensions to check
* @return array List of extensions suitable for current TYPO3 version
*/
public function getExtensionsSuitableForTypo3Version($extensions)
{
$suitableExtensions = [];
/** @var Extension $extension */
foreach ($extensions as $extension) {
/** @var Dependency $dependency */
foreach ($extension->getDependencies() as $dependency) {
if ($dependency->getIdentifier() === 'typo3') {
try {
if ($this->checkTypo3Dependency($dependency)) {
$suitableExtensions[] = $extension;
}
} catch (Exception\UnresolvedTypo3DependencyException $e) {
}
break;
}
}
}
return $suitableExtensions;
}
/**
* Gets a list of various extensions in various versions and returns
* a filtered list containing the extension-version combination with
* the highest version number.
*
* @param Extension[] $extensions
* @param bool $showUnsuitable
*
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Extension[]
*/
public function filterYoungestVersionOfExtensionList(array $extensions, $showUnsuitable)
{
if (!$showUnsuitable) {
$extensions = $this->getExtensionsSuitableForTypo3Version($extensions);
}
$filteredExtensions = [];
foreach ($extensions as $extension) {
$extensionKey = $extension->getExtensionKey();
if (!array_key_exists($extensionKey, $filteredExtensions)) {
$filteredExtensions[$extensionKey] = $extension;
continue;
}
$currentVersion = $filteredExtensions[$extensionKey]->getVersion();
$newVersion = $extension->getVersion();
if (version_compare($newVersion, $currentVersion, '>')) {
$filteredExtensions[$extensionKey] = $extension;
}
}
return $filteredExtensions;
}
}
@@ -0,0 +1,104 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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 Downloading Extensions
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class DownloadUtility implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility
*/
protected $terUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\Repository\Helper
*/
protected $repositoryHelper;
/**
* @var string
*/
protected $downloadPath = 'Local';
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
*/
protected $fileHandlingUtility;
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility $terUtility
*/
public function injectTerUtility(\TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility $terUtility)
{
$this->terUtility = $terUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\Repository\Helper $repositoryHelper
*/
public function injectRepositoryHelper(\TYPO3\CMS\Extensionmanager\Utility\Repository\Helper $repositoryHelper)
{
$this->repositoryHelper = $repositoryHelper;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
*/
public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
{
$this->fileHandlingUtility = $fileHandlingUtility;
}
/**
* Download an extension
*
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
*/
public function download(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension)
{
$mirrorUrl = $this->repositoryHelper->getMirrors()->getMirrorUrl();
$fetchedExtension = $this->terUtility->fetchExtension($extension->getExtensionKey(), $extension->getVersion(), $extension->getMd5hash(), $mirrorUrl);
if (isset($fetchedExtension['extKey']) && !empty($fetchedExtension['extKey']) && is_string($fetchedExtension['extKey'])) {
$this->fileHandlingUtility->unpackExtensionFromExtensionDataArray($fetchedExtension, $extension, $this->getDownloadPath());
}
}
/**
* Set the download path
*
* @param string $downloadPath
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
*/
public function setDownloadPath($downloadPath)
{
if (!in_array($downloadPath, \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::returnAllowedInstallTypes())) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException($downloadPath . ' not in allowed download paths', 1344766387);
}
$this->downloadPath = $downloadPath;
}
/**
* Get the download path
*
* @return string
*/
public function getDownloadPath()
{
return $this->downloadPath;
}
}
@@ -0,0 +1,152 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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\Core\Environment;
use TYPO3\CMS\Core\SingletonInterface;
/**
* Utility for dealing with ext_emconf
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class EmConfUtility implements SingletonInterface
{
/**
* Returns the $EM_CONF array from an extensions ext_emconf.php file
*
* @param array $extension Extension information array
* @return array|bool EMconf array values or false if no ext_emconf.php found.
*/
public function includeEmConf(array $extension)
{
$_EXTKEY = $extension['key'];
$path = Environment::getPublicPath() . '/' . $extension['siteRelPath'] . 'ext_emconf.php';
$EM_CONF = null;
if (file_exists($path)) {
include $path;
if (is_array($EM_CONF[$_EXTKEY])) {
return $EM_CONF[$_EXTKEY];
}
}
return false;
}
/**
* Generates the content for the ext_emconf.php file
* Sets dependencies from TER data if any
*
* @param array $extensionData
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension Extension object from TER data
* @return string
* @internal
*/
public function constructEmConf(array $extensionData, \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension = null)
{
if (is_object($extension) && empty($extensionData['EM_CONF']['constraints'])) {
$extensionData['EM_CONF']['constraints'] = unserialize($extension->getSerializedDependencies(), ['allowed_classes' => false]);
}
$emConf = $this->fixEmConf($extensionData['EM_CONF']);
$emConf = var_export($emConf, true);
$code = '<?php
/***************************************************************
* Extension Manager/Repository config file for ext "' . $extensionData['extKey'] . '".
*
* Auto generated ' . date('d-m-Y H:i') . '
*
* Manual updates:
* Only the data in the array - everything else is removed by next
* writing. "version" and "dependencies" must not be touched!
***************************************************************/
$EM_CONF[$_EXTKEY] = ' . $emConf . ';
';
return $code;
}
/**
* Fix the em conf - Converts old / ter em_conf format to new format
*
* @param array $emConf
* @return array
*/
public function fixEmConf(array $emConf)
{
if (
!isset($emConf['constraints']) || !isset($emConf['constraints']['depends'])
|| !isset($emConf['constraints']['conflicts']) || !isset($emConf['constraints']['suggests'])
) {
if (!isset($emConf['constraints']) || !isset($emConf['constraints']['depends'])) {
$emConf['constraints']['depends'] = $this->stringToDependency($emConf['dependencies'] ?? '');
if (isset($emConf['PHP_version']) && (string)$emConf['PHP_version'] !== '') {
$emConf['constraints']['depends']['php'] = $emConf['PHP_version'];
}
if (isset($emConf['TYPO3_version']) && (string)$emConf['TYPO3_version'] !== '') {
$emConf['constraints']['depends']['typo3'] = $emConf['TYPO3_version'];
}
}
if (!isset($emConf['constraints']) || !isset($emConf['constraints']['conflicts'])) {
$emConf['constraints']['conflicts'] = $this->stringToDependency($emConf['conflicts'] ?? '');
}
if (!isset($emConf['constraints']) || !isset($emConf['constraints']['suggests'])) {
$emConf['constraints']['suggests'] = [];
}
}
// Remove TER v1-style entries
unset($emConf['dependencies']);
unset($emConf['conflicts']);
unset($emConf['suggests']);
unset($emConf['private']);
unset($emConf['download_password']);
unset($emConf['TYPO3_version']);
unset($emConf['PHP_version']);
unset($emConf['internal']);
unset($emConf['module']);
unset($emConf['loadOrder']);
unset($emConf['lockType']);
unset($emConf['shy']);
unset($emConf['priority']);
unset($emConf['modify_tables']);
unset($emConf['CGLcompliance']);
unset($emConf['CGLcompliance_note']);
return $emConf;
}
/**
* Checks whether the passed dependency is TER-style (string)or
* TER2-style (array)and returns a single string for displaying the
* dependencies.
*
* It leaves out all version numbers and the "php" and "typo3" dependencies,
* as they are implicit and of no interest without the version number.
*
* @param mixed $dependency Either a string or an array listing dependencies.
* @return array A simple dependency list for display
*/
public function stringToDependency($dependency)
{
$constraint = [];
if (is_string($dependency) && $dependency !== '') {
$dependency = explode(',', $dependency);
foreach ($dependency as $v) {
$constraint[$v] = '';
}
}
return $constraint;
}
}
@@ -0,0 +1,98 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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 extension model related helper functions
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class ExtensionModelUtility
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
*/
protected $objectManager;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* Map a legacy extension array to an object
*
* @param array $extensionArray
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
*/
public function mapExtensionArrayToModel(array $extensionArray)
{
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension */
$extension = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension::class);
$extension->setExtensionKey($extensionArray['key']);
if (isset($extensionArray['version'])) {
$extension->setVersion($extensionArray['version']);
}
if (isset($extensionArray['constraints'])) {
$extension->setDependencies($this->convertDependenciesToObjects(serialize($extensionArray['constraints'])));
}
return $extension;
}
/**
* Converts string dependencies to an object storage of dependencies
*
* @param string $dependencies
* @return \SplObjectStorage
*/
public function convertDependenciesToObjects($dependencies)
{
$dependenciesObject = new \SplObjectStorage();
$unserializedDependencies = unserialize($dependencies, ['allowed_classes' => false]);
if (!is_array($unserializedDependencies)) {
return $dependenciesObject;
}
foreach ($unserializedDependencies as $dependencyType => $dependencyValues) {
// Dependencies might be given as empty string, e.g. conflicts => ''
if (!is_array($dependencyValues)) {
continue;
}
foreach ($dependencyValues as $dependency => $versions) {
if ($dependencyType && $dependency) {
$versionNumbers = \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionsStringToVersionNumbers($versions);
$lowest = $versionNumbers[0];
if (count($versionNumbers) === 2) {
$highest = $versionNumbers[1];
} else {
$highest = '';
}
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependencyObject */
$dependencyObject = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency::class);
$dependencyObject->setType($dependencyType);
// dynamically migrate 'cms' dependency to 'core' dependency
// see also \TYPO3\CMS\Core\Package\Package::getPackageMetaData
$dependencyObject->setIdentifier($dependency === 'cms' ? 'core' : $dependency);
$dependencyObject->setLowestVersion($lowest);
$dependencyObject->setHighestVersion($highest);
$dependenciesObject->attach($dependencyObject);
unset($dependencyObject);
}
}
}
return $dependenciesObject;
}
}
@@ -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);
}
}
@@ -0,0 +1,272 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Importer;
/*
* 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\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3\CMS\Extensionmanager\Utility\Parser\AbstractExtensionXmlParser;
/**
* Importer object for extension list
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class ExtensionListUtility implements \SplObserver
{
/**
* Keeps instance of a XML parser.
*
* @var AbstractExtensionXmlParser
*/
protected $parser;
/**
* Keeps number of processed version records.
*
* @var int
*/
protected $sumRecords = 0;
/**
* Keeps record values to be inserted into database.
*
* @var array
*/
protected $arrRows = [];
/**
* Keeps fieldnames of tx_extensionmanager_domain_model_extension table.
*
* @var array
*/
protected static $fieldNames = [
'extension_key',
'version',
'integer_version',
'current_version',
'alldownloadcounter',
'downloadcounter',
'title',
'ownerusername',
'author_name',
'author_email',
'authorcompany',
'last_updated',
'md5hash',
'repository',
'state',
'review_state',
'category',
'description',
'serialized_dependencies',
'update_comment'
];
/**
* Table name to be used to store extension models.
*
* @var string
*/
protected static $tableName = 'tx_extensionmanager_domain_model_extension';
/**
* Maximum of rows that can be used in a bulk insert for the current
* database platform.
*
* @var int
*/
protected $maxRowsPerChunk = 50;
/**
* Keeps indexes of fields that should not be quoted.
*
* @var array
*/
protected static $fieldIndicesNoQuote = [2, 3, 5, 11, 13, 14, 15, 16];
/**
* Keeps repository UID.
*
* The UID is necessary for inserting records.
*
* @var int
*/
protected $repositoryUid = 1;
/**
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository
*/
protected $repositoryRepository;
/**
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
*/
protected $extensionRepository;
/**
* @var \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
*/
protected $extensionModel;
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
*/
protected $objectManager;
/**
* Only import extensions newer than this date (timestamp),
* see constructor
*
* @var int
*/
protected $minimumDateToImport;
/**
* Class constructor.
*
* Method retrieves and initializes extension XML parser instance.
*
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
*/
public function __construct()
{
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
$this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
$this->repositoryRepository = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository::class);
$this->extensionRepository = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository::class);
$this->extensionModel = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension::class);
// @todo catch parser exception
$this->parser = \TYPO3\CMS\Extensionmanager\Utility\Parser\XmlParserFactory::getParserInstance('extension');
if (is_object($this->parser)) {
$this->parser->attach($this);
} else {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
static::class . ': No XML parser available.',
1476108717
);
}
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable(self::$tableName);
$maxBindParameters = PlatformInformation::getMaxBindParameters(
$connection->getDatabasePlatform()
);
$countOfBindParamsPerRow = count(self::$fieldNames);
// flush at least chunks of 50 elements - in case the currently used
// database platform does not support that, the threshold is lowered
$this->maxRowsPerChunk = min(
$this->maxRowsPerChunk,
floor($maxBindParameters / $countOfBindParamsPerRow)
);
// Only import extensions that are compatible with 7.6 or higher.
// TER only allows to publish extensions with compatibility if the TYPO3 version has been released
// And 7.6 was released on 10th of November 2015.
// This effectively reduces the number of extensions imported into this TYPO3 installation
// by more than 70%. As long as the extensions.xml from TER includes these files, we need to "hack" this
// within TYPO3 Core.
// For TYPO3 v10.0, this date could be set to 2017-04-04 (8 LTS release).
// Also see https://decisions.typo3.org/t/reduce-size-of-extension-manager-db-table/329/
$this->minimumDateToImport = strtotime('2015-11-10T00:00:00+00:00');
}
/**
* Method initializes parsing of extension.xml.gz file.
*
* @param string $localExtensionListFile absolute path to extension list xml.gz
* @param int $repositoryUid UID of repository when inserting records into DB
* @return int total number of imported extension versions
*/
public function import($localExtensionListFile, $repositoryUid = null)
{
if ($repositoryUid !== null && is_int($repositoryUid)) {
$this->repositoryUid = $repositoryUid;
}
$zlibStream = 'compress.zlib://';
$this->sumRecords = 0;
$this->parser->parseXml($zlibStream . $localExtensionListFile);
// flush last rows to database if existing
if (!empty($this->arrRows)) {
GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_extensionmanager_domain_model_extension')
->bulkInsert(
'tx_extensionmanager_domain_model_extension',
$this->arrRows,
self::$fieldNames
);
}
$extensions = $this->extensionRepository->insertLastVersion($this->repositoryUid);
$this->repositoryRepository->updateRepositoryCount($extensions, $this->repositoryUid);
return $this->sumRecords;
}
/**
* Method collects and stores extension version details into the database.
*
* @param AbstractExtensionXmlParser $subject a subject notifying this observer
*/
protected function loadIntoDatabase(AbstractExtensionXmlParser &$subject)
{
if ($this->sumRecords !== 0 && $this->sumRecords % $this->maxRowsPerChunk === 0) {
GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable(self::$tableName)
->bulkInsert(
self::$tableName,
$this->arrRows,
self::$fieldNames
);
$this->arrRows = [];
}
$versionRepresentations = VersionNumberUtility::convertVersionStringToArray($subject->getVersion());
// order must match that of self::$fieldNames!
$this->arrRows[] = [
$subject->getExtkey(),
$subject->getVersion(),
$versionRepresentations['version_int'],
// initialize current_version, correct value computed later:
0,
(int)$subject->getAlldownloadcounter(),
(int)$subject->getDownloadcounter(),
$subject->getTitle() !== null ? $subject->getTitle() : '',
$subject->getOwnerusername(),
$subject->getAuthorname() !== null ? $subject->getAuthorname() : '',
$subject->getAuthoremail() !== null ? $subject->getAuthoremail() : '',
$subject->getAuthorcompany() !== null ? $subject->getAuthorcompany() : '',
(int)$subject->getLastuploaddate(),
$subject->getT3xfilemd5(),
$this->repositoryUid,
$this->extensionModel->getDefaultState($subject->getState() ?: ''),
(int)$subject->getReviewstate(),
$this->extensionModel->getCategoryIndexFromStringOrNumber($subject->getCategory() ?: ''),
$subject->getDescription() ?: '',
$subject->getDependencies() ?: '',
$subject->getUploadcomment() ?: ''
];
++$this->sumRecords;
}
/**
* Method receives an update from a subject.
*
* @param \SplSubject $subject a subject notifying this observer
*/
public function update(\SplSubject $subject)
{
if (is_subclass_of($subject, AbstractExtensionXmlParser::class)) {
if ((int)$subject->getLastuploaddate() > $this->minimumDateToImport) {
$this->loadIntoDatabase($subject);
}
}
}
}
@@ -0,0 +1,87 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Importer;
/*
* 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!
*/
/**
* Importer object for mirror list.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class MirrorListUtility implements \SplObserver
{
/**
* Keeps instance of a XML parser.
*
* @var \TYPO3\CMS\Extensionmanager\Utility\Parser\AbstractMirrorXmlParser
*/
protected $parser;
/**
* Keeps mirrors' details.
*
* @var array
*/
protected $arrTmpMirrors = [];
/**
* Class constructor.
*
* Method retrieves and initializes extension XML parser instance
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
*/
public function __construct()
{
// @todo catch parser exception
$this->parser = \TYPO3\CMS\Extensionmanager\Utility\Parser\XmlParserFactory::getParserInstance('mirror');
if (is_object($this->parser)) {
$this->parser->attach($this);
} else {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
static::class . ': No XML parser available.',
1476108687
);
}
}
/**
* Method collects mirrors' details and returns instance of
* \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors with retrieved details.
*
* @param string $localMirrorListFile absolute path to local mirror xml.gz file
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors
*/
public function getMirrors($localMirrorListFile)
{
$zlibStream = 'compress.zlib://';
$this->parser->parseXml($zlibStream . $localMirrorListFile);
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors $objRepositoryMirrors */
$objRepositoryMirrors = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors::class);
$objRepositoryMirrors->setMirrors($this->arrTmpMirrors);
$this->arrTmpMirrors = [];
return $objRepositoryMirrors;
}
/**
* Method receives an update from a subject.
*
* @param \SplSubject $subject a subject notifying this observer
*/
public function update(\SplSubject $subject)
{
// @todo mirrorxml_abstract_parser
if (is_subclass_of($subject, \TYPO3\CMS\Extensionmanager\Utility\Parser\AbstractXmlParser::class)) {
$this->arrTmpMirrors[] = $subject->getAll();
}
}
}
@@ -0,0 +1,710 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
use TYPO3\CMS\Core\Database\Schema\SqlReader;
use TYPO3\CMS\Core\Service\OpcodeCacheService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
use TYPO3\CMS\Impexp\Utility\ImportExportUtility;
/**
* Extension Manager Install Utility
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
*/
public $objectManager;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
*/
protected $dependencyUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
*/
protected $fileHandlingUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
*/
protected $listUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
*/
public $extensionRepository;
/**
* @var \TYPO3\CMS\Core\Package\PackageManager
*/
protected $packageManager;
/**
* @var \TYPO3\CMS\Core\Cache\CacheManager
*/
protected $cacheManager;
/**
* @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
*/
protected $signalSlotDispatcher;
/**
* @var \TYPO3\CMS\Core\Registry
*/
protected $registry;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility
*/
public function injectDependencyUtility(\TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility)
{
$this->dependencyUtility = $dependencyUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
*/
public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
{
$this->fileHandlingUtility = $fileHandlingUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
*/
public function injectListUtility(\TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility)
{
$this->listUtility = $listUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
*/
public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
{
$this->extensionRepository = $extensionRepository;
}
/**
* @param \TYPO3\CMS\Core\Package\PackageManager $packageManager
*/
public function injectPackageManager(\TYPO3\CMS\Core\Package\PackageManager $packageManager)
{
$this->packageManager = $packageManager;
}
/**
* @param \TYPO3\CMS\Core\Cache\CacheManager $cacheManager
*/
public function injectCacheManager(\TYPO3\CMS\Core\Cache\CacheManager $cacheManager)
{
$this->cacheManager = $cacheManager;
}
/**
* @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
*/
public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
{
$this->signalSlotDispatcher = $signalSlotDispatcher;
}
/**
* @param \TYPO3\CMS\Core\Registry $registry
*/
public function injectRegistry(\TYPO3\CMS\Core\Registry $registry)
{
$this->registry = $registry;
}
/**
* Helper function to install an extension
* also processes db updates and clears the cache if the extension asks for it
*
* @param array $extensionKeys
* @throws ExtensionManagerException
*/
public function install(...$extensionKeys)
{
$flushCaches = false;
foreach ($extensionKeys as $extensionKey) {
$this->loadExtension($extensionKey);
$extension = $this->enrichExtensionWithDetails($extensionKey, false);
$this->saveDefaultConfiguration($extensionKey);
if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
$flushCaches = true;
}
}
if ($flushCaches) {
$this->cacheManager->flushCaches();
} else {
$this->cacheManager->flushCachesInGroup('system');
}
$this->reloadCaches();
$this->updateDatabase($extensionKeys);
foreach ($extensionKeys as $extensionKey) {
$this->processExtensionSetup($extensionKey);
$this->emitAfterExtensionInstallSignal($extensionKey);
}
}
/**
* @param string $extensionKey
*/
public function processExtensionSetup($extensionKey)
{
$extension = $this->enrichExtensionWithDetails($extensionKey, false);
$this->ensureConfiguredDirectoriesExist($extension);
$this->importInitialFiles($extension['siteRelPath'] ?? '', $extensionKey);
$this->importStaticSqlFile($extension['siteRelPath']);
$this->importT3DFile($extension['siteRelPath']);
}
/**
* Helper function to uninstall an extension
*
* @param string $extensionKey
* @throws ExtensionManagerException
*/
public function uninstall($extensionKey)
{
$dependentExtensions = $this->dependencyUtility->findInstalledExtensionsThatDependOnMe($extensionKey);
if (is_array($dependentExtensions) && !empty($dependentExtensions)) {
throw new ExtensionManagerException(
\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
'extensionList.uninstall.dependencyError',
'extensionmanager',
[$extensionKey, implode(',', $dependentExtensions)]
),
1342554622
);
}
$this->unloadExtension($extensionKey);
}
/**
* Wrapper function to check for loaded extensions
*
* @param string $extensionKey
* @return bool TRUE if extension is loaded
*/
public function isLoaded($extensionKey)
{
return $this->packageManager->isPackageActive($extensionKey);
}
/**
* Reset and reload the available extensions
*/
public function reloadAvailableExtensions()
{
$this->listUtility->reloadAvailableExtensions();
}
/**
* Wrapper function for loading extensions
*
* @param string $extensionKey
*/
protected function loadExtension($extensionKey)
{
$this->packageManager->activatePackage($extensionKey);
}
/**
* Wrapper function for unloading extensions
*
* @param string $extensionKey
*/
protected function unloadExtension($extensionKey)
{
$this->packageManager->deactivatePackage($extensionKey);
$this->emitAfterExtensionUninstallSignal($extensionKey);
$this->cacheManager->flushCachesInGroup('system');
}
/**
* Emits a signal after an extension has been installed
*
* @param string $extensionKey
*/
protected function emitAfterExtensionInstallSignal($extensionKey)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionInstall', [$extensionKey, $this]);
}
/**
* Emits a signal after an extension has been uninstalled
*
* @param string $extensionKey
*/
protected function emitAfterExtensionUninstallSignal($extensionKey)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionUninstall', [$extensionKey, $this]);
}
/**
* Checks if an extension is available in the system
*
* @param string $extensionKey
* @return bool
*/
public function isAvailable($extensionKey)
{
return $this->packageManager->isPackageAvailable($extensionKey);
}
/**
* Reloads the package information, if the package is already registered
*
* @param string $extensionKey
* @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available
* @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
* @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed
* @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found
*/
public function reloadPackageInformation($extensionKey)
{
if ($this->packageManager->isPackageAvailable($extensionKey)) {
$this->reloadOpcache();
$this->packageManager->reloadPackageInformation($extensionKey);
}
}
/**
* Fetch additional information for an extension key
*
* @param string $extensionKey
* @param bool $loadTerInformation
* @internal
* @return array
* @throws ExtensionManagerException
*/
public function enrichExtensionWithDetails($extensionKey, $loadTerInformation = true)
{
$extension = $this->getExtensionArray($extensionKey);
if (!$loadTerInformation) {
$availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfInformation([$extensionKey => $extension]);
} else {
$availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation([$extensionKey => $extension]);
}
if (!isset($availableAndInstalledExtensions[$extensionKey])) {
throw new ExtensionManagerException(
'Please check your uploaded extension "' . $extensionKey . '". The configuration file "ext_emconf.php" seems to be invalid.',
1391432222
);
}
return $availableAndInstalledExtensions[$extensionKey];
}
/**
* @param string $extensionKey
* @return array
* @throws ExtensionManagerException
*/
protected function getExtensionArray($extensionKey)
{
$availableExtensions = $this->listUtility->getAvailableExtensions();
if (isset($availableExtensions[$extensionKey])) {
return $availableExtensions[$extensionKey];
}
throw new ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1342864081);
}
/**
* Creates directories as requested in ext_emconf.php
*
* @param array $extension
*/
protected function ensureConfiguredDirectoriesExist(array $extension)
{
$this->fileHandlingUtility->ensureConfiguredDirectoriesExist($extension);
}
/**
* Gets the content of the ext_tables.sql and ext_tables_static+adt.sql files
* Additionally adds the table definitions for the cache tables
*
* @param array $extension
* @deprecated since TYPO3 v9, will be removed with TYPO3v10
*/
public function processDatabaseUpdates(array $extension)
{
trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
$extTablesSqlFile = Environment::getPublicPath() . '/' . $extension['siteRelPath'] . 'ext_tables.sql';
$extTablesSqlContent = '';
if (file_exists($extTablesSqlFile)) {
$extTablesSqlContent .= file_get_contents($extTablesSqlFile);
}
if ($extTablesSqlContent !== '') {
try {
$this->updateDbWithExtTablesSql($extTablesSqlContent);
} catch (\TYPO3\CMS\Core\Database\Schema\Exception\StatementException $e) {
throw new ExtensionManagerException(
$e->getMessage(),
1476340371
);
}
}
$this->importStaticSqlFile($extension['siteRelPath']);
$this->importT3DFile($extension['siteRelPath']);
}
/**
* Emits a signal to manipulate the tables definitions
*
* @param string $extensionKey
* @throws ExtensionManagerException
* @return mixed
* @deprecated since TYPO3 v9, will be removed with TYPO3v10
* @see \TYPO3\CMS\Core\Database\Schema\SqlReader::emitTablesDefinitionIsBeingBuiltSignal
*/
protected function emitTablesDefinitionIsBeingBuiltSignal($extensionKey)
{
$signalReturn = $this->signalSlotDispatcher->dispatch(__CLASS__, 'tablesDefinitionIsBeingBuilt', [[], $extensionKey]);
// This is important to support old associated returns
$signalReturn = array_values($signalReturn);
$sqlString = $signalReturn[0];
if (!is_array($sqlString)) {
throw new ExtensionManagerException(
sprintf(
'The signal %s of class %s returned a value of type %s, but array was expected.',
'tablesDefinitionIsBeingBuilt',
__CLASS__,
gettype($sqlString)
),
1382360258
);
}
if (!empty($sqlString)) {
trigger_error(
sprintf(
'The signal %s of class %s is deprecated and will be removed in TYPO3 v10.0.',
'tablesDefinitionIsBeingBuilt',
__CLASS__
),
E_USER_DEPRECATED
);
}
return $sqlString;
}
/**
* Reload Cache files and Typo3LoadedExtensions
*/
public function reloadCaches()
{
$this->reloadOpcache();
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::loadExtLocalconf(false);
\TYPO3\CMS\Core\Core\Bootstrap::loadBaseTca(false);
\TYPO3\CMS\Core\Core\Bootstrap::loadExtTables(false);
}
/**
* Reloads PHP opcache
*/
protected function reloadOpcache()
{
GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
}
/**
* Executes all safe database statements.
* Tables and fields are created and altered. Nothing gets deleted or renamed here.
*
* @param array $extensionKeys
*/
protected function updateDatabase(array $extensionKeys)
{
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
$schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
$sqlStatements = [];
$sqlStatements[] = $sqlReader->getTablesDefinitionString();
foreach ($extensionKeys as $extensionKey) {
$sqlStatements += $this->emitTablesDefinitionIsBeingBuiltSignal($extensionKey);
}
$sqlStatements = $sqlReader->getCreateTableStatementArray(implode(LF . LF, array_filter($sqlStatements)));
$updateStatements = $schemaMigrator->getUpdateSuggestions($sqlStatements);
$updateStatements = array_merge_recursive(...array_values($updateStatements));
$selectedStatements = [];
foreach (['add', 'change', 'create_table', 'change_table'] as $action) {
if (empty($updateStatements[$action])) {
continue;
}
$selectedStatements = array_merge(
$selectedStatements,
array_combine(array_keys($updateStatements[$action]), array_fill(0, count($updateStatements[$action]), true))
);
}
$schemaMigrator->migrate($sqlStatements, $selectedStatements);
}
/**
* Save default configuration of an extension
*
* @param string $extensionKey
*/
protected function saveDefaultConfiguration($extensionKey)
{
$extensionConfiguration = $this->objectManager->get(ExtensionConfiguration::class);
$extensionConfiguration->synchronizeExtConfTemplateWithLocalConfiguration($extensionKey);
}
/**
* Update database / process db updates from ext_tables
*
* @param string $rawDefinitions The raw SQL statements from ext_tables.sql
* @deprecated since TYPO3 v9, will be removed with TYPO3v10
*/
public function updateDbWithExtTablesSql($rawDefinitions)
{
trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
$statements = $sqlReader->getCreateTableStatementArray($rawDefinitions);
if (count($statements) !== 0) {
$schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
$schemaMigrationService->install($statements);
}
}
/**
* Import static SQL data (normally used for ext_tables_static+adt.sql)
*
* @param string $rawDefinitions
*/
public function importStaticSql($rawDefinitions)
{
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
$statements = $sqlReader->getStatementArray($rawDefinitions);
$schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
$schemaMigrationService->importStaticData($statements, true);
}
/**
* Remove an extension (delete the directory)
*
* @param string $extension
* @throws ExtensionManagerException
*/
public function removeExtension($extension)
{
$absolutePath = $this->fileHandlingUtility->getAbsoluteExtensionPath($extension);
if ($this->fileHandlingUtility->isValidExtensionPath($absolutePath)) {
if ($this->packageManager->isPackageAvailable($extension)) {
// Package manager deletes the extension and removes the entry from PackageStates.php
$this->packageManager->deletePackage($extension);
} else {
// The extension is not listed in PackageStates.php, we can safely remove it
$this->fileHandlingUtility->removeDirectory($absolutePath);
}
} else {
throw new ExtensionManagerException('No valid extension path given.', 1342875724);
}
}
/**
* Checks if an update for an extension is available which also resolves dependencies.
*
* @internal
* @param Extension $extensionData
* @return bool
*/
public function isUpdateAvailable(Extension $extensionData)
{
return (bool)$this->getUpdateableVersion($extensionData);
}
/**
* Returns the updateable version for an extension which also resolves dependencies.
*
* @internal
* @param Extension $extensionData
* @return bool|Extension FALSE if no update available otherwise latest possible update
*/
public function getUpdateableVersion(Extension $extensionData)
{
// Only check for update for TER extensions
$version = $extensionData->getIntegerVersion();
/** @var $extensionUpdates[] \TYPO3\CMS\Extensionmanager\Domain\Model\Extension */
$extensionUpdates = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion(
$extensionData->getExtensionKey(),
$version,
0,
false
);
if ($extensionUpdates->count() > 0) {
foreach ($extensionUpdates as $extensionUpdate) {
try {
$this->dependencyUtility->checkDependencies($extensionUpdate);
if (!$this->dependencyUtility->hasDependencyErrors()) {
return $extensionUpdate;
}
} catch (ExtensionManagerException $e) {
}
}
}
return false;
}
/**
* Uses the export import extension to import a T3D or XML file to PID 0
* Execution state is saved in the this->registry, so it only happens once
*
* @param string $extensionSiteRelPath
*/
protected function importT3DFile($extensionSiteRelPath)
{
$registryKeysToCheck = [
$extensionSiteRelPath . 'Initialisation/data.t3d',
$extensionSiteRelPath . 'Initialisation/dataImported',
];
foreach ($registryKeysToCheck as $registryKeyToCheck) {
if ($this->registry->get('extensionDataImport', $registryKeyToCheck)) {
// Data was imported before => early return
return;
}
}
$importFileToUse = null;
$possibleImportFiles = [
$extensionSiteRelPath . 'Initialisation/data.t3d',
$extensionSiteRelPath . 'Initialisation/data.xml'
];
foreach ($possibleImportFiles as $possibleImportFile) {
if (!file_exists(Environment::getPublicPath() . '/' . $possibleImportFile)) {
continue;
}
$importFileToUse = $possibleImportFile;
}
if ($importFileToUse !== null) {
/** @var ImportExportUtility $importExportUtility */
$importExportUtility = $this->objectManager->get(ImportExportUtility::class);
try {
$importResult = $importExportUtility->importT3DFile(Environment::getPublicPath() . '/' . $importFileToUse, 0);
$this->registry->set('extensionDataImport', $extensionSiteRelPath . 'Initialisation/dataImported', 1);
$this->emitAfterExtensionT3DImportSignal($importFileToUse, $importResult);
} catch (\ErrorException $e) {
$logger = $this->objectManager->get(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
$logger->log(\TYPO3\CMS\Core\Log\LogLevel::WARNING, $e->getMessage());
}
}
}
/**
* Emits a signal after an t3d file was imported
*
* @param string $importFileToUse
* @param int $importResult
*/
protected function emitAfterExtensionT3DImportSignal($importFileToUse, $importResult)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionT3DImport', [$importFileToUse, $importResult, $this]);
}
/**
* Imports a static tables SQL File (ext_tables_static+adt)
* Execution state is saved in the this->registry, so it only happens once
*
* @param string $extensionSiteRelPath
*/
protected function importStaticSqlFile($extensionSiteRelPath)
{
$extTablesStaticSqlRelFile = $extensionSiteRelPath . 'ext_tables_static+adt.sql';
if (!$this->registry->get('extensionDataImport', $extTablesStaticSqlRelFile)) {
$extTablesStaticSqlFile = Environment::getPublicPath() . '/' . $extTablesStaticSqlRelFile;
$shortFileHash = '';
if (file_exists($extTablesStaticSqlFile)) {
$extTablesStaticSqlContent = file_get_contents($extTablesStaticSqlFile);
$shortFileHash = md5($extTablesStaticSqlContent);
$this->importStaticSql($extTablesStaticSqlContent);
}
$this->registry->set('extensionDataImport', $extTablesStaticSqlRelFile, $shortFileHash);
$this->emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile);
}
}
/**
* Emits a signal after a static sql file was imported
*
* @param string $extTablesStaticSqlRelFile
*/
protected function emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionStaticSqlImport', [$extTablesStaticSqlRelFile, $this]);
}
/**
* Imports files from Initialisation/Files to fileadmin
* via lowlevel copy directory method
*
* @param string $extensionSiteRelPath relative path to extension dir
* @param string $extensionKey
*/
protected function importInitialFiles($extensionSiteRelPath, $extensionKey)
{
$importRelFolder = $extensionSiteRelPath . 'Initialisation/Files';
if (!$this->registry->get('extensionDataImport', $importRelFolder)) {
$importFolder = Environment::getPublicPath() . '/' . $importRelFolder;
if (file_exists($importFolder)) {
$destinationRelPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . $extensionKey;
$destinationAbsolutePath = Environment::getPublicPath() . '/' . $destinationRelPath;
if (!file_exists($destinationAbsolutePath) &&
GeneralUtility::isAllowedAbsPath($destinationAbsolutePath)
) {
GeneralUtility::mkdir($destinationAbsolutePath);
}
GeneralUtility::copyDirectory($importRelFolder, $destinationRelPath);
$this->registry->set('extensionDataImport', $importRelFolder, 1);
$this->emitAfterExtensionFileImportSignal($destinationAbsolutePath);
}
}
}
/**
* Emits a signal after extension files were imported
*
* @param string $destinationAbsolutePath
*/
protected function emitAfterExtensionFileImportSignal($destinationAbsolutePath)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionFileImport', [$destinationAbsolutePath, $this]);
}
}
@@ -0,0 +1,301 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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\Core\Environment;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\Type\File\ImageInfo;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
/**
* Utility for dealing with extension list related functions
*
* @TODO: Refactor this API class:
* - The methods depend on each other, they take each others result, that could be done internally
* - There is no good wording to distinguish existing and loaded extensions
* - The name 'listUtility' is not good, the methods could be moved to some 'extensionInformationUtility', or a repository?
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class ListUtility implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility
*/
protected $emConfUtility;
/**
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
*/
protected $extensionRepository;
/**
* @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
*/
protected $installUtility;
/**
* @var \TYPO3\CMS\Core\Package\PackageManager
*/
protected $packageManager;
/**
* @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
*/
protected $signalSlotDispatcher;
/**
* @var array
*/
protected $availableExtensions;
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility
*/
public function injectEmConfUtility(\TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility)
{
$this->emConfUtility = $emConfUtility;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
*/
public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
{
$this->extensionRepository = $extensionRepository;
}
/**
* @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
*/
public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility)
{
$this->installUtility = $installUtility;
}
/**
* @param \TYPO3\CMS\Core\Package\PackageManager $packageManager
*/
public function injectPackageManager(\TYPO3\CMS\Core\Package\PackageManager $packageManager)
{
$this->packageManager = $packageManager;
}
/**
* @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
*/
public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
{
$this->signalSlotDispatcher = $signalSlotDispatcher;
}
/**
* Returns the list of available, but not necessarily loaded extensions
*
* @return array[] All extensions with info
*/
public function getAvailableExtensions()
{
if ($this->availableExtensions === null) {
$this->availableExtensions = [];
$this->emitPackagesMayHaveChangedSignal();
foreach ($this->packageManager->getAvailablePackages() as $package) {
$installationType = $this->getInstallTypeForPackage($package);
$this->availableExtensions[$package->getPackageKey()] = [
'siteRelPath' => str_replace(Environment::getPublicPath() . '/', '', $package->getPackagePath()),
'type' => $installationType,
'key' => $package->getPackageKey(),
'ext_icon' => ExtensionManagementUtility::getExtensionIcon($package->getPackagePath()),
];
}
}
return $this->availableExtensions;
}
/**
* Reset and reload the available extensions
*/
public function reloadAvailableExtensions()
{
$this->availableExtensions = null;
$this->packageManager->scanAvailablePackages();
$this->getAvailableExtensions();
}
/**
* @param string $extensionKey
* @return \TYPO3\CMS\Core\Package\PackageInterface
* @throws \TYPO3\CMS\Core\Package\Exception\UnknownPackageException if the specified package is unknown
*/
public function getExtension($extensionKey)
{
return $this->packageManager->getPackage($extensionKey);
}
/**
* Emits packages may have changed signal
*/
protected function emitPackagesMayHaveChangedSignal()
{
$this->signalSlotDispatcher->dispatch('PackageManagement', 'packagesMayHaveChanged');
}
/**
* Returns "System", "Global" or "Local" based on extension position in filesystem.
*
* @param PackageInterface $package
* @return string
*/
protected function getInstallTypeForPackage(PackageInterface $package)
{
foreach (Extension::returnInstallPaths() as $installType => $installPath) {
if (GeneralUtility::isFirstPartOfStr($package->getPackagePath(), $installPath)) {
return $installType;
}
}
return '';
}
/**
* Enrich the output of getAvailableExtensions() with an array key installed = 1 if an extension is loaded.
*
* @param array $availableExtensions
* @return array
*/
public function getAvailableAndInstalledExtensions(array $availableExtensions)
{
foreach ($this->packageManager->getActivePackages() as $extKey => $_) {
if (isset($availableExtensions[$extKey])) {
$availableExtensions[$extKey]['installed'] = true;
}
}
return $availableExtensions;
}
/**
* Adds the information from the emconf array to the extension information
*
* @param array $extensions
* @return array
*/
public function enrichExtensionsWithEmConfInformation(array $extensions)
{
foreach ($extensions as $extensionKey => $properties) {
$emconf = $this->emConfUtility->includeEmConf($properties);
if ($emconf) {
$extensions[$extensionKey] = array_merge($emconf, $properties);
} else {
unset($extensions[$extensionKey]);
}
}
return $extensions;
}
/**
* Adds the information from the emconf array and TER to the extension information
*
* @param array $extensions
* @return array
*/
public function enrichExtensionsWithEmConfAndTerInformation(array $extensions)
{
$extensions = $this->enrichExtensionsWithEmConfInformation($extensions);
foreach ($extensions as $extensionKey => $properties) {
$terObject = $this->getExtensionTerData($extensionKey, $extensions[$extensionKey]['version'] ?? '');
if ($terObject !== null) {
$extensions[$extensionKey]['terObject'] = $terObject;
$extensions[$extensionKey]['updateAvailable'] = false;
$extensions[$extensionKey]['updateToVersion'] = null;
$extensionToUpdate = $this->installUtility->getUpdateableVersion($terObject);
if ($extensionToUpdate !== false) {
$extensions[$extensionKey]['updateAvailable'] = true;
$extensions[$extensionKey]['updateToVersion'] = $extensionToUpdate;
}
}
}
return $extensions;
}
/**
* Tries to find given extension with given version in TER data.
* If extension is found but not the given version, we return TER data from highest version with version data set to
* given one.
*
* @param string $extensionKey Key of the extension
* @param string $version String representation of version number
* @return Extension|null Extension TER object or NULL if nothing found
*/
protected function getExtensionTerData($extensionKey, $version)
{
$terObject = $this->extensionRepository->findOneByExtensionKeyAndVersion($extensionKey, $version);
if (!$terObject instanceof Extension) {
// Version unknown in TER data, try to find extension
$terObject = $this->extensionRepository->findHighestAvailableVersion($extensionKey);
if ($terObject instanceof Extension) {
// Found in TER now, set version information to the known ones, so we can look if there is a newer one
// Use a cloned object, otherwise wrong information is stored in persistenceManager
$terObject = clone $terObject;
$terObject->setVersion($version);
$terObject->setIntegerVersion(
VersionNumberUtility::convertVersionNumberToInteger($terObject->getVersion())
);
} else {
$terObject = null;
}
}
return $terObject;
}
/**
* Adds information about icon size to the extension information
*
* @param array $extensions
* @return array
*/
public function enrichExtensionsWithIconInformation(array $extensions)
{
foreach ($extensions as &$properties) {
$extIconPath = Environment::getPublicPath() . '/' . $properties['siteRelPath'] . $properties['ext_icon'];
if (@is_file($extIconPath)) {
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $extIconPath);
$properties['ext_icon_width'] = $imageInfo->getWidth();
$properties['ext_icon_height'] = $imageInfo->getHeight();
} else {
$properties['ext_icon_width'] = 0;
$properties['ext_icon_height'] = 0;
}
}
unset($properties);
return $extensions;
}
/**
* Gets all available and installed extension with additional information
* from em_conf and TER (if available)
*
* @return array
*/
public function getAvailableAndInstalledExtensionsWithAdditionalInformation()
{
$availableExtensions = $this->getAvailableExtensions();
$availableAndInstalledExtensions = $this->getAvailableAndInstalledExtensions($availableExtensions);
$availableAndInstalledExtensions = $this->enrichExtensionsWithIconInformation($availableAndInstalledExtensions);
return $this->enrichExtensionsWithEmConfAndTerInformation($availableAndInstalledExtensions);
}
}
@@ -0,0 +1,399 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Abstract parser for TYPO3's extension.xml file.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
abstract class AbstractExtensionXmlParser extends AbstractXmlParser
{
/**
* Keeps current author company of an extension's version.
*
* @var string
*/
protected $authorcompany;
/**
* Keeps current author mail address of an extension's version.
*
* @var string
*/
protected $authoremail;
/**
* Keeps current author name of an extension's version.
*
* @var string
*/
protected $authorname;
/**
* Keeps current category of an extension's version.
*
* @var string
*/
protected $category;
/**
* Keeps current dependencies of an extension's version.
*
* @var string
*/
protected $dependencies;
/**
* Keeps current description of an extension's version.
*
* @var string
*/
protected $description;
/**
* Keeps current download number sum of all extension's versions.
*
* @var string
*/
protected $extensionDownloadCounter;
/**
* Keeps current key of an extension.
*
* @var string
*/
protected $extensionKey;
/**
* Keeps current upload date of an extension's version.
*
* @var string
*/
protected $lastuploaddate;
/**
* Keeps current owner username of an extension's version.
*
* @var string
*/
protected $ownerusername;
/**
* Keeps current reviewstate of an extension's version.
*
* @var string
*/
protected $reviewstate;
/**
* Keeps current state of an extension's version.
*
* @var string
*/
protected $state;
/**
* Keeps current t3x file hash of an extension's version.
*
* @var string
*/
protected $t3xfilemd5;
/**
* Keeps current title of an extension's version.
*
* @var string
*/
protected $title;
/**
* Keeps current upload comment of an extension's version.
*
* @var string
*/
protected $uploadcomment;
/**
* Keeps current version number.
*
* @var string
*/
protected $version;
/**
* Keeps current download number of an extension's version.
*
* @var string
*/
protected $versionDownloadCounter;
/**
* Returns an assoziative array of all extension version properties.
*
* Valid array keys of returned array are:
* extkey, version, alldownloadcounter, downloadcounter, title, description,
* state, reviewstate, category, lastuploaddate, uploadcomment, dependencies,
* authorname, authoremail, authorcompany, ownerusername, t3xfilemd5
*
* @see $extensionKey, $version, $extensionDownloadCounter,
* @return array assoziative array of an extension version's properties
*/
public function getAll()
{
$versionProperties = [];
$versionProperties['extkey'] = $this->extensionKey;
$versionProperties['version'] = $this->version;
$versionProperties['alldownloadcounter'] = $this->extensionDownloadCounter;
$versionProperties['downloadcounter'] = $this->versionDownloadCounter;
$versionProperties['title'] = $this->title;
$versionProperties['description'] = $this->description;
$versionProperties['state'] = $this->state;
$versionProperties['reviewstate'] = $this->reviewstate;
$versionProperties['category'] = $this->category;
$versionProperties['lastuploaddate'] = $this->lastuploaddate;
$versionProperties['uploadcomment'] = $this->uploadcomment;
$versionProperties['dependencies'] = $this->dependencies;
$versionProperties['authorname'] = $this->authorname;
$versionProperties['authoremail'] = $this->authoremail;
$versionProperties['authorcompany'] = $this->authorcompany;
$versionProperties['ownerusername'] = $this->ownerusername;
$versionProperties['t3xfilemd5'] = $this->t3xfilemd5;
return $versionProperties;
}
/**
* Returns download number sum of all extension's versions.
*
* @return string download number sum
* @see $extensionDLCounter, getAll()
*/
public function getAlldownloadcounter()
{
return $this->extensionDownloadCounter;
}
/**
* Returns company name of extension author.
*
* @return string company name of extension author
* @see $authorcompany, getAll()
*/
public function getAuthorcompany()
{
return $this->authorcompany;
}
/**
* Returns e-mail address of extension author.
*
* @return string e-mail address of extension author
* @see $authoremail, getAll()
*/
public function getAuthoremail()
{
return $this->authoremail;
}
/**
* Returns name of extension author.
*
* @return string name of extension author
* @see $authorname, getAll()
*/
public function getAuthorname()
{
return $this->authorname;
}
/**
* Returns category of an extension.
*
* @return string extension category
* @see $category, getAll()
*/
public function getCategory()
{
return $this->category;
}
/**
* Returns dependencies of an extension's version.
*
* @return string extension dependencies
* @see $dependencies, getAll()
*/
public function getDependencies()
{
return $this->dependencies;
}
/**
* Returns description of an extension's version.
*
* @return string extension description
* @see $description, getAll()
*/
public function getDescription()
{
return $this->description;
}
/**
* Returns download number of an extension's version.
*
* @return string download number
* @see $versionDLCounter, getAll()
*/
public function getDownloadcounter()
{
return $this->versionDownloadCounter;
}
/**
* Returns key of an extension.
*
* @return string extension key
* @see $extensionKey, getAll()
*/
public function getExtkey()
{
return $this->extensionKey;
}
/**
* Returns last uploaddate of an extension's version.
*
* @return string last upload date of an extension's version
* @see $lastuploaddate, getAll()
*/
public function getLastuploaddate()
{
return $this->lastuploaddate;
}
/**
* Returns username of extension owner.
*
* @return string extension owner's username
* @see $ownerusername, getAll()
*/
public function getOwnerusername()
{
return $this->ownerusername;
}
/**
* Returns review state of an extension's version.
*
* @return string extension review state
* @see $reviewstate, getAll()
*/
public function getReviewstate()
{
return $this->reviewstate;
}
/**
* Returns state of an extension's version.
*
* @return string extension state
* @see $state, getAll()
*/
public function getState()
{
return $this->state;
}
/**
* Returns t3x file hash of an extension's version.
*
* @return string t3x file hash
* @see $t3xfilemd5, getAll()
*/
public function getT3xfilemd5()
{
return $this->t3xfilemd5;
}
/**
* Returns title of an extension's version.
*
* @return string extension title
* @see $title, getAll()
*/
public function getTitle()
{
return $this->title;
}
/**
* Returns extension upload comment.
*
* @return string extension upload comment
* @see $uploadcomment, getAll()
*/
public function getUploadcomment()
{
return $this->uploadcomment;
}
/**
* Returns version number.
*
* @return string version number
* @see $version, getAll()
*/
public function getVersion()
{
return $this->version;
}
/**
* Method resets version class properties.
*
* @param bool $resetAll If TRUE, additionally extension properties are reset
* @see $extensionKey, $version, $extensionDLCounter, $versionDLCounter,
*/
protected function resetProperties($resetAll = false)
{
// resetting at least class property "version" is mandatory
// as we need to do some magic in regards to
// an extension's and version's child node "downloadcounter"
$this->version = $this->title = $this->versionDownloadCounter = $this->description = $this->state = $this->reviewstate = $this->category = $this->lastuploaddate = $this->uploadcomment = $this->dependencies = $this->authorname = $this->authoremail = $this->authorcompany = $this->ownerusername = $this->t3xfilemd5 = null;
if ($resetAll) {
$this->extensionKey = $this->extensionDownloadCounter = null;
}
}
/**
* Convert dependencies from TER format to EM_CONF format
*
* @param string $dependencies serialized dependency array
* @return string
*/
protected function convertDependencies($dependencies)
{
$newDependencies = [];
$dependenciesArray = unserialize($dependencies, ['allowed_classes' => false]);
if (is_array($dependenciesArray)) {
foreach ($dependenciesArray as $version) {
if (!empty($version['kind']) && !empty($version['extensionKey'])) {
$newDependencies[$version['kind']][$version['extensionKey']] = $version['versionRange'];
}
}
}
return serialize($newDependencies);
}
}
@@ -0,0 +1,181 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Abstract parser for TYPO3's mirror.xml file.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
abstract class AbstractMirrorXmlParser extends AbstractXmlParser
{
/**
* Keeps country of currently processed mirror.
*
* @var string
*/
protected $country;
/**
* Keeps hostname of currently processed mirror.
*
* @var string
*/
protected $host;
/**
* Keeps path to mirrored TER of currently processed mirror.
*
* @var string
*/
protected $path;
/**
* Keeps sponsor link of currently processed mirror.
*
* @var string
*/
protected $sponsorlink;
/**
* Keeps sponsor logo location of currently processed mirror.
*
* @var string
*/
protected $sponsorlogo;
/**
* Keeps sponsor name of currently processed mirror.
*
* @var string
*/
protected $sponsorname;
/**
* Keeps title of currently processed mirror.
*
* @var string
*/
protected $title;
/**
* Returns an associative array of all mirror properties.
*
* Valid array keys of returned array are:
* country, host, path, sponsorlink, sponsorlogo, sponsorname, title
*
* @return array assoziative array of a mirror's properties
* @see $country, $host, $path, $sponsorlink, $sponsorlogo, $sponsorname, $title
*/
public function getAll()
{
$mirrorProperties = [];
$mirrorProperties['title'] = $this->title;
$mirrorProperties['host'] = $this->host;
$mirrorProperties['path'] = $this->path;
$mirrorProperties['country'] = $this->country;
$mirrorProperties['sponsorname'] = $this->sponsorname;
$mirrorProperties['sponsorlink'] = $this->sponsorlink;
$mirrorProperties['sponsorlogo'] = $this->sponsorlogo;
return $mirrorProperties;
}
/**
* Returns country of currently processed mirror.
*
* @return string name of country a mirror is located in
* @see $country, getAll()
*/
public function getCountry()
{
return $this->country;
}
/**
* Returns host of currently processed mirror.
*
* @return string host name
* @see $host, getAll()
*/
public function getHost()
{
return $this->host;
}
/**
* Returns path to mirrored TER of currently processed mirror.
*
* @return string path name
* @see $path, getAll()
*/
public function getPath()
{
return $this->path;
}
/**
* Returns sponsor link of currently processed mirror.
*
* @return string URL of a sponsor's website
* @see $sponsorlink, getAll()
*/
public function getSponsorlink()
{
return $this->sponsorlink;
}
/**
* Returns sponsor logo location of currently processed mirror.
*
* @return string a sponsor's logo location
* @see $sponsorlogo, getAll()
*/
public function getSponsorlogo()
{
return $this->sponsorlogo;
}
/**
* Returns sponsor name of currently processed mirror.
*
* @return string name of sponsor
* @see $sponsorname, getAll()
*/
public function getSponsorname()
{
return $this->sponsorname;
}
/**
* Returns title of currently processed mirror.
*
* @return string title of mirror
* @see $title, getAll()
*/
public function getTitle()
{
return $this->title;
}
/**
* Method resets version class properties.
*
* @see $country, $host, $path, $sponsorlink, $sponsorlogo, $sponsorname, $title
*/
protected function resetProperties()
{
$this->title = $this->host = $this->path = $this->country
= $this->sponsorname = $this->sponsorlink = $this->sponsorlogo = null;
}
}
@@ -0,0 +1,113 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Abstract parser for EM related TYPO3 xml files.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
abstract class AbstractXmlParser implements \SplSubject
{
/**
* Keeps XML parser instance.
*
* @var mixed
*/
protected $objXml;
/**
* Keeps name of required PHP extension
* for this class to work properly.
*
* @var string
*/
protected $requiredPhpExtensions;
/**
* Keeps list of attached observers.
*
* @var \SplObserver[]
*/
protected $observers = [];
/**
* Method attaches an observer.
*
* @param \SplObserver $observer an observer to attach
* @see $observers, detach(), notify()
*/
public function attach(\SplObserver $observer)
{
$this->observers[] = $observer;
}
/**
* Method detaches an attached observer
*
* @param \SplObserver $observer an observer to detach
* @see $observers, attach(), notify()
*/
public function detach(\SplObserver $observer)
{
$key = array_search($observer, $this->observers, true);
if ($key !== false) {
unset($this->observers[$key]);
}
}
/**
* Method notifies attached observers.
*
* @see $observers, attach(), detach()
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
* Method determines if a necessary PHP extension is available.
*
* Method tries to load the extension if necessary and possible.
*
* @return bool TRUE, if PHP extension is available, otherwise FALSE
*/
public function isAvailable()
{
$isAvailable = true;
if (!extension_loaded($this->requiredPhpExtensions)) {
$prefix = PHP_SHLIB_SUFFIX === 'dll' ? 'php_' : '';
if (!(((bool)ini_get('enable_dl') && !(bool)ini_get('safe_mode')) && function_exists('dl') && dl($prefix . $this->requiredPhpExtensions . PHP_SHLIB_SUFFIX))) {
$isAvailable = false;
}
}
return $isAvailable;
}
/**
* Method parses an XML file.
*
* @param string $file GZIP stream resource
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of XML parser errors
*/
abstract public function parseXml($file);
/**
* Create required parser
*/
abstract protected function createParser();
}
@@ -0,0 +1,184 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Parser for TYPO3's extension.xml file.
*
* Depends on PHP ext/xmlreader which should be available
* with PHP >= 5.1.0.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class ExtensionXmlPullParser extends AbstractExtensionXmlParser
{
/**
* Class constructor.
*/
public function __construct()
{
$this->requiredPhpExtensions = 'xmlreader';
}
/**
* Create required parser
*/
protected function createParser()
{
$this->objXml = new \XMLReader();
}
/**
* Method parses an extensions.xml file.
*
* @param string $file GZIP stream resource
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of parser error
*/
public function parseXml($file)
{
$this->createParser();
if (!(is_object($this->objXml) && get_class($this->objXml) === 'XMLReader')) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Unable to create XML parser.', 1342640540);
}
if ($this->objXml->open($file, 'utf-8') === false) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
sprintf('Unable to open file resource %s.', $file),
1476108651
);
}
while ($this->objXml->read()) {
if ($this->objXml->nodeType == \XMLReader::ELEMENT) {
$this->startElement($this->objXml->name);
} else {
if ($this->objXml->nodeType == \XMLReader::END_ELEMENT) {
$this->endElement($this->objXml->name);
} else {
continue;
}
}
}
$this->objXml->close();
}
/**
* Method is invoked when parser accesses start tag of an element.
*
* @param string $elementName element name at parser's current position
*/
protected function startElement($elementName)
{
switch ($elementName) {
case 'extension':
$this->extensionKey = $this->objXml->getAttribute('extensionkey');
break;
case 'version':
$this->version = $this->objXml->getAttribute('version');
break;
case 'downloadcounter':
// downloadcounter could be a child node of
// extension or version
if ($this->version == null) {
$this->extensionDownloadCounter = $this->getElementValue($elementName);
} else {
$this->versionDownloadCounter = $this->getElementValue($elementName);
}
break;
case 'title':
$this->title = $this->getElementValue($elementName);
break;
case 'description':
$this->description = $this->getElementValue($elementName);
break;
case 'state':
$this->state = $this->getElementValue($elementName);
break;
case 'reviewstate':
$this->reviewstate = $this->getElementValue($elementName);
break;
case 'category':
$this->category = $this->getElementValue($elementName);
break;
case 'lastuploaddate':
$this->lastuploaddate = $this->getElementValue($elementName);
break;
case 'uploadcomment':
$this->uploadcomment = $this->getElementValue($elementName);
break;
case 'dependencies':
$this->dependencies = $this->convertDependencies($this->getElementValue($elementName));
break;
case 'authorname':
$this->authorname = $this->getElementValue($elementName);
break;
case 'authoremail':
$this->authoremail = $this->getElementValue($elementName);
break;
case 'authorcompany':
$this->authorcompany = $this->getElementValue($elementName);
break;
case 'ownerusername':
$this->ownerusername = $this->getElementValue($elementName);
break;
case 't3xfilemd5':
$this->t3xfilemd5 = $this->getElementValue($elementName);
break;
}
}
/**
* Method is invoked when parser accesses end tag of an element.
*
* @param string $elementName: element name at parser's current position
*/
protected function endElement($elementName)
{
switch ($elementName) {
case 'extension':
$this->resetProperties(true);
break;
case 'version':
$this->notify();
$this->resetProperties();
break;
}
}
/**
* Method returns the value of an element at XMLReader's current
* position.
*
* Method will read until it finds the end of the given element.
* If element has no value, method returns NULL.
*
* @param string $elementName: name of element to retrieve it's value from
* @return string an element's value if it has a value, otherwise NULL
*/
protected function getElementValue(&$elementName)
{
$value = null;
if (!$this->objXml->isEmptyElement) {
$value = '';
while ($this->objXml->read()) {
if ($this->objXml->nodeType == \XMLReader::TEXT || $this->objXml->nodeType == \XMLReader::CDATA || $this->objXml->nodeType == \XMLReader::WHITESPACE || $this->objXml->nodeType == \XMLReader::SIGNIFICANT_WHITESPACE) {
$value .= $this->objXml->value;
} else {
if ($this->objXml->nodeType == \XMLReader::END_ELEMENT && $this->objXml->name === $elementName) {
break;
}
}
}
}
return $value;
}
}
@@ -0,0 +1,184 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Parser for TYPO3's extension.xml file.
*
* Depends on PHP ext/xml which should be available
* with PHP 4+. This is the parser used in TYPO3
* Core <= 4.3 (without the "collect all data in one
* array" behaviour).
* Notice: ext/xml has proven to be buggy with entities.
* Use at least PHP 5.2.9+ and libxml2 2.7.3+!
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class ExtensionXmlPushParser extends AbstractExtensionXmlParser
{
/**
* Keeps current data of element to process.
*
* @var string
*/
protected $elementData = '';
/**
* Class constructor.
*/
public function __construct()
{
$this->requiredPhpExtensions = 'xml';
}
/**
* Create required parser
*/
protected function createParser()
{
$this->objXml = xml_parser_create();
xml_set_object($this->objXml, $this);
}
/**
* Method parses an extensions.xml file.
*
* @param string $file GZIP stream resource
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of parse errors
*/
public function parseXml($file)
{
$this->createParser();
if (!is_resource($this->objXml)) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Unable to create XML parser.', 1342640663);
}
// Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
$previousValueOfEntityLoader = libxml_disable_entity_loader(true);
// keep original character case of XML document
xml_parser_set_option($this->objXml, XML_OPTION_CASE_FOLDING, false);
xml_parser_set_option($this->objXml, XML_OPTION_SKIP_WHITE, false);
xml_parser_set_option($this->objXml, XML_OPTION_TARGET_ENCODING, 'utf-8');
xml_set_element_handler($this->objXml, 'startElement', 'endElement');
xml_set_character_data_handler($this->objXml, 'characterData');
if (!($fp = fopen($file, 'r'))) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(sprintf('Unable to open file resource %s.', $file), 1342640689);
}
while ($data = fread($fp, 4096)) {
if (!xml_parse($this->objXml, $data, feof($fp))) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(sprintf('XML error %s in line %u of file resource %s.', xml_error_string(xml_get_error_code($this->objXml)), xml_get_current_line_number($this->objXml), $file), 1342640703);
}
}
libxml_disable_entity_loader($previousValueOfEntityLoader);
xml_parser_free($this->objXml);
}
/**
* Method is invoked when parser accesses start tag of an element.
*
* @param resource $parser parser resource
* @param string $elementName element name at parser's current position
* @param array $attrs array of an element's attributes if available
*/
protected function startElement($parser, $elementName, $attrs)
{
switch ($elementName) {
case 'extension':
$this->extensionKey = $attrs['extensionkey'];
break;
case 'version':
$this->version = $attrs['version'];
break;
default:
$this->elementData = '';
}
}
/**
* Method is invoked when parser accesses end tag of an element.
*
* @param resource $parser parser resource
* @param string $elementName Element name at parser's current position
*/
protected function endElement($parser, $elementName)
{
switch ($elementName) {
case 'extension':
$this->resetProperties(true);
break;
case 'version':
$this->notify();
$this->resetProperties();
break;
case 'downloadcounter':
// downloadcounter could be a child node of
// extension or version
if ($this->version == null) {
$this->extensionDownloadCounter = $this->elementData;
} else {
$this->versionDownloadCounter = $this->elementData;
}
break;
case 'title':
$this->title = $this->elementData;
break;
case 'description':
$this->description = $this->elementData;
break;
case 'state':
$this->state = $this->elementData;
break;
case 'reviewstate':
$this->reviewstate = $this->elementData;
break;
case 'category':
$this->category = $this->elementData;
break;
case 'lastuploaddate':
$this->lastuploaddate = $this->elementData;
break;
case 'uploadcomment':
$this->uploadcomment = $this->elementData;
break;
case 'dependencies':
$this->dependencies = $this->convertDependencies($this->elementData);
break;
case 'authorname':
$this->authorname = $this->elementData;
break;
case 'authoremail':
$this->authoremail = $this->elementData;
break;
case 'authorcompany':
$this->authorcompany = $this->elementData;
break;
case 'ownerusername':
$this->ownerusername = $this->elementData;
break;
case 't3xfilemd5':
$this->t3xfilemd5 = $this->elementData;
break;
}
}
/**
* Method is invoked when parser accesses any character other than elements.
*
* @param resource $parser parser resource
* @param string $data An element's value
*/
protected function characterData($parser, $data)
{
$this->elementData .= $data;
}
}
@@ -0,0 +1,148 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Parser for TYPO3's mirrors.xml file.
*
* Depends on PHP ext/xmlreader which should be available
* with PHP >= 5.1.0.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class MirrorXmlPullParser extends AbstractMirrorXmlParser
{
public function __construct()
{
$this->requiredPhpExtensions = 'xmlreader';
}
/**
* Create required parser
*/
protected function createParser()
{
$this->objXml = new \XMLReader();
}
/**
* Method parses an extensions.xml file.
*
* @param string $file file resource, typically a stream
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of XML parser errors
*/
public function parseXml($file)
{
$this->createParser();
if (!(is_object($this->objXml) && get_class($this->objXml) === 'XMLReader')) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Unable to create XML parser.', 1342640820);
}
if ($this->objXml->open($file, 'utf-8') === false) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(sprintf('Unable to open file resource %s.', $file), 1342640893);
}
while ($this->objXml->read()) {
if ($this->objXml->nodeType == \XMLReader::ELEMENT) {
$this->startElement($this->objXml->name);
} else {
if ($this->objXml->nodeType == \XMLReader::END_ELEMENT) {
$this->endElement($this->objXml->name);
} else {
continue;
}
}
}
$this->objXml->close();
}
/**
* Method is invoked when parser accesses start tag of an element.
*
* @param string $elementName element name at parser's current position
* @see endElement()
*/
protected function startElement($elementName)
{
switch ($elementName) {
case 'title':
$this->title = $this->getElementValue($elementName);
break;
case 'host':
$this->host = $this->getElementValue($elementName);
break;
case 'path':
$this->path = $this->getElementValue($elementName);
break;
case 'country':
$this->country = $this->getElementValue($elementName);
break;
case 'name':
$this->sponsorname = $this->getElementValue($elementName);
break;
case 'link':
$this->sponsorlink = $this->getElementValue($elementName);
break;
case 'logo':
$this->sponsorlogo = $this->getElementValue($elementName);
break;
default:
// Do nothing
}
}
/**
* Method is invoked when parser accesses end tag of an element.
*
* @param string $elementName element name at parser's current position
* @see startElement()
*/
protected function endElement($elementName)
{
switch ($elementName) {
case 'mirror':
$this->notify();
$this->resetProperties();
break;
default:
// Do nothing
}
}
/**
* Method returns the value of an element at XMLReader's current
* position.
*
* Method will read until it finds the end of the given element.
* If element has no value, method returns NULL.
*
* @param string &$elementName name of element to retrieve it's value from
* @return string an element's value if it has a value, otherwise NULL
*/
protected function getElementValue(&$elementName)
{
$value = null;
if (!$this->objXml->isEmptyElement) {
$value = '';
while ($this->objXml->read()) {
if ($this->objXml->nodeType == \XMLReader::TEXT || $this->objXml->nodeType == \XMLReader::CDATA || $this->objXml->nodeType == \XMLReader::WHITESPACE || $this->objXml->nodeType == \XMLReader::SIGNIFICANT_WHITESPACE) {
$value .= $this->objXml->value;
} else {
if ($this->objXml->nodeType == \XMLReader::END_ELEMENT && $this->objXml->name === $elementName) {
break;
}
}
}
}
return $value;
}
}
@@ -0,0 +1,159 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Parser for TYPO3's mirrors.xml file.
*
* Depends on PHP ext/xml which should be available
* with PHP 4+. This is the parser used in TYPO3
* Core <= 4.3 (without the "collect all data in one
* array" behaviour).
* Notice: ext/xml has proven to be buggy with entities.
* Use at least PHP 5.2.9+ and libxml2 2.7.3+!
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class MirrorXmlPushParser extends AbstractMirrorXmlParser
{
/**
* @var string
*/
protected $element;
/**
* Class constructor.
*/
public function __construct()
{
$this->requiredPhpExtensions = 'xml';
}
/**
* Create required parser
*/
protected function createParser()
{
$this->objXml = xml_parser_create();
xml_set_object($this->objXml, $this);
}
/**
* Method parses a mirror.xml file.
*
* @param string $file GZIP stream resource
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of XML parser errors
*/
public function parseXml($file)
{
$this->createParser();
if (!is_resource($this->objXml)) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Unable to create XML parser.', 1342641009);
}
// Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
$previousValueOfEntityLoader = libxml_disable_entity_loader(true);
// keep original character case of XML document
xml_parser_set_option($this->objXml, XML_OPTION_CASE_FOLDING, false);
xml_parser_set_option($this->objXml, XML_OPTION_SKIP_WHITE, false);
xml_parser_set_option($this->objXml, XML_OPTION_TARGET_ENCODING, 'utf-8');
xml_set_element_handler($this->objXml, 'startElement', 'endElement');
xml_set_character_data_handler($this->objXml, 'characterData');
if (!($fp = fopen($file, 'r'))) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(sprintf('Unable to open file resource %s.', $file), 1342641010);
}
while ($data = fread($fp, 4096)) {
if (!xml_parse($this->objXml, $data, feof($fp))) {
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(sprintf('XML error %s in line %u of file resource %s.', xml_error_string(xml_get_error_code($this->objXml)), xml_get_current_line_number($this->objXml), $file), 1342641011);
}
}
libxml_disable_entity_loader($previousValueOfEntityLoader);
xml_parser_free($this->objXml);
}
/**
* Method is invoked when parser accesses start tag of an element.
*
* @param resource $parser parser resource
* @param string $elementName element name at parser's current position
* @param array $attrs array of an element's attributes if available
*/
protected function startElement($parser, $elementName, $attrs)
{
switch ($elementName) {
default:
$this->element = $elementName;
}
}
/**
* Method is invoked when parser accesses end tag of an element.
* Although the first parameter seems unused, it needs to be there for
* adherence to the API of xml_set_element_handler
*
* @see xml_set_element_handler
* @param resource $parser parser resource
* @param string $elementName element name at parser's current position
*/
protected function endElement($parser, $elementName)
{
switch ($elementName) {
case 'mirror':
$this->notify();
$this->resetProperties();
break;
default:
$this->element = null;
}
}
/**
* Method is invoked when parser accesses any character other than elements.
* Although the first parameter seems unused, it needs to be there for
* adherence to the API of xml_set_character_data_handler
*
* @see xml_set_character_data_handler
* @param resource $parser parser resource
* @param string $data an element's value
*/
protected function characterData($parser, $data)
{
if (isset($this->element)) {
switch ($this->element) {
case 'title':
$this->title = $data;
break;
case 'host':
$this->host = $data;
break;
case 'path':
$this->path = $data;
break;
case 'country':
$this->country = $data;
break;
case 'name':
$this->sponsorname = $data;
break;
case 'link':
$this->sponsorlink = $data;
break;
case 'logo':
$this->sponsorlogo = $data;
break;
default:
// Do nothing
}
}
}
}
@@ -0,0 +1,78 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Parser;
/*
* 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!
*/
/**
* Factory for XML parsers.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class XmlParserFactory
{
/**
* An array with instances of xml parsers.
* This member is set in the getParserInstance() function.
*
* @var array
*/
protected static $instance = [];
/**
* Keeps array of all available parsers.
*
* @todo This would better be moved to a global configuration array like
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']. (might require EM to be moved in a sysext)
*
* @var array
*/
protected static $parsers = [
'extension' => [
\TYPO3\CMS\Extensionmanager\Utility\Parser\ExtensionXmlPushParser::class => 'ExtensionXmlPushParser.php',
\TYPO3\CMS\Extensionmanager\Utility\Parser\ExtensionXmlPullParser::class => 'ExtensionXmlPullParser.php',
],
'mirror' => [
\TYPO3\CMS\Extensionmanager\Utility\Parser\MirrorXmlPushParser::class => 'MirrorXmlPushParser.php',
\TYPO3\CMS\Extensionmanager\Utility\Parser\MirrorXmlPullParser::class=> 'MirrorXmlPullParser.php',
]
];
/**
* Obtains a xml parser instance.
*
* This function will return an instance of a class that implements
* \TYPO3\CMS\Extensionmanager\Utility\Parser\AbstractExtensionXmlParser
*
* @param string $parserType type of parser, one of extension and mirror
* @param string $excludeClassNames (optional) comma-separated list of class names
* @return \TYPO3\CMS\Extensionmanager\Utility\Parser\AbstractExtensionXmlParser an instance of an extension.xml parser
*/
public static function getParserInstance($parserType, $excludeClassNames = '')
{
if (!isset(self::$instance[$parserType]) || !is_object(self::$instance[$parserType]) || !empty($excludeClassNames)) {
// reset instance
self::$instance[$parserType] = ($objParser = null);
foreach (self::$parsers[$parserType] as $className => $file) {
if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($excludeClassNames, $className)) {
$objParser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($className);
if ($objParser->isAvailable()) {
self::$instance[$parserType] = &$objParser;
break;
}
$objParser = null;
}
}
}
return self::$instance[$parserType];
}
}
@@ -0,0 +1,318 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility\Repository;
/*
* 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\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extensionmanager\Domain\Model\Repository;
use TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository;
use TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
use TYPO3\CMS\Extensionmanager\Utility\Importer\ExtensionListUtility;
use TYPO3\CMS\Extensionmanager\Utility\Importer\MirrorListUtility;
/**
* Central utility class for repository handling.
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class Helper implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* ##########################################
* Problem constants - to be used in bitmasks
* ##########################################
*/
/**
* Type of problem: extension file not existing in file system.
*
* @var int
*/
const PROBLEM_EXTENSION_FILE_NOT_EXISTING = 1;
/**
* Type of problem: wrong hash indicates outdated extension file.
*
* @var int
*/
const PROBLEM_EXTENSION_HASH_CHANGED = 2;
/**
* Type of problem: no version records in database.
*
* @var int
*/
const PROBLEM_NO_VERSIONS_IN_DATABASE = 4;
/**
* Keeps instance of repository class.
*
* @var Repository
*/
protected $repository;
/**
* @var RepositoryRepository
*/
protected $repositoryRepository;
/**
* @var ExtensionRepository
*/
protected $extensionRepository;
/**
* @var ObjectManager
*/
protected $objectManager;
public function __construct()
{
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$repositoryRepository = $this->objectManager->get(RepositoryRepository::class);
$this->extensionRepository = $this->objectManager->get(ExtensionRepository::class);
/** @var Repository $repository */
$repository = $repositoryRepository->findOneTypo3OrgRepository();
if (is_object($repository)) {
$this->setRepository($repository);
}
}
/**
* Method registers required repository instance to work with.
*
* Repository instance is passed by reference.
*
* @param Repository $repository
* @see $repository
*/
public function setRepository(Repository $repository)
{
$this->repository = $repository;
}
/**
* Method fetches extension list file from remote server.
*
* Delegates to {@link fetchFile()}.
*
* @throws ExtensionManagerException
* @see fetchFile()
*/
public function fetchExtListFile()
{
$this->fetchFile($this->getRemoteExtListFile(), $this->getLocalExtListFile());
}
/**
* Method fetches mirror list file from remote server.
*
* Delegates to {@link fetchFile()}.
*
* @throws ExtensionManagerException
* @see fetchFile()
*/
public function fetchMirrorListFile()
{
$this->fetchFile($this->getRemoteMirrorListFile(), $this->getLocalMirrorListFile());
}
/**
* Method fetches contents from remote server and
* writes them into a file in the local file system.
*
* @param string $remoteResource remote resource to read contents from
* @param string $localResource local resource (absolute file path) to store retrieved contents to (must be within typo3temp/)
* @see \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl(), \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile()
* @throws ExtensionManagerException
*/
protected function fetchFile($remoteResource, $localResource)
{
$isOffline = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
if ($isOffline) {
throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078780);
}
if (is_string($remoteResource) && is_string($localResource) && !empty($remoteResource) && !empty($localResource)) {
$fileContent = GeneralUtility::getUrl($remoteResource);
if ($fileContent !== false) {
if (GeneralUtility::writeFileToTypo3tempDir($localResource, $fileContent) !== null) {
throw new ExtensionManagerException(sprintf('Could not write to file %s.', $localResource), 1342635378);
}
} else {
throw new ExtensionManagerException(sprintf('Could not access remote resource %s.', $remoteResource), 1342635425);
}
}
}
/**
* Method returns location of local extension list file.
*
* @return string local location of file
* @see getRemoteExtListFile()
*/
public function getLocalExtListFile()
{
return Environment::getVarPath() . '/extensionmanager/' . (int)$this->repository->getUid() . '.extensions.xml.gz';
}
/**
* Method returns location of remote extension list file.
*
* @return string remote location of file
* @see getLocalExtListFile()
*/
public function getRemoteExtListFile()
{
$mirror = $this->getMirrors(true)->getMirror();
$filePath = 'https://' . $mirror['host'] . $mirror['path'] . 'extensions.xml.gz';
return $filePath;
}
/**
* Method returns location of remote file containing
* the extension checksum hash.
*
* @return string remote location of file
*/
public function getRemoteExtHashFile()
{
$mirror = $this->getMirrors(true)->getMirror();
$filePath = 'https://' . $mirror['host'] . $mirror['path'] . 'extensions.md5';
return $filePath;
}
/**
* Method returns location of local mirror list file.
*
* @return string local location of file
* @see getRemoteMirrorListFile()
*/
public function getLocalMirrorListFile()
{
return Environment::getVarPath() . '/extensionmanager/' . (int)$this->repository->getUid() . '.mirrors.xml.gz';
}
/**
* Method returns location of remote mirror list file.
*
* @return string remote location of file
* @see getLocalMirrorListFile()
*/
public function getRemoteMirrorListFile()
{
$filePath = $this->repository->getMirrorListUrl();
return $filePath;
}
/**
* Method returns available mirrors for registered repository.
*
* If there are no mirrors registered to the repository,
* the method will retrieve them from file system or remote
* server.
*
* @param bool $forcedUpdateFromRemote if boolean TRUE, mirror configuration will always retrieved from remote server
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors instance of repository mirrors class
* @throws ExtensionManagerException
*/
public function getMirrors($forcedUpdateFromRemote = true)
{
$assignedMirror = $this->repository->getMirrors();
if ($forcedUpdateFromRemote || $assignedMirror === null || !is_object($assignedMirror)) {
if ($forcedUpdateFromRemote || !is_file($this->getLocalMirrorListFile())) {
$this->fetchMirrorListFile();
}
$objMirrorListImporter = GeneralUtility::makeInstance(MirrorListUtility::class);
$this->repository->addMirrors($objMirrorListImporter->getMirrors($this->getLocalMirrorListFile()));
}
return $this->repository->getMirrors();
}
/**
* Method returns information if currently available
* extension list might be outdated.
*
* @see \TYPO3\CMS\Extensionmanager\Utility\Repository\Helper::PROBLEM_NO_VERSIONS_IN_DATABASE,
* @throws ExtensionManagerException
* @return int "0" if everything is perfect, otherwise bitmask with problems
*/
public function isExtListUpdateNecessary()
{
if ($this->repository === null) {
throw new ExtensionManagerException('No extension repository was found.', 1500060252);
}
$updateNecessity = 0;
if ($this->extensionRepository->countByRepository($this->repository->getUid()) <= 0) {
$updateNecessity |= self::PROBLEM_NO_VERSIONS_IN_DATABASE;
}
if (!is_file($this->getLocalExtListFile())) {
$updateNecessity |= self::PROBLEM_EXTENSION_FILE_NOT_EXISTING;
} else {
$remotemd5 = GeneralUtility::getUrl($this->getRemoteExtHashFile());
if ($remotemd5 !== false) {
$localmd5 = md5_file($this->getLocalExtListFile());
if ($remotemd5 !== $localmd5) {
$updateNecessity |= self::PROBLEM_EXTENSION_HASH_CHANGED;
}
} else {
throw new ExtensionManagerException('Could not retrieve extension hash file from remote server.', 1342635016);
}
}
return $updateNecessity;
}
/**
* Method updates TYPO3 database with up-to-date
* extension version records.
*
* @return bool TRUE if the extension list was successfully update, FALSE if no update necessary
* @throws ExtensionManagerException
* @see isExtListUpdateNecessary()
*/
public function updateExtList()
{
$updated = false;
$updateNecessity = $this->isExtListUpdateNecessary();
if ($updateNecessity !== 0) {
// retrieval of file necessary
$tmpBitmask = self::PROBLEM_EXTENSION_FILE_NOT_EXISTING | self::PROBLEM_EXTENSION_HASH_CHANGED;
if (($tmpBitmask & $updateNecessity) > 0) {
$this->fetchExtListFile();
$updateNecessity &= ~$tmpBitmask;
}
// database table cleanup
if ($updateNecessity & self::PROBLEM_NO_VERSIONS_IN_DATABASE) {
$updateNecessity &= ~self::PROBLEM_NO_VERSIONS_IN_DATABASE;
} else {
// Use straight query as extbase "remove" is too slow here
// This truncates the whole table. It would be more correct to remove only rows of a specific
// repository, but multiple repository handling is not implemented, and truncate is quicker.
GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_extensionmanager_domain_model_extension')
->truncate('tx_extensionmanager_domain_model_extension');
}
// no further problems - start of import process
if ($updateNecessity === 0) {
$uid = $this->repository->getUid();
$objExtListImporter = $this->objectManager->get(ExtensionListUtility::class);
$objExtListImporter->import($this->getLocalExtListFile(), $uid);
$updated = true;
}
}
return $updated;
}
}
@@ -0,0 +1,129 @@
<?php
namespace TYPO3\CMS\Extensionmanager\Utility;
/*
* 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\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
/**
* Utility to find and execute class.ext_update.php scripts of extensions
* @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
*/
class UpdateScriptUtility
{
/**
* Returns true, if ext_update class says it wants to run.
*
* @param string $extensionKey extension key
* @return mixed NULL, if update is not available, else update script return
*/
public function executeUpdateIfNeeded($extensionKey)
{
$className = $this->requireUpdateScript($extensionKey);
$scriptObject = GeneralUtility::makeInstance($className);
// old em always assumed the method exist, we do so too.
// @TODO: Make this smart, let scripts implement interfaces
// @TODO: With current ext_update construct it is impossible to enforce some type of return
return $scriptObject->access() ? $scriptObject->main() : null;
}
/**
* Require update script.
* Throws exception if update script does not exist, so checkUpdateScriptExists()
* should be called before
*
* @param string $extensionKey
* @return string Class name of update script
* @throws ExtensionManagerException
*/
protected function requireUpdateScript($extensionKey)
{
if (class_exists('ext_update', false)) {
throw new ExtensionManagerException(
'class ext_update for this run does already exist, requiring impossible',
1359748085
);
}
$className = $this->determineUpdateClassName($extensionKey);
if ($className === '') {
throw new ExtensionManagerException(
'Requested update script of extension does not exist',
1359747976
);
}
return $className;
}
/**
* Checks if an update class file exists.
*
* Does not check if some update is needed.
*
* @param string $extensionKey Extension key
* @return bool TRUE, if there is some update script and it needs to be executed
*/
public function checkUpdateScriptExists($extensionKey)
{
$className = $this->determineUpdateClassName($extensionKey);
if ($className !== '') {
$updater = GeneralUtility::makeInstance($className);
return $updater->access();
}
return false;
}
/**
* Determine the real class name to use
*
* @param string $extensionKey
* @return string Returns the final class name if an update script is present, otherwise empty string
* @throws ExtensionManagerException If an update script is present but no ext_update class can be loaded
*/
protected function determineUpdateClassName($extensionKey)
{
$updateScript = GeneralUtility::getFileAbsFileName('EXT:' . $extensionKey . '/class.ext_update.php');
if (!file_exists($updateScript)) {
return '';
}
// get script contents
$scriptSourceCode = GeneralUtility::getUrl($updateScript);
// check if it has a namespace
if (!preg_match('/<\?php.*namespace\s+([^;]+);.*class/is', $scriptSourceCode, $matches)) {
// if no, rename the class with a unique name
$className = 'ext_update' . md5($extensionKey . $scriptSourceCode);
$temporaryFileName = Environment::getVarPath() . '/transient/' . $className . '.php';
if (!file_exists(GeneralUtility::getFileAbsFileName($temporaryFileName))) {
$scriptSourceCode = preg_replace('/^\s*class\s+ext_update\s+/m', 'class ' . $className . ' ', $scriptSourceCode);
GeneralUtility::writeFileToTypo3tempDir($temporaryFileName, $scriptSourceCode);
}
$updateScript = $temporaryFileName;
} else {
$className = $matches[1] . '\ext_update';
}
include_once $updateScript;
if (!class_exists($className, false)) {
throw new ExtensionManagerException(
sprintf('class.ext_update.php of extension "%s" did not declare ext_update class', $extensionKey),
1428176468
);
}
return $className;
}
}