first commit
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user